見出し画像

Solanaを使って合同会社型DAOを作れるのか?MetaplexとRealmsを使って試してみた。



はじめに

プロイノベーション法律事務所 弁護士/Pro-Innovation Consulting Pte. Ltd.ダイレクターの谷です。普段、ブロックチェーン分野でスタートアップの支援等を行っております。Solana関係の皆様には大変お世話になっており、今年はSuperteamのメンバーにも加えていただきました。

法律が絡む分野ということでDAO分野で貢献したいという気持ちは持ったものの、DAOのって難しいですね。正直、私の苦手分野かもと思うこともあります。最近方針としてまとまってきたのは、私はガバナンスモデルとか社会学系の議論はなんとかついて行くだけにして、テクニカルな部分(小さな工夫の積み重ね)で貢献できるようにしようということです。そこで、DAOの分野でとりあえず手を動かしてみて、何か掴もうと思いました。

2024年、日本では、金商法の内閣府令改正があり、合同会社型DAOというものがスキームとして提案されていることはご存知の方も多いかと思います。このスキームで使うトークンとして提案されているのは、日本の規制の関係で、基本的にNFTとなります。

他方、SolanaでDAOと言えば、数年前からRealmsが有名です。なお、今年、Solana Labsからチームがスピンアウトして、Realms Ltdというイギリスの会社を設立して、Realmsの周辺のビジネスを引き継いだみたいです(ニュース記事)。記事によれば事業として自立が求められているようでもありますので、何か新しい動きがあるかもしれないと思っています。

今回、
1. 公表されている合同会社型DAOのモデル定款やサンプルの諸規則において、オンチェーン上どのような機能が必要とされているかを見て、
2. それが(オンチェーンプログラムを自作することなく)MetaplexやRealmsを使って実現できるかを試してみました。

その結果、基本的に既存の技術を使って対応可能という感触を得ました。下記では、その過程を共有させていただきます。

1. 合同会社型DAOにおいて、オンチェーンプログラムの利用が想定される場面と求められる機能の整理

