# 在本地链上理解代币状态与交易确认

原文: https://yishto.com/learn/application-platforms/local-chain-token-lab/

作者: [Yishto](https://yishto.com/about/)

只用本地测试链设计练习凭据，解释ERC-721身份、铸造授权、转移与交易回执，并区分元数据和链上所有权。

首次发布: 2026-11-10
内容更新: 2026-11-10

本地链可以反复创建和清空状态，适合学习合约与交易。案例发放没有价值承诺的练习凭据，只使用开发网络生成的测试账户与教学数据，不使用真实资产或公开网络凭证。

一枚凭据由合约地址和token ID共同定位。token ID为1的记录在两个合约中不是同一枚。先理解身份、授权与确认，再考虑显示图片；图片地址并不能解释所有权怎样改变。

## 本地网络与持久记录

开发链是专门用于本地测试的以太坊客户端环境，通常能预置测试账户、余额和快速出块。具体行为由所选工具配置决定，不把快速确认当成公共网络保证。官方开发网络页列有Hardhat Network，可从其当前入门流程建立工程。

工程记录链ID、RPC端点、编译器版本和部署地址。钱包或脚本连接的链必须与部署链一致，查询前检查合约地址确实有代码。相同地址文本出现在不同链上，不保证是相同合约。

网络重置后原交易、状态与地址映射可能失效。部署记录也要带上本次环境身份，清理教学数据后重新部署并更新客户端配置。不要让旧UI继续显示上一轮凭据，好像链状态仍然存在。

## ERC-721 保证哪些接口

ERC-721定义非同质化token的所有权、余额、授权与转移等接口，并要求相应ERC-165接口支持。元数据和枚举是可选扩展，基础标准不保证存在完整列表查询，也不保证tokenURI指向的内容永久保存。

转移可以由所有者或已授权主体发起。授权单枚token与授权操作员管理全部token具有不同范围，测试时分别验证撤销与转移后的状态。使用安全转移到合约地址还涉及接收接口；不能把所有地址都当成普通外部账户。

链上所有权不自动赋予图片版权或现实权益。本课凭据只是合成记录，不承诺商业权利。元数据如果存放在外部服务器，服务器内容与可访问性独立于链状态，界面应区分链查询失败和资源加载失败。

## 建立可编译的练习工程

从所选开发框架的官方初始化流程创建本地工程，保留生成配置与锁文件。安装OpenZeppelin Contracts 5.x，选择与安装版本兼容的Solidity编译器。来源中的示例使用 `^0.8.24`，不要套用旧版构造器与新包混编。

下面是独立练习合约片段，采用官方ERC721基类与铸造机制，并将铸造限制给部署者。没有使用逐 token URI 存储扩展，也没有公开分发功能。基类仍提供名称、符号与元数据接口，未设置基础 URI 时，这份片段不会给出有效的外部元数据地址。代码未在Solidity工具链实测，读者需要先编译、测试再部署；它不被标记为已运行合约。

```solidity
// Teaching fragment: compile and test before use.
pragma solidity ^0.8.24;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract LabReceipt is ERC721 {
    address public immutable minter;
    uint256 private nextId;
    constructor() ERC721("LabReceipt", "LAB") {
        minter = msg.sender;
    }
    function mint(address recipient) external returns (uint256) {
        require(msg.sender == minter, "minter only");
        uint256 id = nextId++;
        _mint(recipient, id);
        return id;
    }
}
```

创建测试文件，分别用部署者和其他测试账户调用mint。部署者成功，其他账户应回退；两次成功调用产生不同ID，通过ownerOf检查归属。这个版本使用 `_mint`，不检查合约接收者回调，练习只向本地外部测试账户铸造；需要合约接收者时另行设计安全铸造并补测试。

启动本地开发节点，将部署目标明确指向本地RPC，部署后保存地址和链ID。随后连接本地客户端，先只实现链信息、合约地址、所有者查询与提交状态。应用不会因返回一个哈希就显示铸造完成。

## 提交、回执与成功状态

一次交易经历待用户确认、已提交、待回执、成功或失败。用户拒绝签名不是链执行失败，网络超时也不表示交易没被处理。保存交易哈希与链信息，用对应链查询回执，检查状态与合约状态，而不是无限提交相同操作。

本地开发网络可能立即出块，这会掩盖等待和错误界面。通过测试适配器增加延迟与失败路径，确认客户端在回执到达前不显示最终所有者。真实链确认还涉及链重组与确认策略，本地练习不能验证公共网络最终性。

读取状态与写入状态分开。`ownerOf`是查询，mint与transfer改变状态需要交易。返回的token ID要与事件或最终查询对应，日志仅是诊断，不能替代确定的业务确认。

## 离线所有权账本的边界

下面Python模拟唯一铸造与所有者转移，重复ID和非所有者操作被拒绝。它没有EVM、gas、签名、回执、事件、授权操作员或重组，不能称为ERC-721实现。

```python
owners = {}
def mint(token_id, owner):
    if token_id in owners:
        return False
    owners[token_id] = owner
    return True
def transfer(token_id, actor, recipient):
    if token_id not in owners or owners[token_id] != actor:
        return False
    owners[token_id] = recipient
    return True
assert mint(1, "A") and not mint(1, "B")
assert not transfer(1, "B", "C")
assert not transfer(2, None, "C")
assert transfer(1, "A", "C")
print(owners)
```

实际输出 `{1: 'C'}`。第二次铸造ID 1失败，B转移失败，A转给C成功。这个模型只验证简化所有者规则；真实ERC-721还允许合法授权方转移，不能把模型的限制当标准全文。

合约的唯一性来自其状态与铸造规则，不是页面避免连点。测试回退后计数器与所有权没有留下部分变化，使用实际工具验证事务回退。Python返回False不模拟Solidity revert的执行和gas语义。

## 元数据与错误恢复

加入元数据前决定是否需要扩展，明确记录格式与外部内容版本。教学凭据可以只有合成名称与课程ID，不需要借用官方游戏资产。图片失败时仍显示已确认的token身份，不把无法加载图片当作token消失。

交易长时间无回执时检查链连接、账户与哈希，再决定恢复动作。节点重启或重置后更新环境记录；旧请求结果只适用于对应环境。测试账户私钥也不写入公开教程或仓库，使用开发工具提供的临时机制。


测试脚本不要只断言交易调用没有抛错，还要查询最终ownerOf与授权状态。事件索引需要链ID、合约地址和区块位置，重置后清理旧索引；仅靠token ID无法隔离环境。将查询失败、token不存在和错误链分别映射为界面状态，用户才知道是重连还是重新部署。

依赖锁文件记录具体Contracts版本，编译器设置和优化选项与部署记录一致。修改合约后重新编译，不能沿用旧ABI连接新代码；客户端同时保留ABI与部署地址对应关系。

## 练习：验收交易与权限

运行实际合约测试，覆盖未授权mint、连续唯一ID、非所有者转移、授权后转移与撤销。检查零地址或无效token等失败条件，确认失败没有改变已存在的所有权。

客户端模拟拒签、提交后断线、回执失败和节点重置，分别显示恢复动作。记录本地工具版本、编译结果与实际回执；本文只运行Python账本，未声称Solidity编译通过。完成这些检查仍只支持本地教学，公共部署需要独立审查。

## 资料来源与延伸阅读

- [Local blockchain development networks](https://github.com/ethereum/ethereum-org-website/blob/dev/public/content/developers/docs/development-networks/index.md)
- [ERC-721: non-fungible token standard](https://github.com/ethereum/ercs/blob/master/ERCS/erc-721.md)
- [ERC-721 implementation guidance](https://docs.openzeppelin.com/contracts/5.x/erc721)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [让数据库约束参与业务正确性](https://yishto.com/learn/service-data/database-indexes-and-transactions/)
- [从单体到多服务：先设计失败与恢复](https://yishto.com/learn/delivery/distributed-systems-and-recovery/)
