mcpField
N
nft

nft

github.com/kali-decoder開発17121 回インストール2 週間前に更新
リポジトリ

nft について

Midnight Network の開発者向けに、OpenZeppelin の Compact 非代替性トークンコントラクトとネイティブトークン操作を扱うスキルです。公開・非公開の所有権パターンに加え、ミント、転送、承認、メタデータ管理を説明します。

主な機能

  1. 01ERC721 相当の Compact コントラクト
  2. 02ミント、転送、承認の説明
  3. 03公開・非公開の所有権パターン
  4. 04トークン URI とメタデータコミットメント
  5. 05コンパイル、デプロイ、テストの説明

活用シーン

  1. 01公開コレクティブル NFT の開発
  2. 02所有権を秘匿する NFT の構築
  3. 03トークンのメタデータ URI 設定
  4. 04Compact NFT コントラクトのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kali-decoder/midnight-skills --skill nft

インストール後、Claude Code で/nftを入力して読み込まれたか確認します。

詳細

対象範囲 このスキルでは、Midnight Network での NFT 開発を扱います。OpenZeppelin の NonFungibleToken コントラクト(Compact での ERC721 風実装)と、ネイティブな Midnight トークン操作(シールド済み/シールドなし)が含まれます。NFT のミント、転送、承認、メタデータ URI、およびプライバシーパターンを扱います。

以下のとおり、ファイルを正確に配置してください。

  • contracts/NonFungibleToken.compact → OpenZeppelin のベースコントラクト
  • contracts/MyNFT.compact → カスタム NFT コントラクト
  • test/ → コントラクトの単体テスト

1) インストール

前提条件
  • Node.js v22.15+
  • Compact 開発ツール:curl --proto '=https' --tlsv1.2 -sSf https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
  • PATH に追加:source $HOME/.local/bin/env
Compact 用 OpenZeppelin Contracts を追加する
mkdir my-nft-project && cd my-nft-project
git init
git submodule add https://github.com/OpenZeppelin/contracts-compact.git
cd contracts-compact && SKIP_ZK=true yarn && cd ..

2) OpenZeppelin NonFungibleToken コントラクト

OpenZeppelin の NonFungibleToken モジュールは、Compact で ERC721 風の実装を提供します。

主な機能
機能状況備考
トークン ID の型Uint<128>Uint256 はサポートされていません(回路の制限)
転送✅ECDSA 公開鍵またはコントラクトアドレス宛て
承認✅トークン単位およびオペレーターの承認
メタデータ URI✅トークンごとの URI 保存
一時停止可能✅Pausable モジュールを通じて利用可能
所有可能✅Ownable モジュールを通じて利用可能
コントラクト間❌まだサポートされていません(_unsafeTransfer を使用)
インポートと使用
pragma language_version >= 0.21.0;

import CompactStandardLibrary;
import "./contracts-compact/node_modules/@openzeppelin-compact/contracts/src/access/Ownable"
  prefix Ownable_;
import "./contracts-compact/node_modules/@openzeppelin-compact/contracts/src/security/Pausable"
  prefix Pausable_;
import "./contracts-compact/node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken"
  prefix NonFungibleToken_;

constructor(
  _name: Opaque<"string">,
  _symbol: Opaque<"string">,
  _initOwner: Either<ZswapCoinPublicKey, ContractAddress>,
) {
  Ownable_initialize(_initOwner);
  NonFungibleToken_initialize(_name, _symbol);
}

export circuit mint(
  to: Either<ZswapCoinPublicKey, ContractAddress>,
  tokenId: Uint<128>,
  uri: Opaque<"string">,
): [] {
  Ownable_assertOnlyOwner();
  NonFungibleToken__mint(to, tokenId);
  NonFungibleToken__setTokenURI(tokenId, uri);
}

export circuit transferFrom(
  from: Either<ZswapCoinPublicKey, ContractAddress>,
  to: Either<ZswapCoinPublicKey, ContractAddress>,
  tokenId: Uint<128>,
): [] {
  Pausable_assertNotPaused();
  NonFungibleToken_transferFrom(from, to, tokenId);
}

