見出し画像

Solanaでトークン(SPL Token)を作成する方法

こちらは、Solanaブロックチェーン上でSolanaトークン(SPL Token)を作成する方法について解説するガイドです。JavascriptでMetaplex UmiクライアントラッパーとMpl Toolboxパッケージを使用することができます。これにより、スクリプトやフロントエンド、バックエンドフレームワークで使用可能な関数を作成できます。

前提条件

  • お好みのコードエディタ(Visual Studio Codeを推奨)

  • Node 18.x.x以上

初期設定

まず、パッケージマネージャー(npm、yarn、pnpm、bunなど)を使用して新しいプロジェクトを作成します(任意)。必要な情報を求められたら入力してください。

npm init

必要なパッケージ

このガイドで使用する必要なパッケージをインストールします。

npm i @metaplex-foundation/umi
npm i @metaplex-foundation/umi-bundle-defaults
npm i @metaplex-foundation/mpl-token-metadata
npm i @metaplex-foundation/umi-uploader-irys;
npm i @metaplex-foundation/mpl-toolbox;

インポートとラッパー関数

このガイドでは、必要なすべてのインポートをリストアップし、コードを実行するためのラッパー関数を作成します。

import {
  createFungible,
  mplTokenMetadata,
} from '@metaplex-foundation/mpl-token-metadata'
import {
  createTokenIfMissing,
  findAssociatedTokenPda,
  getSplAssociatedTokenProgramId,
  mintTokensTo,
} from '@metaplex-foundation/mpl-toolbox'
import {
  generateSigner,
  percentAmount,
  createGenericFile,
  signerIdentity,
  sol,
} from '@metaplex-foundation/umi'
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
import { irysUploader } from '@metaplex-foundation/umi-uploader-irys'
import { base58 } from '@metaplex-foundation/umi/serializers'
import fs from 'fs'
import path from 'path'

// Create the wrapper function
const createAndMintTokens = async () => {
  ///
  ///
  ///  all our code will go in here
  ///
  ///
}

// run the wrapper function
createAndMintTokens()

Umiのセットアップ

この例では「generatedSigner()」を使用してUmiをセットアップする手順を説明します。

「umi」変数とコードブロックは「createAndMintTokens()」関数の内部または外部のどちらに配置しても構いません。重要なのは「createAndMintTokens()」関数自体から「umi」変数にアクセスできることです。

const umi = createUmi('https://api.devnet.solana.com')
  .use(mplTokenMetadata())
  .use(irysUploader())

// Generates a new private key to use with umi.
const signer = generateSigner(umi)

// Assign the private key to be the default Umi identity.
umi.use(signerIdentity(signer))

// If using a newly generated privatekey/wallet you may
// need to airdrop (Devnet only) or transfer SOL to the new address.
await umi.rpc.airdrop(umi.identity.publicKey, sol(1));

トークンの作成

画像のアップロード

まず最初に、トークンを表現し認識しやすくする画像が必要です。これはjpeg、png、またはgif形式で構いません。

Umiには、Arweave、NftStore、AWS、ShdwDriveにファイルを保存するためのプラグインがあります。これらのプラグインをダウンロードしてファイルをアップロードできます。このガイドの冒頭で、Arweaveブロックチェーンにコンテンツを保存する「irysUploader()」プラグインをインストールしましたので、それを使用することにします。

💡 ローカルスクリプト/Node.js
この例では、Irysを使用してArweaveにアップロードするローカルスクリプト/Node.jsアプローチを使用しています。異なるストレージプロバイダーにファイルをアップロードしたり、ブラウザからアップロードしたりする場合は、別のアプローチを取る必要があります。ブラウザのシナリオでは「fs」のインポートと使用は機能しません。

// use `fs` to read file via a string path.

const imageFile = fs.readFileSync(
  path.join(__dirname, '..', '/assets/islandDao.jpg')
)

// Use `createGenericFile` to transform the file into a `GenericFile` type
// that Umi can understand. Make sure you set the mimi tag type correctly
// otherwise Arweave will not know how to display your image.

const umiImageFile = createGenericFile(imageFile, 'island-dao.jpeg', {
  tags: [{ name: 'Content-Type', value: 'image/jpeg' }],
})

// Here we upload the image to Arweave via Irys and we get returned a uri
// address where the file is located. You can log this out but as the
// uploader can takes an array of files it also returns an array of uris.
// To get the uri we want we can call index [0] in the array.

const imageUri = await umi.uploader.upload([umiImageFile]).catch((err) => {
  throw new Error(err)
})

console.log(imageUri[0])

メタデータのアップロード

有効で機能する画像URIができたら、SPLトークンのメタデータの作業を開始できます。

ファンジブルトークンのオフチェーンメタデータの標準は以下の通りです:

{
  "name": "TOKEN_NAME",
  "symbol": "TOKEN_SYMBOL",
  "description": "TOKEN_DESC",
  "image": "TOKEN_IMAGE_URL"
}

ここで含まれるフィールドは以下の通りです:

name
トークンの名前。

symbol
トークンの略称。例えば、Solanaの略称はSOLです。

description
トークンの説明。

image
これは、先ほどアップロードした画像のURI(または画像のオンラインロケーション)に設定されます。

// Example metadata
const metadata = {
  name: 'The Kitten Coin',
  symbol: 'KITTEN',
  description: 'The Kitten Coin is a token created on the Solana blockchain',
  image: imageUri, // Either use variable or paste in string of the uri.
}

// Call upon Umi's `uploadJson` function to upload our metadata to Arweave via Irys.

const metadataUri = await umi.uploader.uploadJson(metadata).catch((err) => {
  throw new Error(err)
})

すべてが計画通りに進んだ場合「metadataUri」変数にはアップロードされたJSONファイルのURIが格納されます。

トークンの作成

Solanaブロックチェーン上で新しいトークンを作成する際、新しいデータに対応するためにいくつかのアカウントを作成する必要があります。

ミントアカウントの作成

トークンをミントする場合、トークンアカウント(個人のウォレット内のミントされたトークンを保持)が必要です。トークンをミントする際、最初の作成者が更新権限とミント権限を保持します。JupiterやOrcaなどのDEXにトークンをリストする際には、検証のために更新権限とミント権限の両方を取り消す必要があります。

ミントアカウント

ミントアカウントを作成し、ミントデータを保存するために「createFungible」ヘルパーメソッドを使用します。このメソッドは、アカウント作成プロセスを簡略化します。

この関数にはミントアドレス用のキーペアを提供する必要があります。また、JSONファイルから追加のメタデータを提供する必要があります。このメタデータには、トークンの名前とメタデータURIアドレスが含まれます。

const mintSigner = generateSigner(umi)

const createMintIx = await createFungible(umi, {
  mint: mintSigner,
  name: 'The Kitten Coin',
  uri: metadataUri, // we use the `metadataUri` variable we created earlier that is storing our uri.
  sellerFeeBasisPoints: percentAmount(0),
  decimals: 9, // set the amount of decimals you want your token to have.
})

トークンのミント

トークンアカウント

トークンを即座にミントする場合、誰かのウォレット内にトークンを保存する場所が必要です。これを行うには、ウォレットとミントアドレスの両方に基づいて数学的にアドレスを生成します。これはAssociated Token Account (ATA)と呼ばれ、単にトークンアカウントとも呼ばれることがあります。

トークンアカウントアドレスの生成

まず最初に、トークンアカウントのアドレスがどうあるべきかを把握する必要があります。mpl-toolboxには、そのための便利な関数があり、インポートして使用できます。

const createTokenIx = createTokenIfMissing(umi, {
  mint: mintSigner.publickey,
  owner: umi.identity.publicKey,
  ataProgram: getSplAssociatedTokenProgramId(umi),
})

トークンアカウントを作成する命令ができたので「mintTokenTo()」命令を使用してそのアカウントにトークンをミントすることができます。

const mintTokensIx = mintTokensTo(umi, {
  mint: mintSigner.publickey,
  token: findAssociatedTokenPda(umi, {
    mint: mintSigner.publickey,
    owner: umi.identity.publicKey,
  }),
  amount: BigInt(1000),
})

トランザクションの送信

トランザクションの送信と配置は複数の方法で行えますが、この例では命令をまとめて1つのアトミックなトランザクションにチェーンし、すべてを一度に送信します。ここでいずれかの命令が失敗した場合、トランザクション全体が失敗します。

// chain the instructions together with .add() then send with .sendAndConfirm()

const tx = await createFungibleIx
  .add(createTokenIx)
  .add(mintTokensIx)
  .sendAndConfirm(umi)

// finally we can deserialize the signature that we can check on chain.
// import { base58 } from "@metaplex-foundation/umi/serializers";

console.log(base58.deserialize(tx.signature)[0])

