关于 nft
面向 Midnight Network 开发者的 NFT 开发技能,涵盖 OpenZeppelin Compact 非同质化代币合约和原生代币操作。内容说明公开与隐私所有权模式、铸造、转移、授权及元数据处理。
主要功能
- 01提供 ERC721 类 Compact 合约
- 02涵盖铸造、转移与授权
- 03支持公开及隐私所有权模式
- 04处理代币 URI 与元数据承诺
- 05说明编译、部署与测试
适用场景
- 01开发公开收藏品 NFT
- 02构建所有权保密的 NFT
- 03为代币配置元数据 URI
- 04测试 Compact NFT 合约
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ kali-decoder/ midnight-skills --skill nft安装后在 Claude Code 中输入/nft确认已加载。
详细介绍
范围 此技能涵盖 Midnight Network 上的 NFT 开发,包括 OpenZeppelin NonFungibleToken 合约(在 Compact 中实现,类似 ERC721)和 Midnight 原生代币操作(shielded/unshielded)。涵盖铸造、转账、授权、元数据 URI 以及 NFT 的隐私模式。
请严格按照以下路径放置文件:
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 标准库提供用于 shielded/unshielded 操作的原生代币函数。
Unshielded 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));
}
Shielded NFT(私有所有权)
使用 Compact 标准库的 shielded 代币函数:
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) Shielded 与 Unshielded NFT
| 方面 | Unshielded(公开) | Shielded(私有) |
|---|---|---|
| 所有权 | 在链上公开 | 私有,仅所有者知晓 |
| 转账 | 所有人可见 | 隐藏,由 ZK 证明 |
| 元数据 | 链上公开 URI | 可提交或哈希处理 |
| Gas/用户成本 | DUST 费用 | DUST 费用(预览版由 1AM 赞助) |
| 用例 | 公共艺术品、收藏品 | 机密资产、私有收藏品 |
| 代币 ID | 可见 | 可通过承诺隐藏 |
何时使用 Shielded NFT
- 所有权保密:需要对 NFT 所有者保密时
- 私有收藏品:所有权敏感的高价值物品
- 合规设计:仅在需要时公开所有权
何时使用 Unshielded NFT
- 公共艺术品:所有权是其价值主张的一部分
- 透明 DAO:作为 NFT 的治理代币
- 标准收藏品:公开来源信息能够增加价值的场景
7) 元数据处理
公开元数据(Unshielded)
export ledger tokenURIs: Map<Uint<128>, Opaque<"string">;
export circuit setTokenURI(
tokenId: Uint<128>,
uri: Opaque<"string">,
): [] {
tokenURIs.insert(disclose(tokenId), disclose(uri));
}
私有元数据(Shielded)
// 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()。 |
| Shielded 转账未提供证明 | 所有者必须为转账签名。请使用 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)实用链接
- Compact 版 OpenZeppelin Contracts — 库文档
- Compact 语言指南 — 智能合约语言参考
- Midnight 代币函数 — 原生代币操作
- 1AM 钱包集成 — 连接钱包以进行 NFT 交互
- Compact 技能 — 语言基础
评论
支持 Markdown