日本DAO協会が、合同会社型DAOの定款雛形とガイドラインを公表しています(https://jpdao.org/template/)。実際に合同会社型DAOを設立する場合、これに一言一句従う必要はなく、会社法その他が許す限りカスタマイズは可能ですが、一定の参考とされるべきものです。

オンチェーンが関係するのは、トークンについて定める定款第6条です。ここから、以下のことが言えます。

日本DAO協会「合同会社型DAO定款雛形」から引用

①「社員権トークン」というNFTの発行が必要である。さらに、「ガバナンストークン」という別の種類のNFTを発行することもある(後者は任意)。

「社員権トークン」と「ガバナンストークン」の違いは、「社員権トークン」は出資元本までの配当を受ける権利を伴う合同会社の社員としての地位と紐づいていて、「ガバナンストークン」はそうではないということです。

2種類のNFTを発行する場合は、日本在住者には「社員権トークン」を流通させ、外国居住者には「ガバナンストークン」を流通させることが想定されています。なぜ外国居住者に対して「社員権トークン」を販売しない選択肢が用意されているかというと、日本では内閣府令の改正によって、出資元本までの分配を受ける権利があるにとどまる社員権は、「有価証券」ではありつつも比較的緩やかな規制が適用されると整理されましたが、日本以外の国でそのような特殊な社員権に対してどのような規制が適用されるか確認が困難だからです。

(※ この記事の目的は、合同会社型DAOの制度や仕組み自体の当否を論じることにないため、詳細は述べませんが、このような国内と国外でトークンを分けるようなスキームについての印象は、下記の3で少し述べています。)。

② 上記の第6条第3項に記載されているように、管理者による、ウォレットを紛失した場合の紛失ウォレット中の既存トークンの無効化や、新規NFTの発行が必要。

まとめると、オンチェーン上で、上記の①と②の機能を果たす必要があります。

(※ 他にも、議題に応じて複数のパターンの定足数や可決に必要な最低賛成投票数を設定することも挙げられますが、どのような場合分けをするか、どの程度オンチェーン上での投票にかけるか等、個別のケースでの対応次第でもありますので割愛します。)

2. MetaplexとRealmsを使って上記の機能を実現できるのか?

① 社員権NFTおよびガバナンスNFTの発行と、その双方のホルダーによる投票

まずは、社員権NFTを発行しなければなりません。そして、社員権NFTをRealms上の投票資格として用いるためには、各社員権NFTが、ある一つのCollection NFTに帰属し、また、そのことがCollection NFTのAuthorityによってVerifyされなければなりません。

Collection NFTやVerifynの概念については、下記のドキュメントが参考になります。

Metaplex:  

Realms:

ここから、実際にNFTを発行して、Verifyしてみます。今回は、以下の4種類のNFTを発行します。
(i) 社員権Collection (1枚)
(ii) 社員権NFT (3枚) - 3人の社員がいると仮定。
(iii) ガバナンスCollection (1枚)
(iv) ガバナンスNFT(2枚) - 社員にはならないガバナンス参加者が2人いると仮定。

そして、(ii)の通常のNFTは(i)のCollection NFTに属すること、および(iv)の通常のNFTは(iii)のCollection NFTに属することをVerifyします。

以下のようなコードで実際にDevnet上で発行、Verifyしてみました。

import * as fs from "fs"
import * as path from "path"
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"
import {
  createSignerFromKeypair,
  signerIdentity,
  generateSigner,
  percentAmount,
} from "@metaplex-foundation/umi"
import {
  createNft,
  mplTokenMetadata,
  fetchDigitalAsset,
  verifyCollectionV1,
} from "@metaplex-foundation/mpl-token-metadata"
import dotenv from "dotenv"
import { sol } from "@metaplex-foundation/umi"
import { findMetadataPda } from "@metaplex-foundation/mpl-token-metadata"
import winston from "winston"

// setup logger
const logger = winston.createLogger({
  transports: [
    new winston.transports.File({ filename: "app.log" }),
    new winston.transports.Console(),
  ],
})
logger.info("Start")

// setup umi
dotenv.config()
const url: string = process.env.NODE_URL || ""
const umi = createUmi(url, "confirmed").use(mplTokenMetadata())

// setup signer
const keypair = umi.eddsa.createKeypairFromSecretKey(
  new Uint8Array(
    JSON.parse(
      fs.readFileSync(path.join(__dirname, "{keypair_path}"), "utf8"),
    ),
  ),
)
const signer = createSignerFromKeypair(umi, keypair)
umi.use(signerIdentity(signer))

// setup mint addresses
const membershipCollectionMint = generateSigner(umi)
logger.info(`membershipCollectionMint: ${membershipCollectionMint.publicKey}`)
const membershipNftMint_1 = generateSigner(umi)
logger.info(`membershipNftMint_1: ${membershipNftMint_1.publicKey}`)
const membershipNftMint_2 = generateSigner(umi)
logger.info(`membershipNftMint_2: ${membershipNftMint_2.publicKey}`)
const membershipNftMint_3 = generateSigner(umi)
logger.info(`membershipNftMint_3: ${membershipNftMint_3.publicKey}`)
const governanceCollectionMint = generateSigner(umi)
logger.info(`governanceCollectionMint: ${governanceCollectionMint.publicKey}`)
const governanceNftMint_1 = generateSigner(umi)
logger.info(`governanceNftMint_1: ${governanceNftMint_1.publicKey}`)
const governanceNftMint_2 = generateSigner(umi)
logger.info(`governanceNftMint_2: ${governanceNftMint_2.publicKey}`)
;(async () => {
  try {
    // airdrop SOL to the signer
    await umi.rpc.airdrop(signer.publicKey, sol(1.5))
    logger.info("Airdropped 1.5 SOL to Signer")

    // create membership collection
    await createNft(umi, {
      mint: membershipCollectionMint,
      name: "Membership Collection",
      uri: "{location of metadata}/membership_collection_metadata.json",
      sellerFeeBasisPoints: percentAmount(0),
      isCollection: false,
      collectionDetails: {
        __kind: "V1",
        size: 3,
      },
    }).sendAndConfirm(umi)

    // fetch membership collection
    const membershipCollection = await fetchDigitalAsset(
      umi,
      membershipCollectionMint.publicKey,
    )
    logger.info("membershipCollection", membershipCollection)

    // create governance collection
    await createNft(umi, {
      mint: governanceCollectionMint,
      name: "Governance Collection",
      uri: "{location of metadata}/governance_collection_metadata.json",
      sellerFeeBasisPoints: percentAmount(0),
      isCollection: false,
      collectionDetails: {
        __kind: "V1",
        size: 2,
      },
    }).sendAndConfirm(umi)

    // fetch governance collection
    const governanceCollection = await fetchDigitalAsset(
      umi,
      governanceCollectionMint.publicKey,
    )
    logger.info("governanceCollection", governanceCollection)

    // create membership nfts and verify
    for (const [i, mint] of [
      membershipNftMint_1,
      membershipNftMint_2,
      membershipNftMint_3,
    ].entries()) {
      await createNft(umi, {
        mint: mint,
        name: `Membership NFT No.${i + 1}`,
        uri: `{location of metadata}/membership_metadata.json`,
        sellerFeeBasisPoints: percentAmount(5.5),
        isCollection: true,
        collection: {
          key: membershipCollectionMint.publicKey,
          verified: false,
        },
      }).sendAndConfirm(umi)

      // fetch membership nft
      const membershipNftAsset = await fetchDigitalAsset(umi, mint.publicKey)
      logger.info("membershipNftAsset", membershipNftAsset)

      // find token account of membership nft
      const membershipNftTokenAccount = findMetadataPda(umi, {
        mint: mint.publicKey,
      })
      logger.info("membershipNftTokenAccount", membershipNftTokenAccount)

      // verify that membership nft token account belongs to membership collection by update authority of membership collection
      await verifyCollectionV1(umi, {
        metadata: membershipNftTokenAccount,
        collectionMint: membershipCollectionMint.publicKey,
        authority: signer,
      }).sendAndConfirm(umi)
    }

    // create governance nfts and verify
    for (const [i, mint] of [
      governanceNftMint_1,
      governanceNftMint_2,
    ].entries()) {
      await createNft(umi, {
        mint: mint,
        name: `Governance NFT No.${i + 1}`,
        uri: `{location of metadata}/governance_metadata.json`,
        sellerFeeBasisPoints: percentAmount(5.5),
        isCollection: true,
        collection: {
          key: governanceCollectionMint.publicKey,
          verified: false,
        },
      }).sendAndConfirm(umi)

      // fetch governance nft
      const governanceNftAsset = await fetchDigitalAsset(umi, mint.publicKey)
      logger.info("governanceNftAsset", governanceNftAsset)

      // find token account of governance nft
      const governanceNftTokenAccount = findMetadataPda(umi, {
        mint: mint.publicKey,
      })
      logger.info("governanceNftTokenAccount", governanceNftTokenAccount)

      // verify that governance nft token account belongs to governance collection by update authority of governance collection
      await verifyCollectionV1(umi, {
        metadata: governanceNftTokenAccount,
        collectionMint: governanceCollectionMint.publicKey,
        authority: signer,
      }).sendAndConfirm(umi)
    }
    logger.info("End")
  } catch (error) {
    logger.error(error)
  }
})()

次に、Realms のUIから、上記で作ったNFTを投票権者とするDAOコントラクトを作成してみます。

RealmsのWebサイトからCreate DAOを選択すると、
Multi-Signature Wallet、 NFT Community DAO、Community Token DAOの3種類が挙げられています。先に述べました通り、日本の合同会社型DAOでは、NFTの発行が想定されているため、NFT Community DAOが候補となります。

この点、合同会社型DAOが特殊なのが、社員権NFTとガバナンスNFTの2つの種類のNFT保有者が投票権者となることです。

もっとも、RealmsのDocには、複数のCollectionに属するNFTが投票権を持つ設計が可能であると記載されています。

Realms Doc - Configure NFT Voting Plugin and Enable NFT Voting Plugin

どうやって、これができるのかという問題意識を持ちながら、Devnet上で試してみました。

下記のように、最初にDAOを作る時は、一つのCollection NFTしか選択できませんでした。

それでもUIに従って進めてみると、とりあえず一つのCollectionに属するNFTホルダーが投票権を持つDAOを作成することができました。


その後、DAOの管理画面を見ていると、もう一つのCollectionに属するNFTを投票権者として追加するというProposalが出せそうでした。

このトランザクションを出そうとすると、Webサイト上できるシュミレーションはOKだったのですが、ウォレットでは失敗の警告が出て、結局トランザクションは成功しませんでした。このエラーの原因は分からなかったです。

② ウォレット紛失等の場合の既存NFTの無効化と新規NFTの発行

上記1の②で、管理者による、ウォレットを紛失した場合の紛失ウォレット中の既存トークンの無効化や、新規NFTの発行が必要、と述べました。

これを達成するために、MetaplexのVerifyの機能は便利に使えそうです。すなわち、NFTをBurnしなくても、Verifyの取り消しであるUnverifyをすることでNFTをDAOの文脈で無効化することができ、また、新しく作成したNFTをVerifyすることで新規発行を行うことができます。これらを行う権限を持つCollection NFTのAuthorityは強力ですので、合同会社型DAOにおいては、複数の業務執行社員のマルチシグで管理することになりそうです。

③ 小括

基本的にはMetaplexとRealmsの機能を組み合わせれば合同会社型DAOに対応できそうではあります。

ただ、UIは日本人やクリプトには詳しくない方にとって決して使いやすいものではないので、特に、MetaplexやRealmsから支援が受けられるのであれば、合同会社型DAO向けのUIを作るというアイディアはあるのかもしれません。また、RealmsのコントラクトにはPluginを足せるようになっているので、必要になればコントラクトレベルでの調整も可能かもしれません。

3. 合同会社型DAOについての感想とDAOについて最近考えていること

いろんな私の知らない需要もあるのだろうとは思いますが、私は、合同会社型DAOについて以下のような印象を持っています。

① 日本の地域社会コミュニティーの活性化に有効に使える場合がある。
② 他方、海外へ社員権トークンを販売することの適法性が法律面でクリアにならないことから、国際的に広げていこうとするプロジェクトにとっては使いずらい面もある。
③ 社員に、出資額元本まで配当できることが目玉であるが、それっていつも重要なのだろうか。いずれにしても、出資額の全額が返ってこない可能性は高いと思われ、ダウンサイドリスクが気になるのであれば合同会社型DAOに出資しないと思う。
④ 日本に拠点を置く必要性がない国際的なプロジェクトにとっては、日本の税制面がハードル(これは税率が高い低いの問題以前に、日本という国の性質からして当然)。

以下、少しこの記事の本論からは脱線です。

特に上記②と③の関係で、いっそ配当が存在しない非営利法人にしてしまえば、そのメンバーシップが、どこの国でも証券に該当することはなさそうで(ただし、米国は、現段階では不透明)、デメリットもほとんどない場合が多いと思います。その変化型として、非営利法人の定款に、ダイレクターは他の法人が発行したトークンによるガバナンスに従うと規定することも可能です(現在普及しているFoundationのモデルはそのようにしています。メンバー交代の手間や費用を省略するという実務的に大きなメリットもあります。)

なお、「非営利」という言葉は、配当を受け取る株主が存在しないことを意味するだけであり、「公益目的」とは違うので広い範囲の設立目的が認められ、また、関係者に利益をもたらすことは全く否定されないことは留意が必要です。Solana Foundationも非営利法人に分類されます。

税制や法制の観点から、大型のLayer 1はスイス、DeFi系はケイマンかパナマのFoundationを使うことが標準的なモデルとなっていますが、ビジネス自体へのファイナンス規制が問題にならず、また、政府や産業界から歓迎される可能性があるAI Agent、DePin、RWA系などで、加えて、特にアジアが重要なマーケットである場合のトークンガバナンスを実現するための器としては、シンガポールの非営利法人(Company Limited by Guarantee)を使ってもよいのではと最近思うようになって調べ始めています。実業的な部分は各国の普通の株式会社が実施して、非営利法人はコミュニティ全体の利益のために動くという役割分担が前提です。実務的には費用面も大きい考慮要素なのですが、シンガポールは、スイスやケイマンと比べれば設立維持コストを抑えられると思います。また、実例は多くはないのですが、老舗のLitecoinのFoundationはこのシンガポールの仕組みを使っています。

日本の一般社団法人/NPOを使ったDAOについても、公益認定を受けるなど対策を取れる場合や会社としての利益を出す予定がない場合は検討の余地があるかもしれないとは思っています。

この辺り、興味がある方がいらっしゃればお話ししましょう。Xなどでのご連絡大歓迎です。

4. 最後に

2024年は、Xや実際にお会いした人を含めて、Solana界隈、ブロックチェーン界隈にどんどん新しい才能が入ってきていると感じました。特に今年は日本でそのように感じました。

来年は、引き続きブロックチェーン分野の起業家のストラクチャリング等の支援、暗号資産の確定申告支援に加えて、もっとほかの国に出ていって、いろいろな繋ぎ役になりたいと考えています。アジアだけではなく、欧米から日本市場への進出や拠点設立の支援に加え、各国の専門家への発信も増やしていきたいです。

Solana、ブロックチェーン関係者の皆様、本年はお世話になりました。良いお年をお迎えください。今年もよろしくお願いいたします!









いいなと思ったら応援しよう!