本文へ移動
hanzochang

技術と実践のノート

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

公開

著者:株式会社hanzochang

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

はじめに

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発行者が任意のアカウントからトークンを回収可能(凍結・没収)
機能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(送金手数料)

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

text
送金額: 100トークン
手数料率: 1%
→ 受取人: 99トークン
→ 発行者: 1トークン(手数料として自動徴収)
text
送金額: 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)
KeypairKeyPairSigner
PublicKeyAddress
numberBigInt
ConnectioncreateSolanaRpc / createSolanaRpcSubscriptions
従来(1.x)新(2.0)
KeypairKeyPairSigner
PublicKeyAddress
numberBigInt
ConnectioncreateSolanaRpc / createSolanaRpcSubscriptions
新SDKを使うべき理由
  • 完全なTree-shaking対応: 使用する機能のみバンドル(Solana Explorerで26%のサイズ削減実績)
  • 外部依存関係ゼロ: 軽量でセキュア
  • ネイティブEd25519暗号化API: 高速な署名処理
  • TypeScriptによる型安全性: 開発時のエラー検出が向上
  • BigIntネイティブサポート: 大きな数値も精度を失わずに扱える

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

環境構築

Solana CLIのインストール

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

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

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

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

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

# テスト用SOLを取得
solana airdrop 10
bash
# ターミナル2: localnetに接続設定
solana config set --url localhost

# テスト用SOLを取得
solana airdrop 10
依存パッケージのインストール
bash
pnpm add @solana/kit @solana-program/system @solana-program/token-2022
bash
pnpm add @solana/kit @solana-program/system @solana-program/token-2022
パッケージ役割
@solana/kitRPC接続、キーペア生成、トランザクション構築
@solana-program/systemシステムプログラム(アカウント作成)
@solana-program/token-2022Token-2022プログラムのクライアント
パッケージ役割
@solana/kitRPC接続、キーペア生成、トランザクション構築
@solana-program/systemシステムプログラム(アカウント作成)
@solana-program/token-2022Token-2022プログラムのクライアント
Token-2022の実装

ファイル構成

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

text
src/
├── index.ts           # エクスポート
├── types.ts           # 型定義
└── utils/
│   ├── connection.ts  # 1. RPC接続
│   └── keypair.ts     # 2. キーペア管理
├── mint.ts            # 3. Mint作成
├── account.ts         # 4. ATA操作
└── transfer.ts        # 5. 発行・送金
text
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: トランザクションの確認待ち、アカウント変更の購読などリアルタイム通知
typescript
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)
typescript
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としての操作
typescript
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)
}
typescript
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: トークンアカウントを凍結できる権限者
typescript
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 }
}
typescript
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を使った関数合成があります。

typescript
const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),      // 1. 空のメッセージ作成
  (tx) => setTransactionMessageFeePayerSigner(payer, tx),  // 2. 手数料支払者設定
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),  // 3. 有効期限設定
  (tx) => appendTransactionMessageInstructions([...], tx),  // 4. 命令追加
);
typescript
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アドレスから一意に決まるトークンアカウントです。「このウォレットの、このトークン用の財布」と考えるとわかりやすいです。

typescript
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 }
}
typescript
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の署名が必要です。

typescript
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
}
typescript
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となります。

動作確認

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

typescript
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)
typescript
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など、用途に応じて活用してみてください。

参考リンク

この記事が参考になったら

この記事が役に立ったと思いましたら、よろしければフォローをお願いします。

Xでフォローする

Token-2022を活用したトークン設計や発行を検討されていましたら、お気軽にご連絡ください。

お問い合わせ

この記事を書いた人

Webプランナー、開発会社の創業メンバーを経て、Fintech・DX・Web3の企画・開発に従事。2021年に独立し、2023年に株式会社hanzochangを設立。現在はAIを活用した業務改善と、自社サービス「指定管理者制度AI」の開発・運用に取り組んでいます。

半澤勇大のXアカウント(新しいタブで開く)

この記事の株式会社hanzochangについて
少し紹介させてください!

この記事に関連する当社のサービス

Web3アプリ開発受託サービス

ウォレット連携やNFTを、使いやすいアプリに。

Solanaなどのブロックチェーンを使ったアプリを、企画・検証から開発・運用まで支援します。ウォレット連携やコントラクトだけでなく、利用者が操作する画面までまとめて設計します。

サービスの詳細を見る
Jくんが接続されたアプリを組み立て、鉛筆で原稿を作るイラスト。Web3サービス開発とコンテンツ作成を表す。
  • 企画・技術検証

    事業の目的に合わせ、必要な機能と技術を検討。

  • アプリ・連携開発

    ウォレットやコントラクトと画面を一体で開発。

  • 公開・運用支援

    公開後の保守や機能改善まで継続して対応。

Web3アプリの開発について、お気軽にご相談ください。