hanzochang
hanzochang
はじめに
対象読者
前提知識
なぜToken-2022が必要になったのか
SPL Tokenの限界
Token-2022の登場
SPL TokenとToken-2022の違い
機能比較
技術的な互換性
Token-2022の主な拡張機能
どちらを選ぶべきか
Token-2022を選ぶ場面
SPL Tokenを選ぶ場面
Solana Kit(Web3.js 2.0)とは
公式推奨のSDK
従来のSDKからの変更点
新SDKを使うべき理由
環境構築
Solana CLIのインストール
ローカルバリデータの起動
ターミナル1: バリデータを起動
または
ターミナル2: localnetに接続設定
テスト用SOLを取得
依存パッケージのインストール
Token-2022の実装
ファイル構成
1. RPC接続の作成
2. キーペア管理
3. Mintの作成
4. Associated Token Account(ATA)の作成
5. トークンの発行(mintTo)
動作確認
まとめ
SPL TokenからToken-2022への移行理由
実装のポイント
参考リンク
https://s3.ap-northeast-1.amazonaws.com/hanzochang.com/_v2/1769610992886_xapc5fea1ui.png

Token-2022とは - SPL Tokenとの違いと拡張機能について

Token-2022(Token Extensions)はSPL Tokenの上位互換として登場したSolanaの新しいトークン規格です。送金手数料、秘密保持転送、ロイヤリティ管理など13以上の拡張機能を解説し、Solana Kitでの実装方法も紹介します。

公開日2026.01.28

更新日2026.01.28

はじめに

Solanaでトークンを発行する際、従来は「SPL Token」が使われてきました。しかし、現実世界のユースケース(送金手数料の徴収、KYC対応、ロイヤリティ管理など)に対応するには機能が不足していました。

この課題を解決するために登場したのが Token-2022(Token Extensions)です。

この記事では、SPL TokenとToken-2022の違いを明確にし、最新のSolana Kit(Web3.js 2.0)を使った実装方法を解説します。

対象読者

  • Solana開発に興味があるTypeScript / JavaScript開発者
  • SPL Tokenは知っているがToken-2022との違いがわからない方
  • 旧@solana/web3.jsから新しいSDKへの移行を検討している方

前提知識

  • JavaScriptまたはTypeScriptの基本
  • Solanaの基本的な概念(アカウント、トランザクション)

なぜToken-2022が必要になったのか

SPL Tokenの限界

SPL Token(Token Program)は2020年にリリースされ、Solanaエコシステムの基盤となってきました。しかし、以下のような機能がネイティブに実装されていませんでした。

  • 送金手数料(Transfer Fees)の徴収
  • オンチェーンでのロイヤリティ管理
  • 秘密保持転送(Confidential Transfers)
  • 利息発生機能
  • 転送の制御・制限機能

これらの機能は、現実世界の資産をトークン化する際や、規制対応が必要な場面で求められていました。

Token-2022の登場

Token-2022は、SPL Tokenの完全上位互換として設計されました。従来の機能をそのまま使いつつ、「拡張機能(Extensions)」として新しい機能を追加できる設計になっています。

実際に、大手機関もToken-2022を採用しています。

SPL TokenとToken-2022の違い

機能比較

機能SPL TokenToken-2022説明
基本的なトークン操作
Mint作成、発行、送金、残高取得など
Transfer Fees
送金時に発行者が手数料を自動徴収(ステーブルコイン向け)
Confidential Transfers
送金額を暗号化してプライバシーを保護
Transfer Hooks
送金時にカスタムプログラムを呼び出し(ロイヤリティ徴収など)
Interest-Bearing
時間経過で残高が自動的に増加(利息トークン)
Metadata
トークン名・シンボル・画像URLをオンチェーンに保存
Non-Transferable
転送不可のソウルバウンドトークン(資格証明など)
Permanent Delegate
発行者が任意のアカウントからトークンを回収可能(凍結・没収)

技術的な互換性

Token-2022はSPL Tokenと高い互換性を持っています。

  • 同じ命令レイアウト(byte for byte互換)
  • Mintアカウントの最初の82バイトは同じ構造
  • Token アカウントの最初の165バイトは同じ構造

そのため、既存のSPL Token向けコードの多くがToken-2022でも動作します。

Token-2022の主な拡張機能

Token-2022は13個以上の拡張機能を提供しています。主なものを紹介します。

Transfer Fees(送金手数料)

トークンの送金時に、発行者が手数料を自動的に徴収できます。

送金額: 100トークン
手数料率: 1%
→ 受取人: 99トークン
→ 発行者: 1トークン(手数料として自動徴収)

Transfer Hooks(転送フック)

トークン転送時にカスタムプログラムを呼び出せます。ロイヤリティの徴収やアクセス制御に使えます。

Confidential Transfers(秘密保持転送)

転送額や残高を暗号化して非公開にできます。商業契約の詳細を保護する場面で有用です。

Interest-Bearing Tokens(利息発生トークン)

トークン残高に対して自動的に利息が発生する設計が可能です。

Metadata(メタデータ)

トークンの名前、シンボル、画像URLなどをオンチェーンに直接保存できます。外部のMetaplexメタデータプログラムが不要になります。

Non-Transferable(譲渡不可)

SBT(Soulbound Token)のように、一度発行したら転送できないトークンを作成できます。

どちらを選ぶべきか

Token-2022を選ぶ場面

  • ステーブルコインや規制対応が必要なトークン
  • NFTでロイヤリティを自動徴収したい
  • 現実資産のトークン化(RWA)
  • プライバシーが必要な転送
  • 新規プロジェクトで将来の拡張性を確保したい

SPL Tokenを選ぶ場面

  • シンプルなファンジブルトークン
  • 既存エコシステム(DEXやウォレット)との互換性を最優先
  • 拡張機能が不要な場合

Token-2022対応のウォレットやDEXは増えていますが、SPL Tokenほど普及していない点は考慮が必要です。

Solana Kit(Web3.js 2.0)とは

Token-2022を実装する前に、最新のSolana SDKについて理解しておきましょう。

公式推奨のSDK

Solana公式ドキュメントでは、以下のように明記されています。

@solana/kit is the recommended TypeScript SDK for building on Solana.

従来の@solana/web3.js 1.x系は「legacy(レガシー)」と位置付けられており、新規開発には@solana/kitの使用が推奨されています。

従来のSDKからの変更点

従来(1.x)新(2.0)
Keypair
KeyPairSigner
PublicKey
Address
number
BigInt
Connection
createSolanaRpc / createSolanaRpcSubscriptions

新SDKを使うべき理由

  • 完全なTree-shaking対応: 使用する機能のみバンドル(Solana Explorerで26%のサイズ削減実績)
  • 外部依存関係ゼロ: 軽量でセキュア
  • ネイティブEd25519暗号化API: 高速な署名処理
  • TypeScriptによる型安全性: 開発時のエラー検出が向上
  • BigIntネイティブサポート: 大きな数値も精度を失わずに扱える

これからSolana開発を始める場合は、@solana/kitを使いましょう。

環境構築

Solana CLIのインストール

sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

インストール後、~/.config/solana/id.jsonにキーペアが生成されます。

ローカルバリデータの起動

開発にはローカルバリデータを使用します。solana-test-validatorまたはSurfpoolが使えます。

# ターミナル1: バリデータを起動
solana-test-validator
# または
surfpool start
# ターミナル2: localnetに接続設定
solana config set --url localhost

# テスト用SOLを取得
solana airdrop 10

依存パッケージのインストール

pnpm add @solana/kit @solana-program/system @solana-program/token-2022
パッケージ役割
@solana/kit
RPC接続、キーペア生成、トランザクション構築
@solana-program/system
システムプログラム(アカウント作成)
@solana-program/token-2022
Token-2022プログラムのクライアント

Token-2022の実装

ファイル構成

以下の順番で実装を解説します。

src/
├── index.ts           # エクスポート
├── types.ts           # 型定義
└── utils/
│   ├── connection.ts  # 1. RPC接続
│   └── keypair.ts     # 2. キーペア管理
├── mint.ts            # 3. Mint作成
├── account.ts         # 4. ATA操作
└── transfer.ts        # 5. 発行・送金

1. RPC接続の作成

Solanaブロックチェーンと通信するには、RPCエンドポイントへの接続が必要です。Solana Kitでは2種類の接続を使い分けます。

  • HTTP(RPC): アカウント情報の取得、トランザクションの送信など通常のリクエスト
  • WebSocket: トランザクションの確認待ち、アカウント変更の購読などリアルタイム通知
import { createSolanaRpc, createSolanaRpcSubscriptions } from '@solana/kit'

export const LOCALNET_HTTP = 'http://127.0.0.1:8899'
export const LOCALNET_WS = 'ws://127.0.0.1:8900'

export const createConnection = (httpUrl: string = LOCALNET_HTTP, wsUrl: string = LOCALNET_WS) => ({
  rpc: createSolanaRpc(httpUrl),
  rpcSubscriptions: createSolanaRpcSubscriptions(wsUrl),
})

export const createLocalnetConnection = () => createConnection(LOCALNET_HTTP, LOCALNET_WS)

ローカル開発では127.0.0.1:8899(HTTP)と127.0.0.1:8900(WebSocket)がデフォルトです。本番環境ではHeliusやQuickNodeなどのRPCプロバイダのURLに差し替えます。

2. キーペア管理

Solanaでトランザクションを送信するには、署名用のキーペア(秘密鍵と公開鍵のペア)が必要です。キーペアは以下の用途で使用します。

  • トランザクション手数料の支払い: SOLを消費するため、残高を持つキーペアが必要
  • トランザクションへの署名: 送金やMint作成などの操作を承認
  • 権限の証明: MintAuthorityやFreezeAuthorityとしての操作
import {
  createKeyPairSignerFromBytes,
  generateKeyPairSigner,
  type KeyPairSigner,
} from '@solana/kit'
import { readFile } from 'fs/promises'

export const createKeypair = async (): Promise<KeyPairSigner> => {
  return generateKeyPairSigner()
}

export const loadKeypairFromFile = async (filePath: string): Promise<KeyPairSigner> => {
  const fileContent = await readFile(filePath, 'utf-8')
  const secretKey = new Uint8Array(JSON.parse(fileContent))
  return createKeyPairSignerFromBytes(secretKey)
}

export const loadDefaultKeypair = async (): Promise<KeyPairSigner> => {
  const homeDir = process.env.HOME || process.env.USERPROFILE || ''
  const defaultPath = `${homeDir}/.config/solana/id.json`
  return loadKeypairFromFile(defaultPath)
}

loadDefaultKeypairは、Solana CLIで生成された~/.config/solana/id.jsonを読み込みます。開発時はこのキーペアを使い回すと便利です。

3. Mintの作成

Mintとは、トークンの「発行元」となるアカウントです。ERC-20のコントラクトアドレスに相当します。Mintには以下の情報が含まれます。

  • decimals: 小数点以下の桁数(USDCは6、SOLは9)
  • supply: 現在の発行総量
  • mintAuthority: 新規トークンを発行できる権限者
  • freezeAuthority: トークンアカウントを凍結できる権限者
import {
  appendTransactionMessageInstructions,
  createTransactionMessage,
  generateKeyPairSigner,
  getSignatureFromTransaction,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransactionMessageWithSigners,
} from '@solana/kit'
import { getCreateAccountInstruction } from '@solana-program/system'
import {
  getMintSize,
  getInitializeMint2Instruction,
  TOKEN_2022_PROGRAM_ADDRESS,
} from '@solana-program/token-2022'

export const createMint = async ({
  connection,
  payer,
  mintAuthority,
  freezeAuthority,
  decimals = 9,
}) => {
  const { rpc, rpcSubscriptions } = connection

  // 1. Mint用のキーペアを生成
  const mint = await generateKeyPairSigner()

  // 2. Mintアカウントに必要なスペースとrentを計算
  const mintSpace = BigInt(getMintSize())
  const mintRent = await rpc.getMinimumBalanceForRentExemption(mintSpace).send()

  // 3. アカウント作成のInstruction
  const createAccountInstruction = getCreateAccountInstruction({
    payer,
    newAccount: mint,
    lamports: mintRent,
    space: mintSpace,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS, // SPL Tokenとの違い
  })

  // 4. Mint初期化のInstruction
  const initializeMintInstruction = getInitializeMint2Instruction({
    mint: mint.address,
    decimals,
    mintAuthority,
    freezeAuthority: freezeAuthority ?? null,
  })

  // 5. 最新のブロックハッシュを取得
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send()

  // 6. トランザクションメッセージを構築(pipeパターン)
  const transactionMessage = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayerSigner(payer, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
    (tx) =>
      appendTransactionMessageInstructions(
        [createAccountInstruction, initializeMintInstruction],
        tx,
      ),
  )

  // 7. 署名
  const signedTransaction = await signTransactionMessageWithSigners(transactionMessage)

  // 8. 送信・確認
  const sendAndConfirm = sendAndConfirmTransactionFactory({
    rpc,
    rpcSubscriptions,
  })

  await sendAndConfirm(signedTransaction, { commitment: 'confirmed' })

  const signature = getSignatureFromTransaction(signedTransaction)

  return { mint, signature }
}

SPL Tokenとの違いはprogramAddressTOKEN_2022_PROGRAM_ADDRESSを指定する点です。

pipeパターンについて

Solana Kitの特徴的なパターンとして、pipeを使った関数合成があります。

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),      // 1. 空のメッセージ作成
  (tx) => setTransactionMessageFeePayerSigner(payer, tx),  // 2. 手数料支払者設定
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),  // 3. 有効期限設定
  (tx) => appendTransactionMessageInstructions([...], tx),  // 4. 命令追加
);

トランザクションメッセージを段階的に構築していくスタイルです。

4. Associated Token Account(ATA)の作成

Solanaでは、トークンを受け取るためにウォレットとは別の「トークンアカウント」が必要です。これはEthereumと異なる重要なポイントです。

  • ウォレット(System Account): SOLを保持、トランザクションに署名
  • トークンアカウント: 特定のMintのトークン残高を保持

