Skip to content

NFT跨链操作文档

zouxyan edited this page Nov 22, 2024 · 1 revision

1 NFT跨链介绍

AntChain Bridge跨链方案支持NFT 在异构链上的跨链,可有效提升资产的流动性。通用的异构链NFT跨链方案大致设计如下图所示:

  • NFT跨链方案中,业务资产合约为遵循ERC1155标准的NFT资产合约,锚定资产合约基本遵循ERC1155标准,但根据业务需求对ERC1155标准做了一定的修改及裁剪;
  • NFT跨链的本质是通过跨链将A链上的资产映射到B链上,业务资产合约负责发起资产映射的跨链请求,锚定资产合约负责接收请求进行相应的资产映射(铸造或解锁)。
  • 资产合约发起跨链请求后,TokenBridge合约负责将NFT跨链请求接入异构链跨链的核心逻辑。一方面TokenBridge合约需要针对上层资产跨链请求进行相关资产锁定或映射,另一方面TokenBridge合约需要根据将跨链请求构造为定制化跨链消息发送给跨链系统合约(SDP合约)。
  • 跨链系统合约收到TokenBridge合约的跨链消息后,将根据AntChain Bridge系统的跨链原理执行具体跨链逻辑。

2 相关合约介绍

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合约,具体介绍参见「异构链插件开发手册」。

2.1 资产合约

锚定资产合约详情

业务资产合约完全符合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);

2.2 TB合约

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合约中核心的事件、变量及方法给出详细介绍。

核心事件

CrossChain

TB合约成功发起跨链请求后会抛出CrossChain的跨链事件,该事件记录了跨链双方信息及跨链资产信息,事件定义如下:

    event CrossChain(
        string indexed _domain, // 目的链域名
        bytes32 indexed _src_contract, // 来源链资产合约标识
        bytes32 indexed _dest_contract, // 目的链资产合约标识
        uint256[] _ids, // 跨链资产id列表
        uint256[] _amounts, // 跨链资产相应数量
        bytes32 _holder, // 资产最终的所有者账户
        uint8 _status // 当前跨链事件的状态
    );

核心变量

token_bridges

该变量用于记录其他链的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)
            )
        );

route_table

该变量用于记录当前链资产与其他链锚定资产的绑定关系,由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"
        );

asset_lock_record

该变量用于记录TB合约锁定的当前链资产信息,具体记录方式为【当前链的资产合约资产id -> 锁定资产的数量】 TB合约在收到业务资产合约的跨链请求(onERC1155Received)时需要锁定资产,即更新该变量;在收到SDP合约消息时根据_mintOrUnlock的执行情况可能更新该变量。

sdp_msg_address

该变量记录当前链上的SDP合约地址,SDP合约是TB合约的底层协议,部署TB合约时需要携带该信息,后期也可以由管理员更新设置该信息

核心接口

onERC1155Received

  • 参数:
    • 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

recvUnorderedMessage

  • 参数:
    • _from_domain:string,跨链消息发送链的域名
    • _sender:bytes32,跨链消息发送TB合约标识
    • _message:bytes,具体跨链消息,包含跨链资产id、跨链资产数量、来源资产合约、目的资产合约、资产最终所有者账户、跨链状态等信息
  • 返回值:无
  • 功能:用于接收SDP合约发送的跨链请求,进行相应的资产解锁或铸造,并构造跨链回执回复给SDP合约

该接口一般由SDP合约直接调用,工作流程大致如下:

setDomainTokenBridgeAddress

  • 参数:
    • _domain:string,其他链域名
    • _token_bridge_address:bytes32,其他链的TB合约信息
  • 返回值:无
  • 权限:管理员(合约的部署账户是默认管理员账户,后期也可以添加管理员账户)
  • 功能:设置token_bridges变量信息

registerRouter

  • 参数:
    • _src_contract:address,来源合约标识
    • _domain:string,目的链域名
    • _dest_contract:bytes32,目的合约标识
  • 返回值:无
  • 权限:管理员(合约的部署账户是默认管理员账户,后期也可以添加管理员账户)
  • 功能:设置route_table变量信息

3 操作手册

假设A链域名为domainA,B链域名为domainB,以「A链 -> B链 -> A链」的NFT跨链为例,完整的操作流程如下:

第一步:注册账户

在A链和B链上分别注册自己的账户,假设A链上的账户为accountA,B链上的账户为accountB 后续在A链或B链上的所有操作都基于相应的账户

第二步:启动跨链插件

跨链插件包括链上插件(系统合约)及链下插件,链上插件的启动包括SDP合约及AM合约的部署以及相关信息设置。

插件的具体开发及工作流程参加异构链插件开发手册

假设A链上准备就绪的的SDP合约地址为sdpAddrA,B链上准备就绪的SDP合约地址为sdpAddrB

第三步:部署TB合约

分别在A链和B链上部署TB合约,建议在部署TB合约时携带SDP合约地址(即sdpAddrAsdpAddrB) 部署成功后,假定A链上的TB合约地址为tbAddrA,B链上的TB合约地址为tbAddrB

第四步:部署资产合约

  • 在A链上部署业务资产合约ERC1155,假设部署成功后地址为assetAddrA
  • 在B链上部署相应的锚定资产合约ERC1155CrossChainMapping,假设部署成功后地址为assetAddrAMapB

第五步:设置TB合约相关信息

  • 在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

第七步:执行A链到B链的NFT资产转移

当前跨链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格式

在进行该步骤时,合约内部的具体调用关系如图所示:

第八步:执行B链到A链的NFT资产转移

当前跨链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格式

在进行该步骤时,合约内部的具体调用关系如图所示: