
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を採用しています。
- Paxos(USDP): 規制対応のステーブルコイン
- GMO Trust: 日本の金融機関によるステーブルコイン
- PayPal USD(PYUSD): PayPalのステーブルコイン
SPL TokenとToken-2022の違い
機能比較
| 機能 | SPL Token | Token-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との違いはprogramAddressにTOKEN_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で数値を扱うKeyPairSignerとAddressという新しい型システム
Token-2022には今回紹介しなかった多くの拡張機能があります。Transfer FeesやTransfer Hooksなど、用途に応じて活用してみてください。
参考リンク
お問い合わせはこちらから
ご希望に応じて職務経歴書や過去のポートフォリオを提出可能ですので、必要な方はお申し付けください。
また内容とによっては返信ができない場合や、お時間をいただく場合がございます。あらかじめご了承ください。