Associated Token Account(ATA)は、ウォレットアドレスとMintアドレスから一意に決まるトークンアカウントです。「このウォレットの、このトークン用の財布」と考えるとわかりやすいです。

import {
  findAssociatedTokenPda,
  getCreateAssociatedTokenIdempotentInstructionAsync,
  TOKEN_2022_PROGRAM_ADDRESS,
} from '@solana-program/token-2022'

export const findAta = async (mint, owner) => {
  const [ata] = await findAssociatedTokenPda({
    mint,
    owner,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
  })
  return ata
}

export const getOrCreateAssociatedTokenAccount = async ({ connection, payer, mint, owner }) => {
  const { rpc, rpcSubscriptions } = connection

  const ata = await findAta(mint, owner)

  // Idempotent版を使うと、既存の場合はスキップ
  const createAtaInstruction = await getCreateAssociatedTokenIdempotentInstructionAsync({
    payer,
    owner,
    mint,
    ata,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,
  })

  // ... トランザクション構築・送信(省略)

  return { ata, signature }
}

getCreateAssociatedTokenIdempotentInstructionAsync(Idempotent版)を使うと、ATAが既に存在する場合でもエラーにならず安全に実行できます。

5. トークンの発行(mintTo)

Mintを作成しただけではトークンは存在しません。mintTo操作でトークンを「発行」して、指定したトークンアカウントに送ります。この操作にはMintAuthorityの署名が必要です。

import { getMintToInstruction } from '@solana-program/token-2022'

export const mintTo = async ({ connection, payer, mint, destination, authority, amount }) => {
  const { rpc, rpcSubscriptions } = connection

  const mintToInstruction = getMintToInstruction({
    mint,
    token: destination,      // 発行先のトークンアカウント(ATA)
    mintAuthority: authority, // MintAuthorityの署名
    amount, // BigIntを使用
  })

  // ... トランザクション構築・送信(省略)

  return signature
}

amountにはBigIntを使います。例えば、decimalsが9のトークンで100トークンを発行する場合は100n * 10n ** 9nとなります。

動作確認

以下のようなスクリプトで動作確認ができます。

import {
  createLocalnetConnection,
  loadDefaultKeypair,
  createMint,
  getOrCreateAssociatedTokenAccount,
  mintTo,
} from '../src/index.js'

const main = async () => {
  // 1. 接続とキーペアの準備
  const connection = createLocalnetConnection()
  const payer = await loadDefaultKeypair()

  console.log('Payer:', payer.address)

  // 2. Mintの作成
  const { mint } = await createMint({
    connection,
    payer,
    mintAuthority: payer.address,
    decimals: 9,
  })

  console.log('Mint created:', mint.address)

  // 3. ATAの作成
  const { ata } = await getOrCreateAssociatedTokenAccount({
    connection,
    payer,
    mint: mint.address,
    owner: payer.address,
  })

  console.log('ATA:', ata)

  // 4. トークンの発行(100トークン)
  await mintTo({
    connection,
    payer,
    mint: mint.address,
    destination: ata,
    authority: payer,
    amount: 100n * 10n ** 9n,
  })

  console.log('Minted 100 tokens!')
}

main().catch(console.error)

まとめ

Token-2022はSPL Tokenの上位互換として、現実世界のユースケースに対応する拡張機能を提供します。

SPL TokenからToken-2022への移行理由

  • 送金手数料、ロイヤリティ、秘密保持転送などの機能が必要
  • 規制対応やコンプライアンスへの対応
  • 将来の拡張性を確保したい

実装のポイント

  • TOKEN_2022_PROGRAM_ADDRESSを使用する
  • Solana Kitのpipeパターンでトランザクションを構築
  • BigIntで数値を扱う
  • KeyPairSignerAddressという新しい型システム

Token-2022には今回紹介しなかった多くの拡張機能があります。Transfer FeesやTransfer Hooksなど、用途に応じて活用してみてください。

参考リンク

picture
hanzochang - 半澤勇大
慶應義塾大学卒業後、Webプランナーとして勤務。 ナショナルクライアントのキャンペーンサイトの企画・演出を担当。 その後開発会社に創業メンバーとして参加。 Fintech案件や大手企業のDXプロジェクトに関わり、その後個人事業主として独立し、 2023年にWeb3に特化した開発会社として法人化しました。 現在はWeb3アプリ開発を中心にAI開発フローの整備を行っています。
また、趣味で2017年ごろより匿名アカウントでCryptoの調査等を行い、 ブロックチェーンメディアやSNSでビットコイン論文等の図解等を発信していました。
X (Twitter)

お問い合わせはこちらから

ご希望に応じて職務経歴書や過去のポートフォリオを提出可能ですので、必要な方はお申し付けください。
また内容とによっては返信ができない場合や、お時間をいただく場合がございます。あらかじめご了承ください。