tokenId 只在一份合约里唯一。真正可定位的身份至少需要 chain id、合约地址与 tokenId。
Ethereum / ERC standards / Lesson 15.02
余额不够时,怎样给每一件资产一个身份?
ERC-20 只需回答“某地址有多少”。当每一件资产都要被单独识别时, 我们还必须回答:它是哪一件、现在归谁、谁能替 owner 移动, 以及目标合约会不会正确接收。ERC-721 就是这套公共语法。
1 / Ethereum
0x721…A11
0xA11CE…
IDENTITY = CHAIN × CONTRACT × ID 721
“拥有”是合约状态,不是图片属性。ownerOf、授权记录和转移规则共同决定谁能做什么。
safeTransferFrom 的“safe”是接收回调。它防止误投给不兼容合约,却不证明资产有价值或接收方可信。
01 / FROM QUANTITY TO IDENTITY
同样拥有 1 个,为什么还不够?
ERC-20 记录数量;ERC-721 必须指出“具体是哪一件”
假设 Alice 有 1 张演唱会门票,Bob 也有 1 张。若只看余额,两人完全一样; 但 Alice 的票可能是 A 区 1 排 1 座,Bob 的票是 C 区 20 排 18 座。 两件资产不能只用数量表达,因为它们的身份、属性和权利都可能不同。
余额回答多少
balanceOf(Alice) = 1 只说明 Alice 有一件,没有告诉我们是哪一件。
tokenId 指向哪件
ownerOf(42) = Alice 把具体编号与当前 owner 连接起来。
标准统一怎样问
钱包和市场无需认识每个项目的内部代码,只要按 ERC-721 接口查询与操作。
“非同质化”不是说两件东西永远无法交换,也不是说它们一定昂贵,而是说系统在记账时 保留了每个 token 的独立身份。即使两张门票图片相同、价格相同, 只要 tokenId 不同,合约就能分别指定 owner、授权和元数据。ERC-721 提案把这类 独立 token 的查询、转移、授权与事件统一成标准接口。[1]
02 / THE IDENTITY TUPLE
#42 不是完整身份,只是局部编号
相同 tokenId 可以同时存在于无数合约,也可以存在于不同网络
tokenId 是一个 uint256。规范没有规定它必须从 1 连续递增,
也没有规定 #1 比 #42 更早或更稀有。它只需要在这份 ERC-721 合约的
有效 token 集合里标识一个独立 token。
在实际产品里还应明确网络/chain。只报“#42”或只看名称与图片,都不足以验证身份。
Interactive model 01
两个都叫 #42,它们是同一枚吗?
- chain
- 1
- contract
- 0x721A…A11
- tokenId
- 42
- chain
- 1
- contract
- 0x721A…A11
- tokenId
- 42
三项全部相同,才指向同一份链上身份记录。
名字、symbol 和图片为什么都不能当身份?
合约名称与 symbol 属于可选元数据扩展,其他合约可以使用同样的文本;图片也能被复制或 指向同一文件。验证 provenance 时,应从可信入口取得网络与合约地址, 再核对 tokenId、铸造与转移历史。界面上的“蓝勾”、集合名或缩略图只是额外线索, 不是协议层身份。
可以重复
规范没有全局名称注册表;仿冒合约可以复制名称和 symbol。
可以复制
媒体文件不是 token 本身;多枚 token 也可以指向完全相同的图片。
必须核对
可信合约地址加 chain id,再配合 tokenId,才形成可查询坐标。
03 / OWNERSHIP AS STATE
ownerOf 看具体一枚,balanceOf 数一个地址
两种查询从相反方向观察同一套所有权状态
ERC-721 同时提供两种观察方式:ownerOf(tokenId) 从 token
找 owner;balanceOf(owner) 从 owner 数有效 token 的数量。
二者必须描述同一个世界,不能各说各话。
因而 ownerOf(42) = Alice,ownerOf(77) = Bob,
balanceOf(Alice) = 2。balance 仍然有用,但它不再足以定位资产。
- 每个当前存在的 tokenId 恰有一个非零 owner。正常状态下同一个 ERC-721 token 不能同时属于两人。
- 一个地址可以拥有零枚、多枚或很多枚。非同质化不等于“每人只能有一枚”。
- balance 等于该地址当前拥有的有效 token 数量。转入加一,转出减一;铸造与销毁也改变 balance。
- 无效 tokenId 的 ownerOf 必须失败。返回零地址会混淆“从未铸造”“已经销毁”和“当前 owner”。
- 零地址不能作为正常 owner。
balanceOf(address(0))也必须失败。
04 / LIFECYCLE AND EVENTS
铸造、授权、转移、销毁:一枚 token 的四幕
mint 和 burn 不是核心函数名,却能通过 Transfer 事件表达状态边界
ERC-721 规定的是可观察结果,不规定你的外部铸造函数必须叫 mint,
也不要求一定提供 burn。常规铸造应发出从零地址来的
Transfer,销毁时发出转往零地址的 Transfer;
中间的普通转移则从旧 owner 指向新 owner。EIP-721 另有一个历史性例外:
合约创建期间分配 token 时,可以不逐枚发出 Transfer。[1]
不存在 → Alice
Transfer(0x0, Alice, 42)
建立 owner 并把 Alice 的 balance 加一。
Alice → Market
Approval(Alice, Market, 42)
owner 不变,只增加单枚移动权限。
Alice → Bob
Transfer(Alice, Bob, 42)
owner 改写,余额调整,单枚授权失效。
Bob → 不存在
Transfer(Bob, 0x0, 42)
移除当前 owner,Bob 的 balance 减一。
Interactive model 02
逐步改写 token #42 的状态
Owner · Alice
Approved · none
Alice balance · +1
Transfer(0x0000…, Alice, 42)
从零地址转入表示创建;规范不要求公开函数必须叫 mint。
事件不是第二份权威状态
Transfer、Approval 与 ApprovalForAll
写入交易日志,便于钱包和索引器重建历史;合约执行时真正读取的是存储中的当前 owner 与授权。
同一笔交易若最终回滚,日志也会一起消失。索引器还要知道:token 转移会使单枚 approval
失效,规范并不要求再额外发一条“清零 Approval”事件。[1]
05 / THE STANDARD SURFACE
九个核心函数、三个事件,外加 ERC-165
核心接口负责查、转、授权与留证;元数据、枚举、铸造都不在这一层
钱包不需要知道合约内部用了怎样的 mapping,只需知道它是否支持
0x80ac58cd,再按统一函数签名查询和调用。ERC-721 核心接口
继承 ERC-165,因此能力检测也是标准的一部分。[2]
观察状态
balanceOf(owner)
ownerOf(tokenId)
getApproved(tokenId)
isApprovedForAll(owner, operator)
转移与授权
transferFrom(from, to, id)
safeTransferFrom(from, to, id)
safeTransferFrom(from, to, id, data)
approve(to, tokenId)
setApprovalForAll(operator, approved)
公开通知
Transfer(from, to, tokenId)
Approval(owner, approved, tokenId)
ApprovalForAll(owner, operator, approved)
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(
address from, address to, uint256 tokenId, bytes calldata data
) external;
}
哪些常见函数其实是可选或项目自定义?
name · symbol · tokenURI
Metadata 扩展
接口 ID 0x5b5e139f。基础 OpenZeppelin ERC721 实现通常包含它,但协议层仍是可选扩展。
totalSupply · tokenByIndex · tokenOfOwnerByIndex
Enumerable 扩展
接口 ID 0x780e9d63。链上枚举有额外存储与 Gas 成本,不是 ERC-721 核心要求。
mint · safeMint · burn
发行与销毁策略
外部函数名、调用权限、供应上限和价格由实现决定;标准只定义相关状态与事件语义。
royaltyInfo · pause · upgrade
其他能力
版税、暂停和升级来自其他标准或项目逻辑,不能因为合约是 ERC-721 就自动假定存在。
interface ID 是怎样来的?
一般情况下,函数签名先经过 Keccak-256,取前 4 字节成为 selector;接口里各函数
selector 再按位 XOR,得到 ERC-165 interface ID。只 XOR
该接口自身定义的函数,不包含继承函数:因此 ERC-721 的
0x80ac58cd 不包含 supportsInterface,Metadata
与 Enumerable 的 ID 也不包含核心九函数。事件、返回类型、参数名以及
view/payable 等修饰不参与计算。ERC-4906 只新增事件,
所以 0x49064906 是规范直接指定的固定值,不是事件 selector 的 XOR。
supportsInterface 只声明“我声称支持什么”,并不证明实现正确、安全或真实。
06 / TWO LEVELS OF PERMISSION
approve 给一枚钥匙,operator 拿一串钥匙
所有权没有转移,但被授权者已经获得调用转移函数的能力
市场合约若要在成交时交付 token,不一定先成为 owner。Alice 可以只批准它移动 #42,也可以让它成为自己在这一份 ERC-721 合约内的全局 operator。 两种授权范围差异巨大,界面却常只写一个模糊的“Approve”。
owns #42 · #77 · #108
getApproved(42)
→ Carol
只可移动 #42;任何转移后自动失效。
isApprovedForAll(Alice, Market)
→ true
可移动 Alice 在这份合约中的全部 token,直至 Alice 撤销。
Interactive model 03
谁能移动 Alice 的 token #42?
current owner
五个容易被忽略的授权细节
- 单枚授权不等于所有权。Carol 能调用转移,但
ownerOf(42)在转移前仍返回 Alice。 - approved 不能凭单枚权限继续批给别人。调用
approve的权限属于 owner 或 owner 的全局 operator。 - 任何转移都会清除该 token 的单枚 approval。即使 Alice 把 #42 转给自己,也应按转移语义清除。
- operator 关系绑定 owner 与合约。Alice 给 Market 的权限不涵盖其他 ERC-721 合约,更不涵盖整个钱包的全部资产。
- 旧 owner 的 operator 关系不会因一枚 token 转出而自动删除。它对 Alice 留下或以后获得的该合约 token 仍有效,直到撤销。
07 / TRANSFER AS A STATE TRANSITION
一次转移,至少改写三类状态
先验证 token、from、to 与 caller,再清授权、改余额、换 owner、发事件
从 Alice 把 #42 转给 Bob,不是“把文件复制过去”,而是一次原子的合约状态转换。 任何前置条件失败,整笔调用回滚;全部成立时,所有权、双方余额、单枚授权和事件日志 才共同形成新结果。
核对对象
token 存在;from 是当前 owner;to 不是零地址。
核对 caller
owner、单枚 approved 或当前 owner 的 operator 三者之一。
清单枚授权
#42 原有的 getApproved 结果失效,不随 token 交给 Bob。
改写账本
Alice balance −1;Bob balance +1;ownerOf(42) → Bob。
留下日志
Transfer(Alice, Bob, 42) 供外部索引与追踪。
权威当前值
ownerOf 与授权查询来自当前状态;其他合约在执行中依此决定是否允许操作。
可索引历史信号
钱包、市场和分析工具监听事件更新界面;交易若回滚,状态与日志一并撤销。
transferFrom 与 safeTransferFrom 共同保证什么?
两者都必须检查 token、from、to 和 caller,并在成功时完成同样的核心所有权转换。
差别只出现在目标地址是合约时:safeTransferFrom 还要求接收者完成一次
标准回调握手;transferFrom 不问,因而可能把 token 送进不会处理或
无法转出的合约。下一节把这条分叉单独展开。
08 / THE RECEIVER HANDSHAKE
“安全”不是鉴定好坏,而是确认合约会接
EOA 直接接收;合约必须返回 0x150b7a02,否则整个 safe transfer 回滚
地址可能属于人控制的 EOA,也可能是一段合约代码。EOA 没有回调函数;
对合约地址,safeTransferFrom 会在更新 ERC-721 状态后调用
onERC721Received。正确返回 selector 才表示接收。[8]
authorized?
检查 owner、approved 或 operator,并验证 from 与 to。
owner[42] = to
清授权、改余额、改 owner、发 Transfer。回调此时已能看到新 owner。
to.onERC721Received(...)
msg.sender 是 NFT 合约;data 原样传给接收方。
return 0x150b7a02
正确返回则提交;缺失、抛错或错误返回会让前面全部变化一起撤销。
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// 接受时必须返回:
return IERC721Receiver.onERC721Received.selector; // 0x150b7a02
Interactive model 04
同一枚 #42,换一种目标与方法会怎样?
safeTransferFrom(Alice, Vault, 42)
→ Vault.onERC721Received(...)
→ return 0x150b7a02
Vault 明确表示会接收,ownerOf(42) 最终变为 Vault。
回调里的 operator 到底是谁?
回调参数 operator 是本次 safe transfer 的发起者,
可能是 owner、单枚 approved,也可能是全局 operator。它不是
setApprovalForAll 意义下角色的保证。回调中的 from
是转移前 owner,安全铸造时通常为零地址;data 的格式由调用方与接收者约定。
09 / METADATA IS A DESCRIPTION LAYER
token 在链上,图片通常不在 token 里
tokenURI 返回的是元数据 URI;JSON 再告诉界面名称、描述与媒体地址
ERC-721 的核心所有权接口即使没有图片也能工作。可选 Metadata 扩展增加
name()、symbol() 与 tokenURI(id)。
tokenURI 返回一条 URI;钱包随后读取 JSON,JSON 才可能通过 image
指向媒体。[1]
链上状态
chain、contract、tokenId、owner 与 approval,可由节点验证。
元数据
tokenURI 指向的 JSON;可能可变、不可用或由管理员更新。
媒体内容
图片或动画字节;位置寻址与内容寻址提供不同保证。
权利约定
版权、门票效力或现实承诺取决于许可证、合同与适用规则。
HTTP、IPFS、data URI 分别保证什么?
位置寻址
部署简单,但域名、服务器、路径或权限变化会影响访问;同一 URL 的内容也可能被替换。
内容寻址
CID 可校验字节是否相同,却不等于永久有人保存或提供内容;pin 与可用性仍需考察。
链上生成或存储
减少外部服务依赖,通常更昂贵;仍要检查合约可升级性与生成逻辑是否可变。
元数据变化,钱包怎样知道要刷新?
ERC-721 核心没有定义更新通知。可选 ERC-4906 增加
MetadataUpdate(tokenId) 与
BatchMetadataUpdate(fromTokenId, toTokenId) 事件,
并指定接口 ID 0x49064906。合约一旦声明支持 ERC-4906,
单枚 token 或连续 ID 范围的 JSON metadata 改变时,必须发出对应更新事件;
规范建议 mint、burn,以及 tokenURI 改变但 JSON metadata 未改变时不发
MetadataUpdate。它只改善变化的可发现性,不授予谁可以修改元数据;
权限仍由实现决定。[9]
10 / IMPLEMENTATION AND EXTENSIONS
最小合约很短,真正的风险都在策略里
ERC-721 解决互操作;供应、权限、升级、URI 与业务规则仍要自行设计
生产实现通常复用经过广泛审查的库,而不是从头手写 owner 与 approval mapping。
下面的 OpenZeppelin 5.x 骨架只做三件事:初始化名称、限制铸造者、用
_safeMint 创建连续编号。它可以符合 ERC-721,却没有
totalSupply(),因为 Enumerable 本来就是可选扩展。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract LorePass is ERC721, Ownable {
uint256 private _nextTokenId = 1;
constructor(address initialOwner)
ERC721("Lore Pass", "LORE")
Ownable(initialOwner)
{}
function safeMint(address to)
external
onlyOwner
returns (uint256 tokenId)
{
tokenId = _nextTokenId++;
_safeMint(to, tokenId);
}
function _baseURI()
internal
pure
override
returns (string memory)
{
return "ipfs://REPLACE_WITH_METADATA_CID/";
}
}
逐行看,它做了什么,也没做什么
ERC721("Lore Pass", "LORE")
复用核心与 Metadata
库提供 owner、balance、approval、转移、接收检查、ERC-165 与基础 tokenURI 逻辑。
onlyOwner
显式限制铸造
ERC-721 不规定谁能 mint。本例把权力给合约 owner;真实项目还要评估单点权限与移交流程。
_nextTokenId++
选择一种编号策略
连续编号只是实现选择。规范允许散列、随机分配或业务编号,只要有效 token 身份不冲突。
_safeMint(to, tokenId)
创建并做接收检查
若 to 是合约,会执行 receiver 回调。外部回调也意味着自定义后续逻辑要考虑重入。
_baseURI()
拼接元数据路径
基础实现通常以 base URI 加 tokenId 的十进制字符串形成 tokenURI;资源需返回 JSON。
no Enumerable · no cap · no burn
没有自动附赠的承诺
供应上限、链上遍历、销毁、暂停、升级和版税都需要另外实现并说明权限。
读实现时要特别检查的风险面
谁能增发
检查公开 mint 条件、角色、签名验证、上限与 tokenId 是否可能重用。
谁能改变规则
代理升级、暂停、强制转移、URI 更新与角色管理员可能改变最初假设。
哪里会外部调用
safe mint/transfer 的 receiver 回调可以重入;检查关键状态与业务动作的顺序。
权限是否过宽
operator for all 是常见高权限入口;检查前端提示、撤销路径与签名域。
描述能否变化
base URI、逐 token URI、升级权、服务器与内容固定方式决定展示是否可变。
扩展是否真的实现
用 ERC-165 核对声明,再测试行为;支持 ID 不是正确性与安全性的证明。
11 / CHOOSE BY THE LEDGER
不要按营销名字选标准,要按资产怎样被计数
一维余额、单件所有权、二维余额,分别对应 ERC-20、721、1155
三种标准没有高低之分,它们回答不同的数据问题。先画出你的账本: 账户只需要一个数量、每件都需要独立 owner,还是同一合约要管理许多类型与份数? 数据模型一旦清楚,接口选择往往自然出现。
| 维度 | ERC-20 | ERC-721 | ERC-1155 |
|---|---|---|---|
| 账本核心 | balance[account] |
owner[tokenId] |
balance[id][account] |
| 资产身份 | 合约地址 | 合约地址 + tokenId | 合约地址 + token type id |
| 转移参数 | 数值 value |
具体一个 tokenId |
id + amount,可批量 |
| 授权模型 | 数值 allowance | 单枚 approved + owner-wide operator | owner-wide operator |
| 标准批量转移 | 无 | 无 | 有 safeBatchTransferFrom |
| 典型问题 | 一个地址有多少单位? | 这一个编号当前归谁? | 这个地址拥有这个类型多少份? |
ERC-721 没有标准 decimals,也不把一个 tokenId 拆成份额。外部包装协议可以表达份额, 但那是另一层合约关系,不是 ERC-721 自身能力。
面对任何 ERC-721,用十步把宣传还原成可验证问题
- 确认 chain 与合约地址从可信入口取得地址,不用名称、symbol 或图片代替身份核验。
- 核对 tokenId 与当前 owner直接读取 ownerOf;无效 token 的查询应失败。
- 检查 ERC-165 声明与实际行为核心、Metadata、Enumerable、ERC-4906 分别核对,不能从其中一个推断全部。
- 画出单枚与全量授权读取 getApproved 与 isApprovedForAll,确认 operator 的范围与撤销方式。
- 追踪 mint、transfer 与 burn 权限谁能创建、销毁、强制移动或重用 tokenId?
- 检查供应承诺来自哪里核心标准没有 totalSupply、cap 或“永不增发”;承诺必须由代码、权限和治理共同支撑。
- 沿 tokenURI 检查元数据记录 URI、JSON、媒体位置、内容哈希、可用性与管理员修改能力。
- 识别升级与管理员边界代理、角色、多签、暂停和 URI 更新可能改变当前结论。
- 审查 receiver 与重入路径safe transfer 会执行外部代码;接收成功也不等于以后能取回。
- 把链上所有权与现实权利分开版权、门票、会员与兑付效力要看许可证、合同和适用规则。
12 / THE DURABLE MODEL
把 ERC-721 压缩成一条状态因果链
身份定位状态,授权决定能力,转移改写账本,事件通知外部,元数据解释展示
真正理解 ERC-721,不是背出“NFT 标准”四个字,而是能在任何合约面前分清: 哪些是核心协议保证,哪些是可选扩展,哪些只是项目自己的经济与治理策略。
六题校准
1. 两个合约里都存在 tokenId #42,最准确的说法是?
选择一个答案。
无 JavaScript 时查看答案
答案 B。tokenId 只是合约内部编号;跨链还必须加入 chain id。
2. 哪一个函数属于 ERC-721 核心必需接口?
选择一个答案。
无 JavaScript 时查看答案
答案 C。totalSupply 属于 Enumerable,tokenURI 属于 Metadata;两者都是可选扩展。
3. Alice 把 #42 转给 Bob 后,Carol 原有的单枚 approval 怎样?
选择一个答案。
无 JavaScript 时查看答案
答案 A。任何转移都会清除该 token 的单枚 approval;不要求额外发清零 Approval 事件。
4. safeTransferFrom 把 #42 发给不兼容 receiver 合约,会发生什么?
选择一个答案。
无 JavaScript 时查看答案
答案 B。缺失、抛错或错误返回都会让 safe transfer 整笔回滚。
5. tokenURI 返回 IPFS URI,协议层最稳妥的结论是?
选择一个答案。
无 JavaScript 时查看答案
答案 C。内容寻址帮助验证字节,不自动保证永久存储、版权或可用性。
6. supportsInterface(0x80ac58cd) 返回 true,能证明什么?
选择一个答案。
无 JavaScript 时查看答案
答案 A。ERC-165 是能力声明,不是正确性、安全性或资产真实性证明。
13 / TERMS AND PRIMARY SOURCES
术语与一手资料
把“核心标准”“可选扩展”“官方实现”分开阅读
- tokenId
- ERC-721 合约内部用于标识一个独立 token 的 uint256;不是全网唯一编号。
- owner
- 当前由 ownerOf(tokenId) 返回的非零地址,是协议层所有权状态。
- approved
- 获准移动一个指定 tokenId 的单个地址;任意转移时失效。
- operator
- 获准管理某 owner 在同一 ERC-721 合约内全部 token 的地址。
- safe transfer
- 向合约转移时要求其完成 IERC721Receiver 回调握手的路径。
- interface ID
- 通过 ERC-165 查询合约声明支持哪些标准接口的 bytes4 标识。
- tokenURI
- 可选 Metadata 扩展中返回 token 元数据位置的函数与结果。
- mint / burn
- 创建与销毁 token 的常用实现术语;不是核心接口规定的公开函数名。
一手资料与标准原文
-
01
ERC-721 · Non-Fungible Token Standard
核心函数、事件、状态要求、安全转移、Metadata 与 Enumerable 扩展的规范原文。
-
02
ERC-165 · Standard Interface Detection
supportsInterface、interface ID 计算与检测流程的规范。
-
03
ethereum.org · ERC-721
以太坊开发者文档对非同质化 token、接口与典型用途的官方说明。
-
04
OpenZeppelin Contracts 5.x · ERC-721 Guide
当前官方库的创建、URI、存储与扩展教学。
-
05
OpenZeppelin Contracts 5.x · ERC-721 API
核心实现、扩展、errors、回调与重入注意事项的 API 参考。
-
06
OpenZeppelin v5.6.1 · IERC721.sol
现代 Solidity 形式的九个核心函数与三个事件接口。
-
07
OpenZeppelin v5.6.1 · ERC721.sol
owner、balance、approval、_update、转移与 Metadata 的官方实现。
-
08
OpenZeppelin v5.6.1 · IERC721Receiver.sol
onERC721Received 的参数、返回 selector 与回调语义。
-
09
ERC-4906 · EIP-721 Metadata Update Extension
MetadataUpdate、BatchMetadataUpdate 与 0x49064906 能力标识。
-
10
ERC-6093 · Custom Errors for Common Token Standards
ERC-721 等常见 token 标准的统一 custom error 词汇。
-
11
ERC-2981 · NFT Royalty Standard
版税信息是独立可选标准;不属于 ERC-721 核心,也不强制付款。
-
12
OpenZeppelin v5.6.1 · ERC721URIStorage.sol
逐 token URI 存储、ERC-4906 支持与 MetadataUpdate 事件的官方实现。
The durable model
ERC-721 记录的不是图片,而是一件资产的身份、所有权与行动权限。
当你再次看到一枚 NFT,先写下 chain、contract 与 tokenId;再读取 owner、 单枚 approval 和 operator;然后检查转移与 receiver 回调;最后才沿 tokenURI 进入 JSON、媒体和现实权利。按这个顺序,你看到的是可验证系统,而不是一张缩略图。