

Cardano研究小组
Cardano是当前智能合约公链领域的“房间里的大象”,五年来市值长期居于前十,拥有独特的社区文化。但中文领域的可靠信息源极少。本小组旨在理性研究和客观评估Cardano的现状及潜力。欢迎感兴趣的小伙伴踊跃加入,一起研究!
Cardano 测试网 NFT 铸造入门教程
-
Cardano 测试网 NFT 铸造入门教程
目录- 核心概念解释
- 环境准备:搭建 Cardano 节点
- 为什么需要运行节点?
- 1. Docker Compose 配置详解
- 2. 启动和验证服务
- 3. 进入 CLI 工作环境
- 第一步:环境配置和验证
- 设置关键环境变量
- 验证节点同步状态
- 第二步:钱包生成和管理
- 理解 Cardano 地址体系
- 生成密钥对
- 生成钱包地址
- 可选:生成质押地址(高级功能)
- 第三步:获取测试币
- 理解 Cardano 代币单位
- 使用官方水龙头
- 验证资金到账
- 备用获取方式
- 第四步:策略脚本创建
- 理解策略脚本的作用
- 生成策略密钥
- 计算密钥哈希
- 设置时间锁定
- 创建策略脚本
- 验证策略脚本
- 第五步:策略ID生成
- 理解策略ID的重要性
- 生成策略ID
- 验证策略ID
- 第六步:NFT元数据创建
- 理解 CIP-25 元数据标准
- 基础元数据结构
- 创建标准元数据
- 添加版税信息(可选)
- 验证元数据格式
- 第七步:构建铸造交易
- 理解UTXO模型
- 查询可用UTXO
- 选择合适的UTXO
- 设置交易参数
- 构建铸造交易
- 交易费用估算
- 第八步:签名和提交交易
- 理解数字签名
- 签名交易
- 最终验证
- 提交交易
- 获取交易哈希
- 第九步:验证和确认结果
- 等待交易确认
- 验证NFT铸造结果
- 生成成功报告
- 保存重要文件
- 进阶操作指南
- 1. 转移NFT到其他地址
- 2. 查询策略下的所有NFT
- 3. 验证NFT真实性
- 故障排除指南
- 常见错误及解决方案
- 调试工具和技巧
- 最佳实践和安全建议
- 1. 密钥管理
- 2. 交易安全检查
- 3. 性能优化建议
- 总结与后续学习
- 你已经掌握的技能
Cardano 是一个第三代区块链平台,以其学术严谨性和可持续性著称。与以太坊不同,Cardano 使用原生代币标准,这意味着 NFT 在协议层面得到支持,而不是通过智能合约实现。这种设计带来了更低的费用、更高的安全性和更好的性能。
核心概念解释
策略ID(Policy ID):每个 NFT 系列都有唯一的策略ID,相当于 NFT 的"身份证"。策略ID 由策略脚本的哈希值生成,确保了 NFT 的唯一性和不可伪造性。
策略脚本(Policy Script):定义了 NFT 铸造规则的脚本,包括谁可以铸造、什么时候可以铸造等条件。一旦部署,这些规则就不可更改。
时间锁定(Time Lock):在策略脚本中设置的时间限制,过期后该策略就永远无法再用于铸造新的代币,保证了 NFT 的稀缺性。
资产名称(Asset Name):在同一策略下,每个代币的唯一标识符。完整的代币标识为"策略ID.资产名称"。
环境准备:搭建 Cardano 节点
为什么需要运行节点?
要与 Cardano 网络交互,我们需要一个全节点来:
- 同步区块链数据
- 验证交易
- 提交交易到网络
- 查询链上状态
我们使用测试网(Preprod)进行学习,因为:
- 测试币免费获取
- 可以安全地进行实验
- 与主网功能完全相同
- 不会产生真实费用
1. Docker Compose 配置详解
创建
docker-compose.yml
文件:version: '3.8' services: # Cardano 节点服务 cardano-node: image: ghcr.io/blinklabs-io/cardano-node:latest container_name: cardano-node restart: unless-stopped ports: - "3001:3001" # 节点 P2P 通信端口 environment: - NETWORK=preprod # 指定测试网络 volumes: - node-data:/data/db # 区块链数据存储 - node-ipc:/ipc # 进程间通信套接字 init: true # 启用 init 进程管理 # Cardano CLI 工具容器 cardano-cli: image: ghcr.io/blinklabs-io/cardano-node:latest container_name: cardano-cli restart: unless-stopped environment: - NETWORK=preprod volumes: - node-ipc:/ipc # 共享通信套接字 depends_on: - cardano-node # 依赖节点服务 command: ["sleep", "infinity"] # 保持容器运行 volumes: node-data: # 持久化区块链数据 node-ipc: # 节点间通信
配置说明:
init: true
:启用轻量级 init 进程,正确处理信号和僵尸进程ports: 3001:3001
:映射节点 P2P 通信端口NETWORK=preprod
:指定使用 Preprod 测试网- 数据卷:
node-data
存储区块链数据,node-ipc
用于 CLI 与节点通信 depends_on
:确保 CLI 容器在节点容器之后启动
2. 启动和验证服务
# 启动所有服务 docker-compose up -d # 查看服务状态 docker-compose ps # 查看节点日志(观察同步进度) docker-compose logs -f cardano-node # 查看最近100行日志 docker-compose logs --tail=100 cardano-node
日志解读:
- 看到 "Chain extended" 表示正在同步区块
- "TraceNodeIsLeader" 表示节点正常参与网络
- 同步过程可能需要几小时,取决于网络速度
3. 进入 CLI 工作环境
# 进入 CLI 容器 docker exec -ti cardano-cli bash # 或者使用 Dockge 的 Web 终端(如果使用 Dockge)
第一步:环境配置和验证
设置关键环境变量
# 设置节点套接字路径(必须) export CARDANO_NODE_SOCKET_PATH=/ipc/node.socket # 验证套接字文件存在 ls -la /ipc/node.socket # 创建工作目录 mkdir -p /keys cd /keys # 验证 CLI 工具版本 cardano-cli --version
验证节点同步状态
# 查询节点状态 cardano-cli query tip --testnet-magic 1
期望输出示例:
{ "block": 3992119, "epoch": 245, "era": "Conway", "hash": "25ffab7df708fedb8d14b6bc2998907afa1735445ba132f03cb1f1f6716d57b2", "slot": 104340690, "slotInEpoch": 142290, "slotsToEpochEnd": 289710, "syncProgress": "100.00" }
重要指标解释:
syncProgress: "100.00"
:必须达到100%才能进行交易era: "Conway"
:当前网络时代,支持最新功能slot
:当前时隙,用于时间锁定计算epoch
:当前纪元,每个纪元约5天
⚠️ 警告:只有当
syncProgress
显示"100.00"
时才能继续后续操作!第二步:钱包生成和管理
理解 Cardano 地址体系
Cardano 使用 Bech32 编码的地址格式:
- 测试网地址:以
addr_test1
开头 - 主网地址:以
addr1
开头 - 地址类型:支付地址、质押地址、脚本地址等
生成密钥对
# 生成支付密钥对 cardano-cli address key-gen \\ --verification-key-file /keys/payment.vkey \\ --signing-key-file /keys/payment.skey # 查看生成的文件 ls -la /keys/payment.* # 查看公钥内容(可以安全分享) cat /keys/payment.vkey
文件说明:
payment.vkey
:验证密钥(公钥),用于生成地址,可以公开payment.skey
:签名密钥(私钥),用于签名交易,必须保密
生成钱包地址
# 生成基础支付地址 cardano-cli address build \\ --payment-verification-key-file /keys/payment.vkey \\ --out-file /keys/payment.addr \\ --testnet-magic 1 # 保存地址到变量 ADDRESS=$(cat /keys/payment.addr) # 显示地址信息 echo "🏦 你的测试网钱包地址:" echo "$ADDRESS" echo "" echo "📝 地址长度: $(echo -n $ADDRESS | wc -c) 字符" echo "🌐 网络类型: Preprod 测试网"
地址示例:
addr_test1vr35z3scn27mertcrm7qmtdec9373924jtt8f24pe9544dcjk0wsx
可选:生成质押地址(高级功能)
# 生成质押密钥对 cardano-cli stake-address key-gen \\ --verification-key-file /keys/stake.vkey \\ --signing-key-file /keys/stake.skey # 生成包含质押功能的地址 cardano-cli address build \\ --payment-verification-key-file /keys/payment.vkey \\ --stake-verification-key-file /keys/stake.vkey \\ --out-file /keys/payment_with_stake.addr \\ --testnet-magic 1 echo "💎 带质押功能的地址:" cat /keys/payment_with_stake.addr
第三步:获取测试币
理解 Cardano 代币单位
- ADA:Cardano 的主要代币
- Lovelace:ADA 的最小单位(1 ADA = 1,000,000 Lovelace)
- 测试网 ADA:没有真实价值,仅用于测试
使用官方水龙头
- 复制钱包地址:
# 显示地址以便复制 echo "请复制以下地址:" echo "$ADDRESS"
- 访问水龙头网站:
- 申请流程:
- 粘贴你的地址
- 选择 "Preprod" 网络
- 点击申请按钮
- 等待确认信息
验证资金到账
# 检查余额(可能需要等待几分钟) cardano-cli query utxo --address $ADDRESS --testnet-magic 1 # 如果没有余额,等待一会儿再查询 sleep 30 cardano-cli query utxo --address $ADDRESS --testnet-magic 1
成功输出示例:
{ "95504c30d402ae2244407a7d7edd1711d88000a20b2101b34c0e80fbf8e5ff4e#0": { "address": "addr_test1vr35z3scn27mertcrm7qmtdec9373924jtt8f24pe9544dcjk0wsx", "datum": null, "datumhash": null, "inlineDatum": null, "inlineDatumRaw": null, "referenceScript": null, "value": { "lovelace": 10000000000 } } }
输出解释:
- UTXO 标识:
TxHash#TxIx
格式,唯一标识每个未花费输出 lovelace: 10000000000
:等于 10,000 ADAdatum
和datumhash
:智能合约相关数据,此处为空
备用获取方式
如果官方水龙头不可用:
- Discord 水龙头:
- 加入 Cardano 官方 Discord
- 在
#testnet-faucet
频道申请
- Telegram 机器人:
- 联系
@CardanoTestnetFaucetBot
- 联系
第四步:策略脚本创建
理解策略脚本的作用
策略脚本定义了代币铸造的规则:
- 签名验证:只有特定密钥持有者可以铸造
- 时间限制:设置铸造的有效时间窗口
- 数量限制:可以设置最大铸造数量
- 多重签名:可以要求多个签名
生成策略密钥
# 生成策略专用密钥对 cardano-cli address key-gen \\ --verification-key-file /keys/policy.vkey \\ --signing-key-file /keys/policy.skey echo "✅ 策略密钥生成完成" # 验证密钥文件 ls -la /keys/policy.*
计算密钥哈希
# 计算公钥哈希(用于策略脚本) cardano-cli address key-hash \\ --payment-verification-key-file /keys/policy.vkey > /keys/policy.keyid # 显示密钥哈希 POLICY_KEY_HASH=$(cat /keys/policy.keyid) echo "🔑 策略密钥哈希: $POLICY_KEY_HASH"
设置时间锁定
# 获取当前区块链状态 CURRENT_SLOT=$(cardano-cli query tip --testnet-magic 1 | jq .slot) echo "⏰ 当前 Slot: $CURRENT_SLOT" # 计算过期时间(24小时后) # 1 slot = 1 秒,24小时 = 86400 秒 EXPIRE_SLOT=$((CURRENT_SLOT + 86400)) echo "⏰ 策略过期 Slot: $EXPIRE_SLOT" # 计算剩余时间 REMAINING_SECONDS=$((EXPIRE_SLOT - CURRENT_SLOT)) REMAINING_HOURS=$((REMAINING_SECONDS / 3600)) echo "⏰ 策略有效期: $REMAINING_HOURS 小时"
时间选择建议:
- NFT 收藏品:1-7天(保证稀缺性)
- 游戏道具:30-365天(根据游戏需求)
- 测试用途:1-24小时(便于快速实验)
创建策略脚本
# 创建策略脚本文件 cat > /keys/policy.script << EOF { "type": "all", "scripts": [ { "type": "sig", "keyHash": "$(cat /keys/policy.keyid)" }, { "type": "before", "slot": ${EXPIRE_SLOT} } ] } EOF echo "✅ 策略脚本创建完成" # 显示策略脚本内容 echo "📜 策略脚本内容:" cat /keys/policy.script | jq '.'
策略脚本解释:
"type": "all"
:所有条件都必须满足"type": "sig"
:需要指定密钥的签名"type": "before"
:必须在指定 slot 之前执行keyHash
:授权签名的密钥哈希slot
:过期的绝对时间点
验证策略脚本
# 验证脚本格式 if jq empty /keys/policy.script 2>/dev/null; then echo "✅ 策略脚本格式正确" else echo "❌ 策略脚本格式错误" fi # 检查文件权限 ls -la /keys/policy.script
第五步:策略ID生成
理解策略ID的重要性
策略ID是策略脚本的加密哈希值,具有以下特性:
- 唯一性:每个不同的策略脚本都有唯一的策略ID
- 不可伪造:无法创建具有相同策略ID的不同脚本
- 永久性:策略ID一旦生成就永远不变
生成策略ID
# 从策略脚本生成策略ID cardano-cli conway transaction policyid \\ --script-file /keys/policy.script > /keys/policy.id # 保存到变量 POLICY_ID=$(cat /keys/policy.id) echo "🆔 你的策略ID:" echo "$POLICY_ID" echo "" echo "📏 策略ID长度: $(echo -n $POLICY_ID | wc -c) 字符" echo "🔒 策略ID类型: 56位十六进制哈希值"
策略ID示例:
bfc0c4cef7bfdb90550769f9f10a2f14709312097a5b6560b001c8b8
验证策略ID
# 验证策略ID格式(应该是56位十六进制) if [[ $POLICY_ID =~ ^[a-f0-9]{56}$ ]]; then echo "✅ 策略ID格式正确" else echo "❌ 策略ID格式错误" fi # 重新生成验证(结果应该相同) POLICY_ID_CHECK=$(cardano-cli conway transaction policyid --script-file /keys/policy.script) if [ "$POLICY_ID" = "$POLICY_ID_CHECK" ]; then echo "✅ 策略ID验证通过" else echo "❌ 策略ID验证失败" fi
第六步:NFT元数据创建
理解 CIP-25 元数据标准
Cardano 使用 CIP-25 标准定义 NFT 元数据:
- 标准化格式:确保不同平台间的兼容性
- 链上存储:元数据直接存储在交易中
- 丰富信息:支持名称、描述、图片、属性等
基础元数据结构
# 设置NFT资产名称 NFT_NAME="MyNFT" echo "🎨 NFT名称: $NFT_NAME" # 将名称转换为十六进制(避免特殊字符问题) NFT_NAME_HEX="4d794e4654" # "MyNFT" 的十六进制编码 echo "🔢 NFT名称(十六进制): $NFT_NAME_HEX" # 验证十六进制编码 echo -n "$NFT_NAME" | xxd -p # 如果xxd可用
创建标准元数据
# 创建符合CIP-25标准的元数据 cat > /keys/metadata.json << EOF { "721": { "${POLICY_ID}": { "${NFT_NAME}": { "name": "My First Cardano NFT", "description": "This is my first NFT created on Cardano testnet for learning purposes.", "image": "ipfs://QmYourImageHashHere", "mediaType": "image/png", "creator": "Cardano NFT Student", "website": "https://cardano.org", "attributes": { "type": "Digital Art", "rarity": "Common", "color": "Blue", "edition": "1/1", "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" }, "files": [ { "name": "My First NFT", "mediaType": "image/png", "src": "ipfs://QmYourImageHashHere" } ] } } } } EOF echo "✅ NFT元数据创建完成"
添加版税信息(可选)
# 创建包含版税的增强元数据 cat > /keys/metadata_with_royalty.json << EOF { "721": { "${POLICY_ID}": { "${NFT_NAME}": { "name": "My First Cardano NFT", "description": "This is my first NFT created on Cardano testnet with royalty settings.", "image": "ipfs://QmYourImageHashHere", "mediaType": "image/png", "creator": "Cardano NFT Student", "website": "https://cardano.org", "attributes": { "type": "Digital Art", "rarity": "Common", "color": "Blue", "edition": "1/1", "created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" }, "royalty": { "rate": "0.05", "addr": "$ADDRESS" } } } }, "777": { "royalty": { "${POLICY_ID}": { "rate": "0.05", "addr": "$ADDRESS" } } } } EOF # 选择使用哪个元数据文件 cp /keys/metadata_with_royalty.json /keys/metadata.json echo "✅ 已启用5%版税设置"
版税设置说明:
rate: "0.05"
:5%版税率addr
:版税接收地址777
标签:CIP-27版税标准- 市场支持:需要NFT市场主动支持版税执行
验证元数据格式
# 验证JSON格式 if jq empty /keys/metadata.json 2>/dev/null; then echo "✅ 元数据JSON格式正确" # 显示元数据摘要 echo "📊 元数据摘要:" echo " - NFT名称: $(jq -r ".\\"721\\".\\"$POLICY_ID\\".\\"$NFT_NAME\\".name" /keys/metadata.json)" echo " - 描述: $(jq -r ".\\"721\\".\\"$POLICY_ID\\".\\"$NFT_NAME\\".description" /keys/metadata.json)" echo " - 创建者: $(jq -r ".\\"721\\".\\"$POLICY_ID\\".\\"$NFT_NAME\\".creator" /keys/metadata.json)" # 检查版税设置 if jq -e ".\\"777\\"" /keys/metadata.json > /dev/null 2>&1; then echo " - 版税: $(jq -r ".\\"777\\".royalty.\\"$POLICY_ID\\".rate" /keys/metadata.json | awk '{print $1*100}')%" else echo " - 版税: 未设置" fi else echo "❌ 元数据JSON格式错误" jq . /keys/metadata.json # 显示错误详情 fi
第七步:构建铸造交易
理解UTXO模型
Cardano使用UTXO(未花费交易输出)模型:
- UTXO:类似现金,要么完全花费,要么不花费
- 找零机制:花费UTXO时,多余的金额作为找零返回
- 原子性:交易要么完全成功,要么完全失败
查询可用UTXO
# 查询当前所有可用UTXO echo "🔍 查询可用UTXO..." cardano-cli query utxo --address $ADDRESS --testnet-magic 1 # 保存查询结果用于分析 cardano-cli query utxo --address $ADDRESS --testnet-magic 1 > /keys/utxos.json # 分析UTXO echo "" echo "📊 UTXO分析:" UTXO_COUNT=$(jq 'length' /keys/utxos.json) TOTAL_LOVELACE=$(jq '[.[].value.lovelace] | add' /keys/utxos.json) TOTAL_ADA=$((TOTAL_LOVELACE / 1000000)) echo " - UTXO数量: $UTXO_COUNT" echo " - 总余额: $TOTAL_LOVELACE Lovelace ($TOTAL_ADA ADA)"
选择合适的UTXO
# 从查询结果中选择第一个UTXO UTXO=$(jq -r 'keys[0]' /keys/utxos.json) echo "🎯 选择的UTXO: $UTXO" # 验证UTXO格式 if [[ $UTXO =~ ^[a-f0-9]{64}#[0-9]+$ ]]; then echo "✅ UTXO格式正确" else echo "❌ UTXO格式错误,请手动设置" echo "请从上面的查询结果中复制一个UTXO:" read -p "输入UTXO (格式: txhash#txix): " UTXO fi echo "🔗 使用UTXO: $UTXO"
设置交易参数
# 获取当前时间信息 CURRENT_SLOT=$(cardano-cli query tip --testnet-magic 1 | jq .slot) echo "⏰ 当前Slot: $CURRENT_SLOT" # 设置交易有效期(1小时后) VALID_UNTIL=$((CURRENT_SLOT + 3600)) echo "⏰ 交易有效到Slot: $VALID_UNTIL" # 计算剩余时间 REMAINING_TIME=$((VALID_UNTIL - CURRENT_SLOT)) echo "⏰ 交易有效期: $((REMAINING_TIME / 60))分钟" # 验证策略是否仍然有效 POLICY_EXPIRE=$(jq -r '.scripts[1].slot' /keys/policy.script) if [ $CURRENT_SLOT -lt $POLICY_EXPIRE ]; then echo "✅ 策略仍然有效 (剩余 $(((POLICY_EXPIRE - CURRENT_SLOT) / 3600))小时)" else echo "❌ 策略已过期,需要重新创建" exit 1 fi
构建铸造交易
echo "🔨 构建铸造交易..." # 构建交易(使用新版cardano-cli语法) cardano-cli conway transaction build \\ --testnet-magic 1 \\ --tx-in $UTXO \\ --mint "1 ${POLICY_ID}.${NFT_NAME_HEX}" \\ --mint-script-file /keys/policy.script \\ --metadata-json-file /keys/metadata.json \\ --change-address $ADDRESS \\ --invalid-hereafter $VALID_UNTIL \\ --out-file /keys/tx.unsigned # 检查构建结果 if [ -f "/keys/tx.unsigned" ]; then echo "✅ 交易构建成功" # 显示交易信息 echo "" echo "📋 交易摘要:" echo " - 输入UTXO: $UTXO" echo " - 铸造资产: 1 ${POLICY_ID}.${NFT_NAME_HEX}" echo " - 找零地址: $ADDRESS" echo " - 有效期至: Slot $VALID_UNTIL" # 显示文件大小 TX_SIZE=$(wc -c < /keys/tx.unsigned) echo \" - 交易大小: $TX_SIZE 字节\" else echo \"❌ 交易构建失败\" exit 1 fi
构建参数说明:
--tx-in
:指定输入UTXO--mint
:指定要铸造的资产数量和名称--mint-script-file
:提供策略脚本文件--metadata-json-file
:附加元数据--change-address
:指定找零地址--invalid-hereafter
:设置交易过期时间
交易费用估算
# 从构建输出中提取费用信息 if grep -q \"Estimated transaction fee:\" /dev/stdout; then echo \"💰 交易费用已在构建过程中显示\" else echo \"💰 交易费用: 约184,000 Lovelace (0.184 ADA)\" fi echo \"\" echo \"💡 费用说明:\" echo \" - 基础费用: ~155,000 Lovelace\" echo \" - 元数据费用: ~20,000 Lovelace\" echo \" - 脚本执行费: ~10,000 Lovelace\" echo \" - 总计: ~185,000 Lovelace\"
第八步:签名和提交交易
理解数字签名
在Cardano中,交易签名确保:
- 身份验证:证明交易由密钥持有者发起
- 完整性:确保交易内容未被篡改
- 不可否认性:签名者无法否认已签名的交易
签名交易
echo \"✍️ 签名交易...\" # 使用两个密钥签名:支付密钥和策略密钥 cardano-cli conway transaction sign \\ --signing-key-file /keys/payment.skey \\ --signing-key-file /keys/policy.skey \\ --testnet-magic 1 \\ --tx-body-file /keys/tx.unsigned \\ --out-file /keys/tx.signed # 验证签名结果 if [ -f \"/keys/tx.signed\" ]; then echo \"✅ 交易签名成功\" # 比较文件大小 UNSIGNED_SIZE=$(wc -c < /keys/tx.unsigned) SIGNED_SIZE=$(wc -c < /keys/tx.signed) echo " - 未签名交易: $UNSIGNED_SIZE 字节" echo " - 已签名交易: $SIGNED_SIZE 字节" echo " - 签名增加: $((SIGNED_SIZE - UNSIGNED_SIZE)) 字节" else echo "❌ 交易签名失败" exit 1 fi
签名过程说明:
- 支付密钥签名:证明有权花费UTXO
- 策略密钥签名:证明有权铸造NFT
- 网络标识:
--testnet-magic 1
确保签名只在测试网有效
最终验证
echo "🔍 最终验证..." # 检查所有必要文件 echo "📁 文件检查:" for file in payment.addr payment.skey policy.skey policy.script metadata.json tx.signed; do if [ -f "/keys/$file" ]; then echo " ✅ $file" else echo " ❌ $file (缺失)" fi done # 检查环境变量 echo "" echo "🔧 环境检查:" echo " - 节点套接字: $CARDANO_NODE_SOCKET_PATH" echo " - 钱包地址: $ADDRESS" echo " - 策略ID: $POLICY_ID" echo " - 使用UTXO: $UTXO" # 最后的节点连接测试 echo "" echo "🌐 节点连接测试:" if cardano-cli query tip --testnet-magic 1 > /dev/null 2>&1; then echo " ✅ 节点连接正常" else echo " ❌ 节点连接失败" exit 1 fi
提交交易
echo "" echo "🚀 提交交易到Cardano网络..." echo "⏳ 请等待..." # 提交已签名的交易 cardano-cli conway transaction submit \\ --testnet-magic 1 \\ --tx-file /keys/tx.signed # 检查提交结果 if [ $? -eq 0 ]; then echo "" echo "🎉🎉🎉 交易提交成功!🎉🎉🎉" echo "" echo "🎊 恭喜!你已经成功铸造了你的第一个Cardano NFT!" else echo "" echo "❌ 交易提交失败" echo "请检查错误信息并重试" exit 1 fi
获取交易哈希
# 计算交易哈希 TX_HASH=$(cardano-cli hash tx --tx-file /keys/tx.signed) echo "🔗 交易哈希: $TX_HASH" # 保存交易哈希 echo "$TX_HASH" > /keys/tx.hash echo "💾 交易哈希已保存到 /keys/tx.hash" # 生成交易摘要 echo "" echo "📋 交易摘要报告:" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🆔 策略ID: $POLICY_ID" echo "🎨 NFT名称: $NFT_NAME ($NFT_NAME_HEX)" echo "🔗 完整资产ID: ${POLICY_ID}.${NFT_NAME_HEX}" echo "📧 钱包地址: $ADDRESS" echo "🔗 交易哈希: $TX_HASH" echo "⏰ 提交时间: $(date)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
第九步:验证和确认结果
等待交易确认
echo "" echo "⏳ 等待交易确认..." echo "💡 Cardano出块时间约20秒,确认通常需要1-3分钟" # 等待一段时间让交易被包含到区块中 for i in {1..6}; do echo "⏳ 等待中... ($i/6)" sleep 30 # 每次等待后检查一次 echo "🔍 检查交易状态..." cardano-cli query utxo --address $ADDRESS --testnet-magic 1 > /tmp/current_utxos.json # 检查是否包含新的NFT if jq -e ".[].value | has(\\"$POLICY_ID\\")" /tmp/current_utxos.json > /dev/null 2>&1; then echo "✅ 检测到NFT!交易已确认" break fi if [ $i -eq 6 ]; then echo "⏰ 等待时间较长,继续检查..." fi done
验证NFT铸造结果
echo "" echo "🔍 验证NFT铸造结果..." # 查询当前钱包状态 cardano-cli query utxo --address $ADDRESS --testnet-magic 1 > /keys/final_utxos.json echo "💼 当前钱包状态:" cat /keys/final_utxos.json | jq '.' # 分析结果 echo "" echo "📊 详细分析:" # 检查NFT是否存在 if jq -e ".[].value | has(\\"$POLICY_ID\\")" /keys/final_utxos.json > /dev/null; then echo "✅ NFT铸造成功!" # 提取NFT信息 NFT_AMOUNT=$(jq -r ".[].value.\\"$POLICY_ID\\".\\"$NFT_NAME_HEX\\"" /keys/final_utxos.json | grep -v null | head -1) echo "🎨 NFT数量: $NFT_AMOUNT" # 计算剩余ADA REMAINING_LOVELACE=$(jq '[.[].value.lovelace] | add' /keys/final_utxos.json) REMAINING_ADA=$((REMAINING_LOVELACE / 1000000)) echo "💰 剩余ADA: $REMAINING_ADA ADA ($REMAINING_LOVELACE Lovelace)" # 计算费用 ORIGINAL_ADA=10000 # 水龙头给的数量 SPENT_ADA=$((ORIGINAL_ADA - REMAINING_ADA)) echo "💸 交易费用: ~$SPENT_ADA ADA" else echo "❌ 未检测到NFT,请检查交易状态" echo "🔍 你可以通过交易哈希在区块链浏览器中查看详情:" echo " https://preprod.cardanoscan.io/transaction/$TX_HASH" fi
生成成功报告
# 创建成功报告 cat > /keys/nft_success_report.txt << EOF 🎉 Cardano NFT 铸造成功报告 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📅 铸造时间: $(date) 🌐 网络: Cardano Preprod 测试网 🎨 NFT 详情: 名称: $NFT_NAME 完整资产ID: ${POLICY_ID}.${NFT_NAME_HEX} 策略ID: $POLICY_ID 资产名称(十六进制): $NFT_NAME_HEX 数量: 1 (独一无二) 🔗 技术信息: 交易哈希: $TX_HASH 钱包地址: $ADDRESS 使用UTXO: $UTXO 💰 费用信息: 估算费用: ~0.18 ADA 剩余余额: ~$REMAINING_ADA ADA 🔍 验证链接: Cardano Scan: https://preprod.cardanoscan.io/transaction/$TX_HASH 🎊 恭喜!你已经成功创建了你的第一个Cardano NFT! ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EOF echo \"\" echo \"📄 成功报告已生成: /keys/nft_success_report.txt\" cat /keys/nft_success_report.txt
保存重要文件
echo \"\" echo \"💾 保存重要文件...\" # 创建备份目录 mkdir -p /keys/backup # 备份关键文件 cp /keys/payment.skey /keys/backup/ cp /keys/payment.vkey /keys/backup/ cp /keys/payment.addr /keys/backup/ cp /keys/policy.skey /keys/backup/ cp /keys/policy.vkey /keys/backup/ cp /keys/policy.script /keys/backup/ cp /keys/policy.id /keys/backup/ cp /keys/metadata.json /keys/backup/ cp /keys/tx.signed /keys/backup/ cp /keys/tx.hash /keys/backup/ cp /keys/nft_success_report.txt /keys/backup/ echo \"✅ 重要文件已备份到 /keys/backup/\" # 显示文件清单 echo \"\" echo \"📁 文件清单:\" ls -la /keys/backup/ echo \"\" echo \"⚠️ 重要提醒:\" echo \" - 请妥善保管私钥文件 (.skey)\" echo \" - 私钥丢失将无法控制钱包和NFT\" echo \" - 建议将备份文件下载到安全位置\"
进阶操作指南
1. 转移NFT到其他地址
# 准备转移NFT的函数 transfer_nft() { local RECIPIENT_ADDRESS=\"$1\" if [ -z \"$RECIPIENT_ADDRESS\" ]; then echo \"❌ 请提供接收地址\" echo \"用法: transfer_nft " return 1 fi echo "📤 准备转移NFT到: $RECIPIENT_ADDRESS" # 查询当前UTXO cardano-cli query utxo --address $ADDRESS --testnet-magic 1 > /tmp/transfer_utxos.json # 选择包含NFT的UTXO NFT_UTXO=$(jq -r "to_entries | map(select(.value.value.\\"$POLICY_ID\\")) | .[0].key" /tmp/transfer_utxos.json) if [ "$NFT_UTXO" = "null" ]; then echo "❌ 未找到包含NFT的UTXO" return 1 fi echo "🎯 使用UTXO: $NFT_UTXO" # 构建转移交易 cardano-cli conway transaction build \\ --testnet-magic 1 \\ --tx-in $NFT_UTXO \\ --tx-out "${RECIPIENT_ADDRESS}+2000000+1 ${POLICY_ID}.${NFT_NAME_HEX}" \\ --change-address $ADDRESS \\ --out-file /keys/transfer.unsigned # 签名交易 cardano-cli conway transaction sign \\ --signing-key-file /keys/payment.skey \\ --testnet-magic 1 \\ --tx-body-file /keys/transfer.unsigned \\ --out-file /keys/transfer.signed # 提交交易 cardano-cli conway transaction submit \\ --testnet-magic 1 \\ --tx-file /keys/transfer.signed echo "✅ NFT转移交易已提交" } echo "" echo "💡 NFT转移功能已准备就绪" echo "用法: transfer_nft "
2. 查询策略下的所有NFT
# 查询策略下所有代币的函数 query_policy_tokens() { echo "🔍 查询策略 $POLICY_ID 下的所有代币..." # 在整个UTXO集中搜索该策略的代币 cardano-cli query utxo --whole-utxo --testnet-magic 1 | \\ jq --arg policy "$POLICY_ID" ' | to_entries | | --- | | map(select(.value.value[$policy])) | map({ utxo: .key, address: .value.address, tokens: .value.value[$policy] }) ' } echo "" echo "💡 策略代币查询功能已准备就绪" echo "用法: query_policy_tokens"
3. 验证NFT真实性
# NFT真实性验证函数 verify_nft() { local ASSET_ID="$1" if [ -z "$ASSET_ID" ]; then ASSET_ID="${POLICY_ID}.${NFT_NAME_HEX}" fi echo "🔍 验证NFT真实性: $ASSET_ID" # 分离策略ID和资产名称 local VERIFY_POLICY_ID="${ASSET_ID%.*}" local VERIFY_ASSET_NAME="${ASSET_ID##*.}" echo " 策略ID: $VERIFY_POLICY_ID" echo " 资产名称: $VERIFY_ASSET_NAME" # 检查策略脚本是否存在 if [ -f "/keys/policy.script" ] && [ "$VERIFY_POLICY_ID" = "$POLICY_ID" ]; then echo "✅ 策略验证通过 - 这是你创建的NFT" # 验证策略ID是否匹配脚本 COMPUTED_POLICY=$(cardano-cli conway transaction policyid --script-file /keys/policy.script) if [ "$COMPUTED_POLICY" = "$VERIFY_POLICY_ID" ]; then echo "✅ 策略脚本验证通过" else echo "❌ 策略脚本验证失败" fi else echo "⚠️ 这不是你创建的NFT,或策略脚本不可用" fi # 查询该NFT的当前持有者 echo "" echo "🔍 查询当前持有者..." cardano-cli query utxo --whole-utxo --testnet-magic 1 | \\ jq --arg policy "$VERIFY_POLICY_ID" --arg asset "$VERIFY_ASSET_NAME" ' | to_entries | | --- | | map(select(.value.value[$policy][$asset])) | map({ holder: .value.address, amount: .value.value[$policy][$asset] }) ' } echo "" echo "💡 NFT验证功能已准备就绪" echo "用法: verify_nft [资产ID]"
故障排除指南
常见错误及解决方案
1. "ScriptWitnessNotValidatingUTXOW" 错误
原因分析:
- 策略时间锁定已过期
- UTXO已被其他交易消费
- 策略脚本签名问题
解决步骤:
# 检查策略是否过期 CURRENT_SLOT=$(cardano-cli query tip --testnet-magic 1 | jq .slot) POLICY_EXPIRE=$(jq -r '.scripts[1].slot' /keys/policy.script) if [ $CURRENT_SLOT -gt $POLICY_EXPIRE ]; then echo "❌ 策略已过期,需要重新创建" # 重新创建策略 EXPIRE_SLOT=$((CURRENT_SLOT + 86400)) # 更新策略脚本 jq --arg slot "$EXPIRE_SLOT" '.scripts[1].slot = ($slot | tonumber)' /keys/policy.script > /keys/policy_new.script mv /keys/policy_new.script /keys/policy.script # 重新生成策略ID cardano-cli conway transaction policyid --script-file /keys/policy.script > /keys/policy.id POLICY_ID=$(cat /keys/policy.id) echo "✅ 策略已更新,新策略ID: $POLICY_ID" else echo "✅ 策略仍然有效" fi # 检查UTXO是否仍然可用 cardano-cli query utxo --address $ADDRESS --testnet-magic 1
2. "unexpected '.'" 语法错误
原因:资产名称包含特殊字符
解决方案:
# 使用十六进制编码 echo -n "YourNFTName" | od -A n -t x1 | tr -d ' \\n' # 或使用预定义的十六进制值 NFT_NAME_HEX="4d794e4654" # "MyNFT"
3. 节点同步问题
症状:查询命令返回错误或过时数据
解决方案:
# 检查节点状态 cardano-cli query tip --testnet-magic 1 # 如果同步进度不是100%,等待同步完成 while true; do SYNC_PROGRESS=$(cardano-cli query tip --testnet-magic 1 | jq -r '.syncProgress') echo "同步进度: $SYNC_PROGRESS" if [ "$SYNC_PROGRESS" = "100.00" ]; then echo "✅ 节点同步完成" break fi sleep 30 done
4. 内存或磁盘空间不足
检查资源使用:
# 检查磁盘空间 df -h # 检查容器资源 docker stats cardano-node cardano-cli # 清理不必要的文件 docker system prune -f
调试工具和技巧
1. 交易详细分析
# 分析未签名交易 analyze_transaction() { local TX_FILE="$1" echo "🔍 分析交易文件: $TX_FILE" # 使用cardano-cli解析交易 cardano-cli conway transaction view --tx-file "$TX_FILE" echo "" echo "📊 交易摘要:" # 提取关键信息 local TX_JSON=$(mktemp) cardano-cli conway transaction view --tx-file "$TX_FILE" --output-json > "$TX_JSON" echo " 输入数量: $(jq '.body.inputs | length' "$TX_JSON")" echo " 输出数量: $(jq '.body.outputs | length' "$TX_JSON")" echo " 费用: $(jq '.body.fee' "$TX_JSON") Lovelace" if jq -e '.body.mint' "$TX_JSON" > /dev/null; then echo " 铸造资产: $(jq '.body.mint' "$TX_JSON")" fi rm "$TX_JSON" } # 使用示例 # analyze_transaction /keys/tx.unsigned
2. 网络连接诊断
# 网络诊断函数 diagnose_network() { echo "🌐 Cardano网络连接诊断" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # 检查套接字连接 if [ -S "$CARDANO_NODE_SOCKET_PATH" ]; then echo "✅ 节点套接字存在" else echo "❌ 节点套接字不存在: $CARDANO_NODE_SOCKET_PATH" fi # 检查节点响应 if cardano-cli query tip --testnet-magic 1 > /dev/null 2>&1; then echo "✅ 节点响应正常" # 获取网络信息 local TIP=$(cardano-cli query tip --testnet-magic 1) echo " 当前区块: $(echo "$TIP" | jq -r '.block')" echo " 当前纪元: $(echo "$TIP" | jq -r '.epoch')" echo " 同步进度: $(echo "$TIP" | jq -r '.syncProgress')%" else echo "❌ 节点响应失败" fi # 检查协议参数 if cardano-cli query protocol-parameters --testnet-magic 1 > /dev/null 2>&1; then echo "✅ 协议参数查询正常" else echo "❌ 协议参数查询失败" fi }
3. 环境重置脚本
# 完整环境重置函数 reset_environment() { echo "🔄 重置Cardano NFT铸造环境" read -p "⚠️ 这将删除所有密钥和数据,确定继续吗?(y/N): " confirm if [ "$confirm" != "y" ]; then echo "❌ 操作已取消" return 1 fi # 备份现有数据 if [ -d "/keys" ]; then local BACKUP_DIR="/keys_backup_$(date +%Y%m%d_%H%M%S)" mv /keys "$BACKUP_DIR" echo "📦 旧数据已备份到: $BACKUP_DIR" fi # 重新创建工作目录 mkdir -p /keys cd /keys # 重新设置环境变量 export CARDANO_NODE_SOCKET_PATH=/ipc/node.socket echo "✅ 环境重置完成" echo "💡 现在可以重新开始NFT铸造流程" }
最佳实践和安全建议
1. 密钥管理
# 安全的密钥备份脚本 backup_keys_securely() { echo "🔐 安全备份密钥" # 创建加密备份 local BACKUP_FILE="/keys/secure_backup_$(date +%Y%m%d_%H%M%S).tar.gz.gpg" # 压缩关键文件 tar -czf - payment.skey payment.vkey policy.skey policy.vkey payment.addr policy.script policy.id metadata.json | \\ gpg --symmetric --cipher-algo AES256 --output "$BACKUP_FILE" echo "✅ 加密备份已创建: $BACKUP_FILE" echo "⚠️ 请记住加密密码!" } # 验证备份完整性 verify_backup() { local BACKUP_FILE="$1" echo "🔍 验证备份完整性: $BACKUP_FILE" # 测试解密(不实际解压) if gpg --decrypt "$BACKUP_FILE" | tar -tzf - > /dev/null 2>&1; then echo "✅ 备份文件完整且可解密" else echo "❌ 备份文件损坏或密码错误" fi }
2. 交易安全检查
# 交易提交前的安全检查 security_check() { echo "🛡️ 交易安全检查" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # 检查网络环境 local NETWORK_MAGIC=$(cardano-cli query tip --testnet-magic 1 | jq -r '.networkMagic // empty') if [ "$NETWORK_MAGIC" = "1" ]; then echo "✅ 确认在测试网环境" else echo "⚠️ 网络环境异常" fi # 检查交易目标 echo "🎯 交易目标检查:" echo " - 铸造策略: $POLICY_ID" echo " - 目标地址: $ADDRESS" echo " - NFT名称: $NFT_NAME ($NFT_NAME_HEX)" # 检查余额充足性 local BALANCE=$(cardano-cli query utxo --address $ADDRESS --testnet-magic 1 | jq '[.[].value.lovelace] | add') if [ "$BALANCE" -gt 1000000 ]; then echo "✅ 余额充足: $((BALANCE / 1000000)) ADA" else echo "⚠️ 余额不足: $((BALANCE / 1000000)) ADA" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" }
3. 性能优化建议
# 性能监控函数 monitor_performance() { echo "📊 性能监控" # 节点性能 echo "🖥️ 节点性能:" docker stats cardano-node --no-stream --format "table {{.CPUPerc}}\\t{{.MemUsage}}\\t{{.NetIO}}\\t{{.BlockIO}}" # 磁盘使用 echo "" echo "💾 磁盘使用:" docker exec cardano-node df -h /data/db # 同步状态 echo "" echo "🔄 同步状态:" local TIP=$(cardano-cli query tip --testnet-magic 1) echo " 同步进度: $(echo "$TIP" | jq -r '.syncProgress')%" echo " 当前纪元: $(echo "$TIP" | jq -r '.epoch')" }
总结与后续学习
你已经掌握的技能
通过完成这个教程,你已经学会了:
- 🏗️ 基础设施搭建
- Docker环境配置
- Cardano节点部署
- 测试网络连接
- 🔐 密码学基础
- 公私钥对生成
- 数字签名原理
- 地址生成机制
- 📜 智能合约基础
- 策略脚本编写
- 时间锁定机制
- 原生代币铸造
- 💼 交易处理
- UTXO模型理解
- 交易构建和签名
- 网络提交流程
- 🎨 NFT标准
- CIP-25元数据格式
- 版税设置(CIP-27)
- 资产命名规范
以上这份详细的指南涵盖了从环境搭建到NFT铸造的完整流程,包含了故障排除、最佳实践和进阶学习路径。通过实际操作,你已经掌握了Cardano NFT开发的核心技能。
歡迎留言回复交流。
Log in to reply.