Brave
@47c132035ed7a1105a867946b99d67af Cardano研究小组 公开
讨论 Cardano 测试网 NFT 铸造入门教程

Cardano 测试网 NFT 铸造入门教程

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:没有真实价值,仅用于测试

使用官方水龙头

  1. 复制钱包地址
# 显示地址以便复制
echo "请复制以下地址:"
echo "$ADDRESS"
  1. 访问水龙头网站
  2. 申请流程
    • 粘贴你的地址
    • 选择 "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 ADA
  • datumdatumhash:智能合约相关数据,此处为空

备用获取方式

如果官方水龙头不可用:

  1. Discord 水龙头
  2. 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 &lt;< 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 &lt;< 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 &lt;< 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 &lt; /keys/tx.unsigned)
    echo \&quot;  - 交易大小: $TX_SIZE 字节\&quot;
else
    echo \&quot;❌ 交易构建失败\&quot;
    exit 1
fi

构建参数说明

  • --tx-in:指定输入UTXO
  • --mint:指定要铸造的资产数量和名称
  • --mint-script-file:提供策略脚本文件
  • --metadata-json-file:附加元数据
  • --change-address:指定找零地址
  • --invalid-hereafter:设置交易过期时间

交易费用估算

# 从构建输出中提取费用信息
if grep -q \&quot;Estimated transaction fee:\&quot; /dev/stdout; then
    echo \&quot;💰 交易费用已在构建过程中显示\&quot;
else
    echo \&quot;💰 交易费用: 约184,000 Lovelace (0.184 ADA)\&quot;
fi

echo \&quot;\&quot;
echo \&quot;💡 费用说明:\&quot;
echo \&quot;  - 基础费用: ~155,000 Lovelace\&quot;
echo \&quot;  - 元数据费用: ~20,000 Lovelace\&quot;
echo \&quot;  - 脚本执行费: ~10,000 Lovelace\&quot;
echo \&quot;  - 总计: ~185,000 Lovelace\&quot;

第八步:签名和提交交易

理解数字签名

在Cardano中,交易签名确保:

  • 身份验证:证明交易由密钥持有者发起
  • 完整性:确保交易内容未被篡改
  • 不可否认性:签名者无法否认已签名的交易

签名交易

echo \&quot;✍️ 签名交易...\&quot;

# 使用两个密钥签名:支付密钥和策略密钥
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 \&quot;/keys/tx.signed\&quot; ]; then
    echo \&quot;✅ 交易签名成功\&quot;
    
    # 比较文件大小
    UNSIGNED_SIZE=$(wc -c &lt; /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 &lt;&lt; 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 \&quot;\&quot;
echo \&quot;📄 成功报告已生成: /keys/nft_success_report.txt\&quot;
cat /keys/nft_success_report.txt

保存重要文件

echo \&quot;\&quot;
echo \&quot;💾 保存重要文件...\&quot;

# 创建备份目录
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 \&quot;✅ 重要文件已备份到 /keys/backup/\&quot;

# 显示文件清单
echo \&quot;\&quot;
echo \&quot;📁 文件清单:\&quot;
ls -la /keys/backup/

echo \&quot;\&quot;
echo \&quot;⚠️  重要提醒:\&quot;
echo \&quot;   - 请妥善保管私钥文件 (.skey)\&quot;
echo \&quot;   - 私钥丢失将无法控制钱包和NFT\&quot;
echo \&quot;   - 建议将备份文件下载到安全位置\&quot;

进阶操作指南

1. 转移NFT到其他地址

# 准备转移NFT的函数
transfer_nft() {
    local RECIPIENT_ADDRESS=\&quot;$1\&quot;
    
    if [ -z \&quot;$RECIPIENT_ADDRESS\&quot; ]; then
        echo \&quot;❌ 请提供接收地址\&quot;
        echo \&quot;用法: 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')"
}

总结与后续学习

你已经掌握的技能

通过完成这个教程,你已经学会了:

  1. 🏗️ 基础设施搭建
    • Docker环境配置
    • Cardano节点部署
    • 测试网络连接
  2. 🔐 密码学基础
    • 公私钥对生成
    • 数字签名原理
    • 地址生成机制
  3. 📜 智能合约基础
    • 策略脚本编写
    • 时间锁定机制
    • 原生代币铸造
  4. 💼 交易处理
    • UTXO模型理解
    • 交易构建和签名
    • 网络提交流程
  5. 🎨 NFT标准
    • CIP-25元数据格式
    • 版税设置(CIP-27)
    • 资产命名规范

以上这份详细的指南涵盖了从环境搭建到NFT铸造的完整流程,包含了故障排除、最佳实践和进阶学习路径。通过实际操作,你已经掌握了Cardano NFT开发的核心技能。