これで、Solana上でトークンを作成する方法を学びました。基本的なプロジェクトアイデアとしては以下のようなものがあります:

  • Solanaトークン クリエーター

  • ミームコイン ジェネレーター

また、JupiterやOrcaなどの分散型取引所にトークンをリストするために、流動性プールの作成をすることもできるようになりました。

フルコード 例

import {
  createFungible,
  mplTokenMetadata,
} from '@metaplex-foundation/mpl-token-metadata'
import {
  createTokenIfMissing,
  findAssociatedTokenPda,
  getSplAssociatedTokenProgramId,
  mintTokensTo,
} from '@metaplex-foundation/mpl-toolbox'
import {
  generateSigner,
  percentAmount,
  createGenericFile,
  signerIdentity,
  sol,
} from '@metaplex-foundation/umi'
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
import { irysUploader } from '@metaplex-foundation/umi-uploader-irys'
import { base58 } from '@metaplex-foundation/umi/serializers'
import fs from 'fs'
import path from 'path'

const createAndMintTokens = async () => {
  const umi = createUmi('https://api.devnet.solana.com')
    .use(mplTokenMetadata())
    .use(irysUploader())

  const signer = generateSigner(umi)

  umi.use(signerIdentity(signer))

  // Airdrop 1 SOL to the identity
  // if you end up with a 429 too many requests error, you may have to use
  // the filesystem wallet method or change rpcs.
  await umi.rpc.airdrop(umi.identity.publicKey, sol(1))

  // use `fs` to read file via a string path.

  const imageFile = fs.readFileSync(path.join(__dirname, '/assets/image.png'))

  // Use `createGenericFile` to transform the file into a `GenericFile` type
  // that Umi can understand. Make sure you set the mimi tag type correctly
  // otherwise Arweave will not know how to display your image.

  const umiImageFile = createGenericFile(imageFile, 'image.png', {
    tags: [{ name: 'Content-Type', value: 'image/png' }],
  })

  // Here we upload the image to Arweave via Irys and we get returned a uri
  // address where the file is located. You can log this out but as the
  // uploader can takes an array of files it also returns an array of uris.
  // To get the uri we want we can call index [0] in the array.

  const imageUri = await umi.uploader.upload([umiImageFile]).catch((err) => {
    throw new Error(err)
  })

  console.log(imageUri[0])

  // Uploading the tokens metadata to Arweave via Irys

  const metadata = {
    name: 'The Kitten Coin',
    symbol: 'KITTEN',
    description: 'The Kitten Coin is a token created on the Solana blockchain',
    image: imageUri, // Either use variable or paste in string of the uri.
  }

  // Call upon Umi's `uploadJson` function to upload our metadata to Arweave via Irys.

  const metadataUri = await umi.uploader.uploadJson(metadata).catch((err) => {
    throw new Error(err)
  })

  // Creating the mintIx

  const mintSigner = generateSigner(umi)

  const createFungibleIx = createFungible(umi, {
    mint: mintSigner,
    name: 'The Kitten Coin',
    uri: metadataUri, // we use the `metadataUri` variable we created earlier that is storing our uri.
    sellerFeeBasisPoints: percentAmount(0),
    decimals: 9, // set the amount of decimals you want your token to have.
  })

  // This instruction will create a new Token Account if required, if one is found then it skips.

  const createTokenIx = createTokenIfMissing(umi, {
    mint: mintSigner.publicKey,
    owner: umi.identity.publicKey,
    ataProgram: getSplAssociatedTokenProgramId(umi),
  })

  // The final instruction (if required) is to mint the tokens to the token account in the previous ix.

  const mintTokensIx = mintTokensTo(umi, {
    mint: mintSigner.publicKey,
    token: findAssociatedTokenPda(umi, {
      mint: mintSigner.publicKey,
      owner: umi.identity.publicKey,
    }),
    amount: BigInt(1000),
  })

  // The last step is to send the ix's off in a transaction to the chain.
  // Ix's here can be omitted and added as needed during the chain.
  // If for example you just want to create the Token without minting
  // any tokens then you can only submit the `createToken` ix.

  // If you want to mint tokens to a different wallet then you can
  // just pull out the `createTokenIx` ix and `mintTokensIx` ix and send
  // them as another tx.

  const tx = await createFungibleIx
    .add(createTokenIx)
    .add(mintTokensIx)
    .sendAndConfirm(umi)

  // finally we can deserialize the signature that we can check on chain.
  console.log(base58.deserialize(tx.signature)[0])
}

createAndMintTokens()


この記事が気に入ったらサポートをしてみませんか?