export circuit approve(
  to: Either<ZswapCoinPublicKey, ContractAddress>,
  tokenId: Uint<128>,
): [] {
  NonFungibleToken_approve(to, tokenId);
}

export circuit setApprovalForAll(
  operator: Either<ZswapCoinPublicKey, ContractAddress>,
  approved: Boolean,
): [] {
  Ownable_assertOnlyOwner();
  NonFungibleToken_setApprovalForAll(operator, approved);
}

export circuit pause(): [] {
  Ownable_assertOnlyOwner();
  Pausable__pause();
}

export circuit unpause(): [] {
  Ownable_assertOnlyOwner();
  Pausable__unpause();
}

3) ネイティブな Midnight NFT パターン

Midnight の標準ライブラリは、シールド済み/シールドなしの操作に対応したネイティブトークン関数を提供します。

シールドなし NFT(公開)
import CompactStandardLibrary;

export ledger nextTokenId: Uint<128>;
export ledger owners: Map<Uint<128>, Either<ZswapCoinPublicKey, ContractAddress>>;
export ledger tokenURIs: Map<Uint<128>, Opaque<"string">>;

export circuit mint(
  to: Either<ZswapCoinPublicKey, ContractAddress>,
  uri: Opaque<"string">,
): Uint<128> {
  const tokenId = nextTokenId;
  nextTokenId += 1;
  owners.insert(disclose(tokenId), disclose(to));
  tokenURIs.insert(disclose(tokenId), disclose(uri));
  return tokenId;
}

export circuit transfer(
  from: Either<ZswapCoinPublicKey, ContractAddress>,
  to: Either<ZswapCoinPublicKey, ContractAddress>,
  tokenId: Uint<128>,
): [] {
  const owner = owners.lookup(disclose(tokenId));
  assert(owner == disclose(from), "Not owner");
  owners.insert(disclose(tokenId), disclose(to));
}

export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> {
  return tokenURIs.lookup(disclose(tokenId));
}
シールド済み NFT(非公開の所有権)

Compact 標準ライブラリのシールド済みトークン関数を使用します。

import CompactStandardLibrary;

// Mint a shielded NFT
export circuit mintShielded(
  to: ZswapCoinPublicKey,
  metadataHash: Field,
): Uint<128> {
  const tokenId = nextTokenId;
  nextTokenId += 1;

  // Store commitment to ownership + metadata
  const commitment = persistentCommit<Uint<128>>(tokenId, freshNonce());
  shieldedCommitments.insert(disclose(to), commitment);

  return tokenId;
}

// Transfer shielded NFT (prove ownership without revealing tokenId publicly)
export circuit transferShielded(
  tokenId: Uint<128>,
  newOwner: ZswapCoinPublicKey,
): [] {
  // Use ZK proof to demonstrate ownership
  // Owner's shielded wallet signs the transfer
  const oldCommitment = shieldedCommitments.lookup(disclose(callerPublicKey()));
  assert(oldCommitment != default<Field>(), "Not owner");

  shieldedCommitments.remove(disclose(callerPublicKey()));
  shieldedCommitments.insert(disclose(newOwner), oldCommitment);
}

4) コントラクトをコンパイルする

cd contracts
compact compile MyNFT.compact artifacts/MyNFT

予想される出力:

Compiling 5 circuits:
  circuit "mint" (k=11, rows=1180)
  circuit "transferFrom" (k=11, rows=1966)
  circuit "approve" (k=10, rows=966)
  circuit "pause" (k=10, rows=125)
  circuit "unpause" (k=10, rows=121)
Overall progress [====================] 5/5

5) デプロイと操作(TypeScript)

import { CompiledContract } from '@midnight-ntwrk/compact-js';
import { deployContract } from '@midnight-ntwrk/midnight-js-contracts';
import { Contract } from './contracts/managed/MyNFT';

const compiledContract = CompiledContract.make('MyNFT', Contract).pipe(
  CompiledContract.withVacantWitnesses,
  CompiledContract.withCompiledFileAssets('./contracts/managed/MyNFT'),
);

