🐊

ThirdwebのclaimConditionを理解する

に公開

SDK経由でThirdwebコントラクトを操作する

ThirdwebのDashboardは便利なのでそこで操作すればいいんですが、やりにくいケースもあります。

  • KMSウォレットなどを利用している場合
  • 大量操作・自動化などをしたい場合

ということでSDK経由でコントラクトを操作していきましょう。

今回はサーバーサイドを想定して、最低限のセットアップと、ハマりがちなclaimConditionについて書きます。

accountの生成

client-sideの場合はwalletを繋げばよいですが、サーバーやスクリプトでwrite transactionを発行するには、accountを使うことになります。

https://portal.thirdweb.com/typescript/v5/adapters

上記のページに、ethersやviemと連携したaccountの作成方法が記載されていますので、スニペットで要点のみ示します。

import { createThirdwebClient } from 'thirdweb';
import { ethers6Adapter } from 'thirdweb/adapters/ethers6';

const client = createThirdwebClient({ secretKey: process.env.THIRDWEB_SECRET_KEY! });
const account = await ethers6Adapter.signer.fromEthers({ signer });

setClaimCondition

さて、accountの準備ができると後はだいたいドキュメントをかいつまんで読めばDashboardからの類推でなんとなく使えるんですが、claimConditionまわりは前提知識がないと戸惑う人も少なくないのかと思います。

ちなみに、概観は公式のDesign Docsに記載されているのですが、ちょっと見つけにくいかもしれません。

https://portal.thirdweb.com/contracts/design-docs/drop#claim-conditions

Dashboardでは抽象化されているが、SDKでは明示的に設定が必要なパターン例

たとえば、ThirdwebのDashboard上では、Only Ownerという設定ができます。

image

しかし、これと同等のことをSDKでやるには、Only Ownerのような抽象は用意されていないためoverrideListの明示的な設定が必要です。

import { getContract, sendAndConfirmTransaction } from 'thirdweb';
import { setClaimConditions } from 'thirdweb/extensions/erc1155';
import { ethereum } from 'thirdweb/chains';

const contract = getContract({
  client,
  chain: ethereum,
  address: contractAddress,
});

const transaction = setClaimConditions({
  contract,
  tokenId,
  phases: [
    {
      maxClaimablePerWallet: 0n,
      overrideList: [{ address: account.address, maxClaimable: 'unlimited' }],
    },
  ],
});

await sendAndConfirmTransaction({ transaction, account });

setClaimConditionsは内部的に何をしているのか

claim conditionはMerkle Treeを用いて管理されています。

SDKでsetClaimConditionsを実行すると、以下のような流れでMerkle Treeが利用されます。

  1. サーバー上でMerkle Treeを生成
  2. Merkle TreeのデータをIPFSにアップロード
  3. 生成したMerkle Treeのハッシュをコントラクトに記録

merkleRootハッシュのコントラクトへの保存

コントラクトには以下のようにmerkleRootにハッシュが保管されます。

DropERC1155.sol
mapping(uint256 => ClaimConditionList) public claimCondition;
IClaimConditionMultiPhase.sol
struct ClaimConditionList {
    uint256 currentStartId;
    uint256 count;
    mapping(uint256 => ClaimCondition) conditions;
    mapping(uint256 => mapping(address => uint256)) supplyClaimedByWallet;
}
IClaimCondition.sol
struct ClaimCondition {
    uint256 startTimestamp;
    uint256 maxClaimableSupply;
    uint256 supplyClaimed;
    uint256 quantityLimitPerWallet;
    bytes32 merkleRoot;
    uint256 pricePerToken;
    address currency;
    string metadata;
}

このように、ベースとなるclaimConditionはコントラクトに直接記録されますが、overrideListの項目はMerkle Treeを用いて管理されています。

IPFS URIのコントラクトへの保存

さらにコントラクトレベルのメタデータとしてcontractURIがあり、Merkle Treeデータを格納しているIPFS URIが記録されています。

IContractMetadata.sol
function contractURI() external view returns (string memory);
ContractMetadata.sol
string public override contractURI;
// ReturnType<typeof getContractMetadata>
{
  name: string;
  symbol: string;
  description: string;
  merkle: { [merkleRoot: `0x${string}`]: `ipfs://${string}` };
}

IPFS URIの中身

IPFS URIの中身は以下のようになっています。

{
  merkleRoot: `0x${string}`,
  baseUri: `ipfs://${string}`,
  originalEntriesUri: `ipfs://${string}`,
  shardNybbles: number,
  tokenDecimals: number,
  isShardedMerkleTree: boolean,
}

ここで、isShardedMerkleTreetrueの場合はシャーディングされたMerkle Treeのデータを格納しています。
また、shardNybblesはシャーディングのためのパラメータで、シャーディングのための桁数を示します。

たとえば、shardNybbles2であれば、アドレスの先頭2桁がシャーディングのキーになります。

  • claimerのアドレスが0x12...のとき${baseUri}/12が該当データのURI
  • claimerのアドレスが0x34...のとき${baseUri}/34が該当データのURI

さらに、originalEntriesUriはoverrideListの各エントリがそのまま格納されたデータのURIです。

claimされたときのclaimConditionのチェック

SDKでclaimToを実行すると、以下のようなチェックが行われます。

  1. getContractMetadataを実行し、IPFS URIを取得
  2. IPFS URIからclaimerに対応したMerkle Treeのデータを取得
  3. Merkle TreeのデータからclaimToの引数_allowListProofを生成

このとき、_allowListProofはIPFS由来のデータのため、setClaimConditions直後にclaimToを実行すると動作が不安定な場合がありました。

SDKでIPFSデータを取得する際に、反映が遅れたりしているのではないかと思います。

まとめ

  • SDKのsetClaimConditionsでは、Only Ownerのような設定の仕方ができない
  • 同じ内容は、overrideListを明示的に設定することで実現できる
  • claimConditionはMerkle Treeを用いて管理されており、データはIPFSに保存されている
  • IPFS URLはcontractURIに記録されている
  • _allowListProofはIPFS由来のデータのため、setClaimConditions直後にclaimToを実行すると動作が不安定な場合がある
キリフダ株式会社

Discussion