Solana x Anchorのブロックチェーン開発 初心者ガイド

本記事は2021年12月に書いたのですが、1年経過してずいぶん状況も変わりましたので、内容を最新化しました。

2021年12月頃は、Solana関連のドキュメントやサンプルプログラムが少なくて、非常に苦労しましたが、1年が経過してから、ドキュメントが相当増えて理解しやすくなりました。

Solanaは最初の取っ掛かりは難しいのですが、全体的にシンプルな仕組みになっているので、ソースも読みやすく、慣れると理解しやすいです。

ミニマルなサンプルプログラム by 256hax

勉強しやすいように、条件文を一切省き、モジュール分割もせず可能な限り1ファイルに収めたミニマルプログラムを作成しました。
Solana関連でアプリケーションを作るときの、サンプルプログラム集としては、世界一充実していると自負しています。これを応用すれば、DEXやNFTマーケットプレイスみたいな複雑なのは除いて、一般的なweb3サービスはだいたい作れると思います。

https://github.com/256hax/solana-anchor-react-minimal-example

Solanaチェーンや他チェーンの開発者(中のひと)が見てくれているようなので、価値はあるのかなと思います。

Solanaの学習ステップ

以降は、Solanaの学習ステップになります。

1. Soloanaの勉強アプローチ

「世界観を知る → 仕組みを知る → コードを読む/検証する → 最初に戻る」というのを何度も繰り返して理解を深めていった。普通は一方向で戻ることはないと思うが、Solanaの場合は一度で理解できず、何度もループした。

2. 世界観を知る

以下の記事が把握しやすく、重要でヒントになる内容がたくさんあった。

3. 仕組みを知る

貴重なポンチ絵があり、コードよりも絵を重視して理解した。

自分なりに整理したポンチ絵は以下のとおり。仕事で使いやすいようにPowerPoint版をアップ(MITライセンスで自由にお使いください)。
Solana Blockchain Outline Figure for Product Manager(Draft Version)

【イメージ例】

4. コードを読む/検証する

Rustを多少理解していないと読むのが若干難しかった。「コードを読む → 不明点見つかる → Rustを勉強する → 検証する → 最初に戻る」をループした。

また、Rust(スマコン)、Anchor(スマコンフレームワーク)、Metaplex(NFTフレームワーク) 誰がどこまでの仕様を担っているのか、全然わからなかったため、最終的にそれぞれ理解を深めていく必要があった。

5. API/コミュニティを読む

勉強しても検証しても、どうしても解決できない場合は、仕様やコードを直接読むしかなかった。
各コミュニティへのリンク集は以下の Support にまとめた。

https://github.com/256hax/solana-anchor-react-minimal-example#reference

具体的な開発方法

資産のみをweb3に置き換える

Anchor使ったスマコン開発から入ってしまうと結構ハマるため、「Web2サービスの中で、資産部分のみweb3に置き換える」といったことがオススメ。
たとえば、「Web2でフリマアプリを作って、金銭のやり取りをトークンに置き換える(資産部分のみweb3に置き換える)」といったイメージ。

web3.jsのみで完結させる

SolanaとMetaplexのweb3.jsが充実しているため、SOL/トークン、NFTを扱うサービスはだいたいこれだけで事足りる。Rustは使う必要ない。

スマコン開発する場合はAnchor完結でモックを作る

スマコンが必要になる例としては、オンチェーン上にデータを保管して、それを扱いたい場合になる。
(例)

  • PDA(個別データ格納用アカウント)にユーザーデータとしてゲームのLVや強さのパラメータを管理したい場合
  • 業務開始→完了→報酬支払 をトレーサビリティ(追跡)できるようにしたい場合

まずは、Anchorでprogramsとtestsを使ってモック開発するのがオススメ。
たとえば普通に組んでいくと、だいたい以下のような感じでサーバーが分かれる。

  • Web2フロントエンド(React)
  • Web2バックエンド(Rails) - ユーザーデータ保管、ビジネスロジックなど
  • Web2キュー処理(Amazon SQS)
  • web3スマコン(Anchor)
  • Web3バックエンド(Solana /Metaplex web3.js) - NFT発行など

これを最初から切り分けして開発すると要件定義含めて、時間とコストがかかるため、まずはAnchorだけでモックを作るのがよい。

ハマりポイント

WHY BLOCKCHAIN?

これが最大のハマりポイントになる。
浅く考えていると、「それ、Web2でよくない?」になってしまうサービスが多い。
最初は「与件としてブロックチェーンを使うこと」と自分で縛りを入れるのがおすすめ。
開発に慣れたあとに、じっくりWHY BLOCKCHAIN?の問に答えられるようなものを作ればよいと思う。

仕組みのハマりポイント

  • Solanaはなにをするにも、すべてにおいてアドレスを発行して管理していく仕組みになっていて、理解はできるが、設計や実装になると難しい。
  • RaydiumやMetaPlexなど、GitHubからクローンしたらすぐ動くものも多数あるが、中身を理解するにはソースコードをちゃんと読む必要があり、自分にはハードルが高かった。

UI/UXの低下

試しにフルオンチェーンでモックを作ってみたところ、なにかする度にPhantomでApproveウィンドウが出てきてしまい、UI/UXが非常に下がることがわかった。
また、オンチェーン上のデータはリアルタイムに頻繁に取得する想定になっていないため、情報取得するときに時間がかかってしまう。
CDNを使ったり、基本的にオフチェーン上でデータを保管して、エビデンス用にオンチェーンを使う、といったことで回避可能だが、コストと時間がかかりすぎてしまう。

開発のハマりポイント

  • Solanaをバニラ状態でカスタマイズして組むのが難しく(Instructionsのシリアライズ/デシリアライズ対応など)、Anchorを使わないと実装できなかった。AnchorはSolana実装の難しい部分を担ってくれているため、Anchorなしの開発は自分には厳しかった。
  • フロントエンドのSolana Web3だけでも送金などの基本機能は利用可能。そのため、データやロジックのカスタマイズをしないのであれば(つまりRust側をいじられないのであれば)、無理してAnchorを利用しなくてもよい。
  • 「% anchor test」で成功したコードをReactにそのまま組み込めずにハマった。ロジック周りはAnchorのtestコードをそのまま実装できるが、それ以外のウォレット接続機能とかはReact側(Phantom)で実装しないといけない。
  • とにかく実際に検証するのが重要。localnet(solana-test-validator)でも、Solana Explorerが利用できるため、デバッグは ①Rustのdbg! ②JSのconsole.log ③Solana Explorer の3点セットで確認がおすすめ。localnetでSolana Explorerを使う方法は以下
    Solana Explorerでlocalnetのトランザクションを閲覧する方法
  • バージョンアップが激しいので、しょっちゅうバージョンアップして追いつくようにする必要がある。SolanaもAnchorも全力疾走感が半端なく、開発力の高さが伺える。
  • Solana公式も含めて、GitHubにアップされているが動かない、というものが多数あった。その場合は、「最終更新日(活発に更新されているか)」「IssueとPull Requestが溜まっているか(バグがどの程度あるか)」が目安になった。最終更新日が4ヶ月以上前で、Issue、PRが10件以上、というものはだいたい動かなかった。
  • 開発環境がMacだとスムーズにいって、Windowsだとハマった。DockerでLinux用意するか、素直にMacを用意するのがよいかも。

コード

  • Anchorでテストコードを実行すると、provider(自分のウォレット)は署名を省略できてしまう。サンプルプログラムでも省略されているため、誰が署名したのかわからなくなることが多い。

たとえば下記のコードは「counter」だけが署名したように見える(Anchor上だとちゃんと動作する)。

    const signatuer = await program.methods
      .initialize(
        new anchor.BN(startCount)
      )
      .accounts({
        counter: counter.publicKey,
        authority: provider.wallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .signers(counter])
      .rpc()

しかし、実際には以下のようにProviderも署名している。曖昧さを防ぐため、なるべく .signers を使うのがよいのではと考えている(この考えが正しいかは不明)。

    const signatuer = await program.methods
      .initialize(
        new anchor.BN(startCount)
      )
      .accounts({
        counter: counter.publicKey,
        authority: provider.wallet.publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .signers(counter, provider.wallet.payer]) // Providerを追加
      .rpc()

もし、誰が署名したか調べたい場合は、Solana ExplorerでLocalnetのトランザクションを追っていくと、「Signer」のラベルが表示されるため把握できる。