const providers = await buildProviders(/* see midnight-js skill */);
const deployed = await deployContract(providers, {
  compiledContract,
  privateStateId: 'nftPrivateState',
  initialPrivateState: {},
});

console.log('NFT Contract:', deployed.deployTxData.public.contractAddress);

// Mint an NFT
const mintResult = await deployed.callTx.mint(
  { type: 'left', value: callerPublicKey() },
  1n, // tokenId
  'https://my-nft.com/metadata/1.json',
);

6) シールド済み NFT とシールドなし NFT

項目シールドなし(公開)シールド済み(非公開)
所有権オンチェーンで公開非公開で、所有者のみが把握
転送誰でも確認可能非公開、ZK 証明で証明
メタデータオンチェーンで公開 URIコミット/ハッシュ化が可能
ガス/ユーザーコストDUST 手数料DUST 手数料(プレビューでは 1AM がスポンサー)
ユースケースパブリックアート、コレクティブル機密資産、非公開のコレクティブル
トークン ID可視コミットメントで非表示にできる
シールド済み NFT を使用する場合
  • 所有権の機密性:NFT の所有者を非公開にする必要がある場合
  • 非公開のコレクティブル:所有権が機密情報となる高価値アイテム
  • コンプライアンス・バイ・デザイン:必要な場合にのみ所有権を開示
シールドなし NFT を使用する場合
  • パブリックアート:所有権が価値提案の一部となる場合
  • 透明性の高い DAO:NFT としてのガバナンストークン
  • 標準的なコレクティブル:公開された来歴が価値を高める場合

7) メタデータの処理

公開メタデータ(シールドなし)
export ledger tokenURIs: Map<Uint<128>, Opaque<"string">;

export circuit setTokenURI(
  tokenId: Uint<128>,
  uri: Opaque<"string">,
): [] {
  tokenURIs.insert(disclose(tokenId), disclose(uri));
}
非公開メタデータ(シールド済み)
// Store only a commitment on-chain
export ledger metadataCommitments: Map<Uint<128>, Field>;

export circuit setPrivateMetadata(
  tokenId: Uint<128>,
  metadataHash: Field, // hash of off-chain metadata
): [] {
  const commitment = persistentCommit<Uint<128>>(tokenId, freshNonce());
  metadataCommitments.insert(disclose(tokenId), commitment);
}

8) よくある落とし穴

問題解決策
Uint<256> のトークン IDサポートされていません。最大で Uint<128> を使用してください(回路の制約)。
コントラクト間の転送まだサポートされていません。_unsafeTransfer は注意して使用してください。
ベース URI の文字列連結Compact ではサポートされていません。トークンごとに URI 全体を保存してください。
tokenId のオーバーフローミント前に assert(tokenId < MAX_ID, "ID overflow") を使用してください。
URI に対する disclose() の欠落メタデータ URI は公開情報です。保存時に disclose() を使用してください。
証明のないシールド済み転送所有者が転送に署名する必要があります。callerPublicKey() で確認してください。

9) NFT コントラクトのテスト

// In contracts/src/test/MyNFT.test.compact
pragma language_version >= 0.21.0;

import MyNFT;
import CompactStandardLibrary;

constructor() {
  MyNFT_initialize("MyNFT", "MNFT");
}

export circuit testMint(): [] {
  const tokenId = MyNFT_mint(disclose(callerPublicKey()), "uri1");
  assert(tokenId == 1, "First token should be 1");
}

export circuit testTransfer(): [] {
  MyNFT_transferFrom(
    callerPublicKey(),
    someOtherKey(),
    1,
  );
  const newOwner = MyNFT_ownerOf(1);
  assert(newOwner == someOtherKey(), "Transfer failed");
}

10) package.json スクリプト

{
  "scripts": {
    "compact": "cd contracts && compact compile MyNFT.compact artifacts/MyNFT",
    "build": "tsc && npm run compact",
    "test": "cd contracts && compact compile MyNFT.test.compact artifacts/MyNFT.test"
  }
}

11) 便利なリンク

コメント