Atomical协议学习
本文主要来自Atomical官方文档的翻译和摘取,时间:2024.1
<!--StartFragment-->
本文主要来自Atomical官方文档的翻译和摘取,时间:2024.1
1. 协议基础
Atomicals协议文档:https://docs.atomicals.xyz/
Indexer 解析器:https://github.com/atomicals/atomicals-electrumx
铸造工具:https://github.com/atomicals/atomicals-js
相关交易市场:
https://atomicalmarket.com/
[https://satsx.io/](https://satsx.io/marketplace/atomicals/ft/sort?page=1)
https://bitatom.io/
社区的ElectrumX服务端点:https://docs.atomicals.xyz/reference-and-tools/electrumx-api-and-public-endpoints
简介:
Atomical是基于UTXO的,比特币网络上的NFT,不需要indexer就可以工作。Atomicals 是可自证、易于验证的。
Atomical的目标是描述数字对象(注意它是对象,不是特指NFT)。静态或动态对象都可以。
Atomical的理念是把在传输时,验证UTXO的整个历史。即使一个atomical被转手了10000次,产生的数据大小也仅有2.5MB (250byte*10000)。所以它是自证明的,不依赖于第三方服务和indexer。
"No transaction history, not your digital object"
常见用法:\
- NFT
- ARC20
- Collections Container
- Realm Names
标识
Atomical的标识有3种:\
- Atomical Number:从0开始的顺序字符,表示atomical铸造的顺序,是个短的简单的表达法。
- Atomical ID:是Atomical的唯一标识符。是铸造它的txid和输出的序号, <txid>i<index> 例如:a14e65573ff32b95b91a0ed8367feec64125e5f4ff44d9901002b262da959e6di0 i不是16进制里的数字,所以一定不会认错。上面表示a14e65573ff32b95b91a0ed8367feec64125e5f4ff44d9901002b262da959e6d是txid,i是分隔符,0表示第一个output。
- REF,是把Atomical ID用Crockford-base32 编码显示的结果,可以有更丰富的字符集。
动态对象
将一个或多个文件或变量添加Atomical的每次交易的 UTXO 的支出中,indexer会计算并展示最终的状态。
数据可以是key-value,也可以是文件,比如PDF,图片等。
针对每个Atomical,在转移,更新等操作时,都会验证它的全部历史,从而获得它的最新状态。\
- NFT
铸造:
Atomical通过Taproot支出脚本的两步提交和揭示方案创建,通过Atomical信封+"m"表示的铸造操作来铸造。交易的输出中承诺了被提交的数据或文件。然后数据被包含在一个支出脚本中,以揭示内容,这可以是一个或多个文件,包括任何类型的内容,如图像、文本或任何媒体。
更新:
除了不可变的内容和文件之外,Atomicals 数字对象支持在使用由字母 "u" 表示的更新操作后的任何时间附加任意数量的文件和状态。方法与铸造类似。
由于不可变存储的特性,每个文件的修订历史都被永久存储,以便能够准确回放状态变化。
转移:
一旦铸造完成,Atomical 可以像普通比特币一样通过任何类型的交易进行转移,包括 Taproot (P2TR)、SegWit、Multisig 和传统地址 (P2PKH)。
强烈建议使用支持 Atomicals 的钱包,在使用不支持 Atomicals 识别的钱包时,用户如果不小心可能会将其发送给错误的接收方。
转移的规则:\
- Atomical 的身份从交易中的第 i 个输入流向第 i 个相应的输出。如果交易中的输出少于 i 个,则 Atomical 的身份总是流向该交易中的第一个输出。
- 如果在同一笔交易中花费了多个 Atomicals,并且每个 Atomical 花费都没有足够的相应输出(例如,有2个输入,但只有1个输出),那么会发生这样的情况:这些多个 Atomicals 将流向第一个输出,从而在同一UTXO上印刻了多个 Atomicals!这并不是一个大问题,实际上,这是一个有用的特性,可以将多个 Atomicals 关联到单个 UTXO 上。
- 我们必须提供一种方法来"分裂"或提取任何单个 Atomical,通过 Atomical Id 进行引用。以下是为此目的提供的 'Splat' 协议操作。提取操作接受一个数组:将当前输入中的 Atomical Id 强制放入第一个输出。这就是能够从单个 UTXO 中拆分出任意数量的 Atomicals 所需的全部操作。如果携带的输入与 Atomical Id 不相关(由于开发者错误),那么它被视为"无操作"。
注意,转账规则也适用于最初铸造的 Atomical。例如,如果在输入位置 i 处铸造了一个 Atomical,那么该 Atomical 的标识将被印在第 i 个输出上,除非输出较少,或者碰到了一个无法花费的OP_RETURN,在这种情况下,标识始终将被印在第一个输出上。请注意,可以在单个交易中铸造多个Atomical,如果输出数量少于相对于输入数量所需的数量,则在没有匹配的第 i 个输出的情况下,Atomical将分配给第一个输出。\

SWAP
下面是一个swap的例子,input1是买家输入的,input2是卖家输入的,把7771卖给买家。\

Splat
注意分离出来的顺序。\

\
2 . ARC20
ARC20用聪来代表token,它们可以像普通比特币那样组合和拆散。
ARC20的名字先到先得,一旦被注册,后续重名的就无效。
ARC20的mint有直接mint和去中心化mint两种。
2.1 mint
2.1.1 去中心化mint
初始化
去中心化mint的初始化包括如下内容:\
- ticker(名字)
- per mint award (每次mint获得多少),
- total mints allowed (总共可以执行多少次mint),
- start block height(开始mint的块高),
- metadata
例如:名字:myticker123,每次允许mint :1,000, mint次数总量:10,000,从块810,000开始mint,使用 image.jpg 甚至metadata(例如描述、链接).
npm run cli init-dft <tick> <per_mint_amt> <mint_count>
<start_height> metadata.json
Optional flags:
--mintbitworkc=<prefix>
--satsbyte=<number>
初始化时还可以加一个可选的mintbitworkc参数。它要求minter提供一个工作量证明前缀,来表示自己付出了一定工作量。例如--mintbitworkc="777",表示minter要计算出开头为777的证明。
--satsbyte=<number>是为每byte愿意支付的gas fee(聪计价),不是Atomical协议的部分。
mint
npm run cli mint-dft <tick>
Optional flags:
--satsbyte=<number>
2.1.2 Direct Mint
在一次交易里把全部的都mint出来。每一聪代表一个币。
npm run cli mint-ft <tick> <total_supply> metadata.json
Optional flags:
--satsbyte=<number>
例如,要铸造供应量为 100,000,00000 的代币,只需创建一个恰好包含 1 个完整比特币的输出(因为 1 BTC = 100,000,00000 Satoshis)。使用直接铸造模式的一个优点是,必须提供所需数量的比特币来证实铸造的总供应量;这大大减少了不诚实的行为者凭空打印代币的情况。
metadata可以直接写进铸币交易。可以用permanent-file-storage来保存图片,再把图片ref进去。
{ "name": "", "desc": "", "image": "atom:btc:dat:<location of store-file data>/image.png", "decimals": 0, "links": { "x": { "v": "https://x.com/..." }, "website": { "v": "https://..." }, "realm": { "v": "atom:btc:realm:myrealmname.subrealm" }, "discord": { "v": "https://discord.gg/..." } }, "legal": { "terms": "" } }
metadata里没有任何字段是必须的,也可以采用任何形式,只要是json就行。
2.2 转账\
- 所有的input应该完全,干净的进入到output。如果output不够,或者某个output过多会导致膨胀(比如多个输入,其中一个有atomical,其他的没有,就可能导致output膨胀),此时多余的余额会被烧掉。 例如下图,1932、7920、0542 Atomical,按FIFO顺序分配,同时跳过不可花费的输出 (OP_RETURN)。第一个输入 NFT 从输出 0 开始分配,然后每个后续 NFT 输入分配给下一个可用输出。 同一 UTXO 输入的多个 Atomical 按atomical_id 排序。由于“1932”在“7920”之前,因此它排在第一位。

\
- 在无法完全(或“干净”)分配输出的情况下,算法会尝试从输出 0 开始分配所有 ARC20 令牌,无论是否已分配另一个 ARC20。做出这一决定是为了在开发人员或钱包意外未能跟踪多个输入 ARC20 的情况下给予宽大处理 - 换句话说,将“尽最大努力”尝试进行补偿和分配 (没看懂,为什么7920不被分配进第2,3个output? 为什么第3个output没有包含600个0542?)\
\

\
\
- 当输出中没有足够数量的聪来容纳输入值时,原子分配不足。结果是多余的输入值被烧毁——或永久销毁。上面的例子显示 Atomical 86a1 的输入值为 2600,但输出中只有 1600 Satoshi,这实际上导致 1000 个单位的 Atomical 86a1 被销毁。

\
- 当预期输出中的 Satoshis 过多并且原子的输入没有足够的剩余余额时,原子的分配会被错误地分配。上面的例子显示 Atomical 86a1 的输入值为 2600,第一个输出有 600 个 Satoshi,但第二个输出有 2100 Satoshi,这无法与 Atomical 86a1 抗衡,因为只剩下 2600 - 600 = 2000 个单位的 Atomical 剩余分配。尝试分配会导致供应膨胀,因此被视为无效。结果是 2000 单位 Atomical 86a1 被永久摧毁

2.3 swap
步骤 1. 卖方签署包含 ARC20 Atomical 9810 的第二个输入和第二个输出,以接收 5000 Satoshis。签名SIGHASH_SINGLE | ANYONECANPAY允许买方添加额外的资金输入以及接收 ARC20 代币的接收地址。
步骤 2. 买方在第一个输入中添加资金输入,并添加第一个输出以接收 ARC20 Atomical 9810 值。签署即表示SIGHASH_ALL买方承诺所有输入和输出。\

2.4 指数
这个功能在文档里显示还未开发完。
由于粉尘限制,使用比特币在单个 UTXO 中传输少于 546 聪是不可能的,因此不可能以低于 546 面额的 ARC20 代币进行传输,这在实际种不太合适。
因此考虑通过ST操作符表示进行指数缩放(有放大也有缩小),表示10的几次方。\

\

\

\

当没有SP操作符,但是有指数时,用最大的指数。\

\
3 . 容器(Collection containers)
这是一种特殊的Atomical对象,专门用来作为NFT的集合。容器名字可读,表示NFT和metadata的集合。容器名字以#开头。可以向容器里添加物品,也可以永久封住,不做任何改变。
Collection的格式:大部分的field都是可选的。
{ "name": "Collection Name", "desc": "Collection description", "image": "atom:btc:dat:<location of store-file data>/image.png", "legal": { //建议要有 "terms": "...", "license": "CC" }, "links": { "x": { "v": "https://x.com/..." }, "website": { "v": "https://..." }, "discord": { "v": "https://discord.gg/..." } }, "attrs": [ { "name": "bodyarmor", "desc": "Type of body armor", "type": "string", "values": [ "metal", "leather", "field" ] }, { "name": "headcovering", "desc": "The type of head covering", "type": "string", "values": [ "bandana", "helmet", "scarf", "baseball cap" ] }, { "name": "stamina", "desc": "The stamina of the hero", "type": "integer", "min": 0, "max": 10 } ], "items": { "0": { "id": "84718b469c40b1bcc7cb324b8e24e4e442f88cd913687ea2bc7b3e79d4fc4fdei0", //必须 "n": "Some Name", "a": [ //必填,代表attributes.它是一个数组,里面每个值代表“attrs”里定义的每一项的值。 0, 3, 8 ] }, "1": { "id": "1070d7c98304bf5ac9a5addceb13e0ce4e840641f82d71d84cebbdac427c1f4fi0", "n": "Another Name", "a": [ 1, 2, 10 ] }, "Signature Series 1": { "id": "14a0d7c98304bf5ac9a5addceb1de0ce4e840641f82d71d84cebbdac427c1fc3i0", "n": "Special", "a": [ 2, 3, 7 ] } } }
如果你的集合没有attr等复杂结构,可以用下面的最简单形式:
{ "name": "Collection Name", "image": "atom:btc:dat:<location of store-file data>/image.png", "items": { "0": { "id": "84718b469c40b1bcc7cb324b8e24e4e442f88cd913687ea2bc7b3e79d4fc4fdei0" }, "1": { "id": "1070d7c98304bf5ac9a5addceb13e0ce4e840641f82d71d84cebbdac427c1f4fi0" }, "9999": { "id": "14a0d7c98304bf5ac9a5addceb1de0ce4e840641f82d71d84cebbdac427c1fc3i0" } } }
可以通过以下命令在链上存储图片,并生成Collections.
// first store the desired main image on chain (see section below) npm run cli store-file ./path/to/image.png image.png --satsbyte=10 Success sent tx: db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461f { "success": true, "data": { "commitTxid": "b57bad8c0b7f58a552574fafc16b6efbbb3bf966b9ccfb24f03580f9462b5997", "revealTxid": "db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461f", "dataId": "db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461fi0" } } // We will use the dataId above to reference the data on chain // ie: atom:btc:dat:db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461fi0/image.png // Store the collection metadata with the following command: npm run cli set-container-data #mycollection-name collection.json --satsbyte=10
容器的一个常见用法是作为NFT集合的容器,进行DMINT(Decentralized Mint)。下面是DMINT的方法:\

4. DMINT
DMINT包含四个步骤:\
- 准备Collection的所有数据(各个NFT的数据,如图片等,会公开给社区)。
- 配置容器(Collection链上铭刻)。
- 验证 NFT item(是Mint NFT里的一个动作)。
- Mint NFT(用户操作)
项目方负责1-3, 要mint的用户最终把NFT mint出来。这样上传图片,验证NFT所需要消耗的gas fee,就都是由minter来承担。
整个过程挺常规的,不细说了。\
5 . Realm Names
就是域名,did
一个人类可读的名字,关联到地址或者某个资源,可以转让。
Realm Names必须以"+"开头。
命令:
npm run cli mint-realm "myrealmname"
5.1 支付
可以在支持Atomical的钱包里,通过Realm names进行转账。
+[inbox]@[realm_name] + 总是出现,表示这是个Atomical Realm inbox: 表示该realm_name下的收款账号。一个realm_name可以有多个收款账号 @ 分隔符,必须有 realm_name: 注册的realm_name 例如: +hello@samplerealm +main@samplerealm +support@samplerealm
关于inbox的定义:
{ "paynames": { "delegate": "atom:btc:id:<atomical_id>/paynames", "hello": { "delegate": "atom:btc:id:<atomical_id>/paynames/hello" }, "main": { "types": { "btc": { "value": "bitcoin btc address" }, "ltc": { "value": "litecoin ltc address", "notes": "optional notes", "instructions": "optional instructions" } } } } }
5.2 子领域
应用思考:https://news.marsbit.co/20231118130551840807.html
拥有realm name, 就可以在它下面注册子名字,跟ENS的概念一样。\

在Realm,或subRealm的结构里,在“subrealms"名字空间里定义规则。
定义规则:
Step1:定义规则:
规则如何定义的描述:https://docs.atomicals.xyz/rules-subrealms-and-dmint
下面是一个例子:这个文件命名为rules.json
{ "subrealms": { "rules": [ { "p": "^[a-z0-9]{6,64}$", "bitworkc": "8888.8" }, { "p": ".*", "o": { "0014383fb23c5b1b40025b0e1dd3e310d6e1ee6d316e": { "v": 1000 } } } ] } }
上面规则文件表示两条规则:\
- 任何人都可以在完成bitworkc:8888.8(大概1-2分钟cpu计算)的情况下,都可以免费mint 6-64长度的,包含a-z,0-9字符的sub realm.
- 发送给0014383fb23c5b1b40025b0e1dd3e310d6e1ee6d316e 1000 聪以上,就可以mint任意长度的sub realm。
Step2:
提交rules上链,使其active。下面的 +mycoolrealm是之前申请的realm。
yarn cli enable-subrealms +mycoolrealm file.json --satsbyte=100
Mint subrealm\
- 用以下命令查询要申请的subrealm是否存在,以及要申请的名字是否合法 yarn cli realm-info mycoolrealm.nicesubrealm
- 用以下命令完成subrealm的申请 yarn cli mint-subrealm +mycoolrealm.nicesubrealm --satsbyte=100
- 如果需要支付,用下面命令完成支付 yarn cli pending-subrealms
. Bitwork挖矿
就是不断换参数(nonce)生成新的交易hash,看交易hash的前几位是否达到了要求。
可以微调,例如要求前缀是 7777.10,这里7777就要求前面几位必须是7777,.后面的10表示第5位必须是第10个字符以上,即a,b,c,d,e,f
示例:
https://mempool.space/testnet/tx/000005970d1d9ac218be8e6f78cfda667e4112a63f2fb0b7b000a8cb2d0dded0
他的难度就是00000为前缀,然后具体参数需要解码里面的P2TR 信息解码。比如61746f6d 用十六进制解码后则是 atom,646d74 解码后则是Operation 中的 dmt ,然后 payload 信息,需要使用 cbor方法解码,解出来是
node cbor.js
{
"args": {
"time": 1703318358,
"nonce": 8083950,
"bitworkc": "00000",
"mint_ticker": "atom"
}\
7. 其他
7.1 递归和引用
{ // Any Atomical data above... "ctx": { // Implied to always get the latest data for an atomical: "resourcename": "atom:btc:id:<atomical_id>/mydata", // Gets permanent data file image.png at the dat_id "icon": "atom:btc:dat:<dat_id>/image.png", // Gets the minted "name" property for a collection "collection-name": "atom:btc:container:<dat_id>$name", // Gets the latest version of import.js located at a realm name "assetbuilder.js": "atom:btc:realm:<realm_name>/import.js", }, // Another other Atomical data ... }
Atomicals Universal Resource Name (URN) 定义是:
atom:<chain>:<ref_type>:<identifier>[$ or /[<file>]]
ref_type:"id", "container", "realm", and "dat"
identifier,对不同ref_type来说,是不同的内容。如对dat来说,是reveal location of the immutable data,即不可变数据的地址。
其中:$表示mint时的data,/表示最新的data。注意对ref_type为dat的来说,无论用$还是/,返回内容都一样
例如:\
- atom:btc:id:<atomical_id> atom:btc:id:14a0d7c98304bf5ac9a5addceb1de0ce4e840641f82d71d84cebbdac427c1fc3i0 表示一个atomical id
- atom:btc:id:<atomical_id>$ 表示这个atomical_id最初mint时提供的json
- atom:btc:id:<atomical_id>$info.pdf 这个atomical_id最初mint时的info.pdf文件
- atom:btc:id:<atomical_id>/ 获取最新数据
- atom:btc:id:<atomical_id>/image.png@[txid or version number or blockheight] 获取某个版本的数据
- atom:btc:container:<containerName>$ 获取container在mint时的数据
...
7.2 链上永久保存图片
// immutably store an image on-chain to reference in the container metadata npm run cli store-file ./path/to/image.png image.png --satsbyte=10 Success sent tx: db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461f { "success": true, "data": { "commitTxid": "b57bad8c0b7f58a552574fafc16b6efbbb3bf966b9ccfb24f03580f9462b5997", "revealTxid": "db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461f", "dataId": "db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461fi0" } }
可以用db8a761ed493627138c5733071558c4caa65912c5cba3e1061c02d6d7933461fi0来引用到这种图片,这就是它的atomical id
<!--EndFragment-->