🔥 BRAVE 質押池已上線 — 在錢包搜尋 BRAVE 即可委託

Cardano 测试网 NFT 铸造入门教程

    • #32128

      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开发的核心技能。

  • 抱歉,回覆主題必需先登入。