-
Notifications
You must be signed in to change notification settings - Fork 19
NFT跨链操作文档
AntChain Bridge跨链方案支持NFT 在异构链上的跨链,可有效提升资产的流动性。通用的异构链NFT跨链方案大致设计如下图所示:
- NFT跨链方案中,
业务资产合约
为遵循ERC1155标准的NFT资产合约,锚定资产合约
基本遵循ERC1155标准,但根据业务需求对ERC1155标准做了一定的修改及裁剪; - NFT跨链的本质是通过跨链将A链上的资产映射到B链上,业务资产合约负责发起资产映射的跨链请求,锚定资产合约负责接收请求进行相应的资产映射(铸造或解锁)。
- 资产合约发起跨链请求后,
TokenBridge
合约负责将NFT跨链请求接入异构链跨链的核心逻辑。一方面TokenBridge
合约需要针对上层资产跨链请求进行相关资产锁定或映射,另一方面TokenBridge
合约需要根据将跨链请求构造为定制化跨链消息发送给跨链系统合约(SDP合约)。 - 跨链系统合约收到
TokenBridge
合约的跨链消息后,将根据AntChain Bridge系统的跨链原理执行具体跨链逻辑。
NFT跨链流程中主要涉及业务资产合约、锚定资产合约、TokenbBridge合约及跨链系统合约。
- 业务资产合约:将A链上的资产(A链原生资产)跨到B链上时需要调用A链上的业务资产合约。业务资产合约遵循ERC1155标准的NFT资产合约,详见2.1节介绍。
-
锚定资产合约:将B链上的锚定资产跨回A链时需要调用B链上的锚定资产合约。锚定资产合约基本遵循ERC1155标准但有一定出入,方案给出锚定资产合约模板
ERC1155CrossChainMapping
,详见2.1节介绍。 - TokenbBridge合约:位于资产合约(资产合约包括业务资产合约和锚定资产合约)及跨链系统合约(包括AM合约和SDP合约)的中间层,负责打通资产合约到跨链系统合约之间的处理逻辑。方案给出TokenbBridge合约模板,后文中均简称为TB合约,详情见2.2节介绍。
- 跨链系统合约:AntChain Bridge异构链插件链上插件的部分,包括AM合约和SDP合约,具体介绍参见「异构链插件开发手册」。
业务资产合约完全符合ERC1155标准,本方案不作详细介绍。
锚定资产合约基本符合ERC1155标准,在本方案中要求锚定资产合约必须实现ERC1155CrossChainMapping
合约模板,该模板提供事件及方法如下:
类型 | 名称 | 备注 |
---|---|---|
event | ApprovalForAll | |
event | TransferBatch | |
event | TransferSingle | |
event | URI | |
function | balanceOf | 检查单个资产持有者的余额 |
function | balanceOfBatch | |
function | mintBatchByTB | 新增接口,TB合约调用其铸造跨链锚定资产 |
function | safeTransferFrom | 向单个地址转移单个资产 |
function | safeBatchTransferFrom | 向多个地址转移多个资产 |
function | setApprovalForAll | |
function | supportsInterface |
上述事件及方法中,除_mintBatchByTB_
方法外均为ERC1155标准定义的事件或方法
接口:业务资产合约与锚定资产合约的接口基本相同,主要区别在于锚定资产合约新增了一个为了适配TB合约的mintBatchByTB
接口
部署位置:业务资产合约部署在资产原生的来源链上,锚定资产合约部署在资产锚定的目的链上。希望把来源链上的资产跨到哪条链,就需要在目的链上部署相应的锚定资产合约
在NFT跨链流程中:
「业务资产合约」主要需要关注的safeTransferFrom
接口和safeBatchTransferFrom
接口:
-
safeTransferFrom
该接口用于从A链上发起NFT跨链资产转移的请求,即将A链的原生资产跨到B链时,需要调用该接口锁定A链上的原生资产( A->B ); -
safeBatchTransferFrom
该接口用于A链接收跨回的资产转移请求,即将B链的锚定资产跨回A链时,需要调用该接口解锁A链上相应的原生资产(B->A);
「锚定资产合约」主要需要关注的safeTransferFrom
接口和mintBatchByTB
/safeBatchTransferFrom
接口。
-
safeTransferFrom
该接口用于从B链上发起NFT跨链资产转移的请求,即将B链的锚定资产跨回A链时,需要调用该接口锁定B链上的锚定资产( B->A ); -
mintBatchByTB
该接口用于B链接收跨链资产转移请求,将A链资产跨到B链时,如果B链锚定资产不足,需要调用该接口铸造锚定资产( A->B ); -
safeBatchTransferFrom
该接口用于B链接收跨链资产转移请求,将A链资产跨到B链时,如果B链存在足够的锁定的锚定资产,需要调用该接口解锁锚定资产(A->B);
TB合约负责将NFT跨链请求接入异构链跨链的核心逻辑,其位于资产合约和SDP合约中间。 相对于上层的资产合约,TB合约需要在发送端接收资产合约的跨链请求进行资产锁定操作,在接收端主动调用资产合约进行相应的资产铸造操作; 相对于底层的SDP合约,TB合约作为SDP合约的上层业务合约,需要在发送端将跨链请求封装打包为跨链消息发送给SDP合约,在接收端被SDP合约调用从而接收跨链消息。 TB合约具体提供事件、变量及方法如下:
类型 | 名称 | 备注 |
---|---|---|
event | CrossChain | 标记跨链事件 |
map | token_bridges | 记录其他链的TB合约信息 |
map | route_table | 记录当前链资产与其他链锚定资产的绑定关系 |
map | asset_lock_record | 记录锁定在当前链上合约的资产信息 |
address | sdp_msg_address | 当前链上的SDP合约标识 |
function | onERC1155Received | 接收资产合约的跨链请求,并发送跨链消息 |
function | onERC1155BatchReceived | |
function | recvUnorderedMessage | 接收SDP合约发送的无序跨链消息,并锁定相应资产 |
function | setDomainTokenBridgeAddress | 设置token_bridges信息 |
function | registerRouter | 注册route_table信息 |
function | deregisterRouter | |
function | setSdpMsgAddress | 设置sdp_msg_address信息 |
function | grantRole | |
function | revokeRole | |
function | setContractOperator | |
function | batchUnlock | |
function | supportsInterface |
下面将对TB合约中核心的事件、变量及方法给出详细介绍。
TB合约成功发起跨链请求后会抛出CrossChain
的跨链事件,该事件记录了跨链双方信息及跨链资产信息,事件定义如下:
event CrossChain(
string indexed _domain, // 目的链域名
bytes32 indexed _src_contract, // 来源链资产合约标识
bytes32 indexed _dest_contract, // 目的链资产合约标识
uint256[] _ids, // 跨链资产id列表
uint256[] _amounts, // 跨链资产相应数量
bytes32 _holder, // 资产最终的所有者账户
uint8 _status // 当前跨链事件的状态
);
该变量用于记录其他链的TB合约信息,由setDomainTokenBridgeAddress
方法进行设置,具体记录方式为【其他链域名
-> 其他链TB合约标识
】:
mapping(string => bytes32) public token_bridges;
TB合约发送跨链消息时(onERC1155Received
)会根据该变量检查目的链的TB合约信息是否有效,并在调用SDP合约的sendUnorderedMessage
方法发送跨链消息时,根据该变量设置接收跨链消息链的接收合约。
require(
token_bridges[dest_domain] != bytes32(0),
"UNKNOW_TOKEN_BRIDGE"
);
// ···
InterContractMessageInterface(sdp_msg_address).sendUnorderedMessage(
dest_domain,
token_bridges[dest_domain],
abi.encode(
ids,
amounts,
TypesToBytes.addressToBytes32(msg.sender),
route_table[msg.sender][dest_domain],
holder,
uint8(CrossChainStatus.START)
)
);
TB合约接收跨链消息时(recvUnorderedMessage
)会根据该变量检查来源链的TB合约信息是否有效,并在调用SDP合约的sendUnorderedMessage
方法发送跨链成功的回执时,根据该变量设置接收跨链回执的接收合约。
require(token_bridges[_from_domain] == _sender, "UNKNOW_TOKEN_BRIDGE");
// ···
InterContractMessageInterface(sdp_msg_address).sendUnorderedMessage(
_from_domain,
token_bridges[_from_domain],
abi.encode(
ids,
amounts,
src_contract,
dest_contract,
holder,
uint8(CrossChainStatus.SUCCESS)
)
);
该变量用于记录当前链资产与其他链锚定资产的绑定关系,由registerRouter
方法进行设置,具体记录方式有两种:
- 将当前链上的原生资产映射到其他链上:【
当前链业务资产合约
,其他链域名
->其他链相应的锚定资产合约
】 - 将其他链上的原生资产映射的锚定资产跨回其他链上:【
当前链锚定资产合约
,其他链域名
->其他链相应的业务资产合约
】
mapping(address => mapping(string => bytes32)) public route_table;
TB合约发送跨链消息时会根据该变量检查来源资产合约(msg.sender
)在目的链dest_domain
上是否存在相应的绑定资产合约
require(
route_table[msg.sender][dest_domain] != bytes32(0),
"ROUTER_IS_NOT_EXISTED"
);
TB合约接收跨链消息时会同样根据该变量检查来源资产合约与目的资产合约的绑定关系是否一致
require(
route_table[dest_contract_addr][_from_domain] == src_contract,
"ROUTER_IS_NOT_EXISTED"
);
该变量用于记录TB合约锁定的当前链资产信息,具体记录方式为【当前链的资产合约
,资产id
-> 锁定资产的数量
】
TB合约在收到业务资产合约的跨链请求(onERC1155Received
)时需要锁定资产,即更新该变量;在收到SDP合约消息时根据_mintOrUnlock
的执行情况可能更新该变量。
该变量记录当前链上的SDP合约地址,SDP合约是TB合约的底层协议,部署TB合约时需要携带该信息,后期也可以由管理员更新设置该信息
- 参数:
- operator:address,跨链请求发送方,资产合约账户或资产合约的授权账户
- from:address,跨链资产发送方,资产合约账户
- _id:uint256,跨链资产id
- _amount:uint256,跨链资产数量
- _data:bytes,跨链请求信息,需要包含跨链接收链域名及跨链资产最终所有者账户
- 返回值:this.onERC1155Received.selector
- 功能:用于接收资产合约的跨链请求,进行相应的资产锁定,并构造跨链消息发送给SDP合约
该接口一般由资产合约调用,当资产合约发起的资产转移请求为跨链请求时,会将资产接收地址设置为TB合约地址,资产合约发现接收地址为合约时会触发onERC1155Received方法的调用,工作流程大致如下:
上述流程中第7步中的sendUnorderedMessage
方法具体参数如下:
- dest_domain:string 目的链域名
- token_bridges[dest_domain]:bytes32 目的链接收TB合约标识
- data:bytes TB合约定制化消息,使用solidity的abi.encode方法编码后作为SDP消息的payload字段。TB模板中提供的示例包含以下字段:
- ids:uint256[] 跨链资产id列表
- amounts:uint256[] 跨链资产数量
- TypesToBytes.addressToBytes32(msg.sender):bytes32 当前链的资产合约标识
- route_table[msg.sender][dest_domain]:bytes32 目的链绑定的资产合约标识
- holder:bytes32 跨链资产最终所有者账户
- uint8(CrossChainStatus.START):uint8 跨链状态,当前刚刚发起跨链消息,故状态为start
- 参数:
- _from_domain:string,跨链消息发送链的域名
- _sender:bytes32,跨链消息发送TB合约标识
- _message:bytes,具体跨链消息,包含跨链资产id、跨链资产数量、来源资产合约、目的资产合约、资产最终所有者账户、跨链状态等信息
- 返回值:无
- 功能:用于接收SDP合约发送的跨链请求,进行相应的资产解锁或铸造,并构造跨链回执回复给SDP合约
该接口一般由SDP合约直接调用,工作流程大致如下:
- 参数:
- _domain:string,其他链域名
- _token_bridge_address:bytes32,其他链的TB合约信息
- 返回值:无
- 权限:管理员(合约的部署账户是默认管理员账户,后期也可以添加管理员账户)
- 功能:设置
token_bridges
变量信息
- 参数:
- _src_contract:address,来源合约标识
- _domain:string,目的链域名
- _dest_contract:bytes32,目的合约标识
- 返回值:无
- 权限:管理员(合约的部署账户是默认管理员账户,后期也可以添加管理员账户)
- 功能:设置
route_table
变量信息
假设A链域名为domainA
,B链域名为domainB
,以「A链 -> B链 -> A链」的NFT跨链为例,完整的操作流程如下:
在A链和B链上分别注册自己的账户,假设A链上的账户为accountA
,B链上的账户为accountB
后续在A链或B链上的所有操作都基于相应的账户
跨链插件包括链上插件(系统合约)及链下插件,链上插件的启动包括SDP合约及AM合约的部署以及相关信息设置。
插件的具体开发及工作流程参加异构链插件开发手册
假设A链上准备就绪的的SDP合约地址为sdpAddrA
,B链上准备就绪的SDP合约地址为sdpAddrB
分别在A链和B链上部署TB合约,建议在部署TB合约时携带SDP合约地址(即sdpAddrA
或sdpAddrB
)
部署成功后,假定A链上的TB合约地址为tbAddrA
,B链上的TB合约地址为tbAddrB
- 在A链上部署业务资产合约
ERC1155
,假设部署成功后地址为assetAddrA
; - 在B链上部署相应的锚定资产合约
ERC1155CrossChainMapping
,假设部署成功后地址为assetAddrAMapB
- 在A链上调用TB合约的
setDomainTokenBridgeAddress
接口,设置A链上的token_bridges
映射表信息,具体调用参数如下:- _domain:domainB
- _token_bridge_address:tbAddrB
- 在A链上调用TB合约的
registerRouter
接口,设置A链上的route_table
映射表信息,具体调用参数如下:- _src_contract:assetAddrA
- _domain:domainB
- _dest_contract:assetAddrAMapB
- 在B链上调用TB合约的
setDomainTokenBridgeAddress
接口,设置B链上的token_bridges
映射表信息,具体调用参数如下:- _domain:domainA
- _token_bridge_address:tbAddrA
- 在B链上调用TB合约的
registerRouter
接口,设置B链上的route_table
映射表信息,具体调用参数如下:- _src_contract:assetAddrAMapB
- _domain:domainA
- _dest_contract:assetAddrA
用accountA
账户在A链上铸造10个assetAddrA
原始资产,假定铸造出的资产id为assetId
当前跨链NFT资产为A链上的原生资产,调用A链上业务资产合约的safeTransferFrom
接口主动发起NFT跨链资产转移请求,具体调用参数可如下:
- from:accountA A链上的账户
- to:tbAddrA A链TB合约标识
- id:assetId 相应的跨链资产id
- amount:1 跨链资产数量
- data:abi.encode(domainB, accountB)
- 注意accountB需要转换为byte32格式,如果是以太坊合约地址(20字节)需要在地址的前12个字节补零,从而保证第二个字段均为byte32格式
在进行该步骤时,合约内部的具体调用关系如图所示:
当前跨链NFT资产为B链上的锚定资产,调用B链上锚定资产合约的safeTransferFrom
接口,发起NFT跨链资产转移请求,具体调用参数可如下:
- from:accountB B链上的账户
- to:tbAddrB B链TB合约标识
- id:assetId 相应的跨链资产id
- amount:1 跨链资产数量
- data:abi.encode(domainA, accountA)
- 注意accountA需要转换为byte32格式,如果是以太坊合约地址(20字节)需要在地址的前12个字节补零,从而保证第二个字段均为byte32格式
在进行该步骤时,合约内部的具体调用关系如图所示: