5个经典的CCIP跨链应用场景

Chainlink 的跨链互操作性协议 (CCIP) 是一种新的通用跨链通信协议,它为智能合约开发者提供了以信任最小化的方式跨区块链网络传输数据和代币的能力。本文介绍CCIP的5个经典的跨链应用场景。

5个经典的CCIP跨链应用场景
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK

Chainlink 的跨链互操作性协议 (CCIP) 是一种新的通用跨链通信协议,它为智能合约开发者提供了以信任最小化的方式跨区块链网络传输数据和代币的能力。

目前,跨多个区块链部署的应用程序受到资产、流动性和用户碎片化的困扰。借助 CCIP,开发人员可以利用代币传输和任意消息传递来创建去中心化的应用程序,这些应用程序由部署在多个不同区块链网络上的多个不同智能合约组成,这些智能合约可以互操作以创建单个统一的应用程序。这种 Web3 设计模式称为跨链智能合约(cross-chain smart contract)。

以下是跨链应用程序的一些 CCIP 用例示例,范围从 DeFi 和跨链 ENS 到在多条链上铸造 NFT 再到跨链游戏。这些用例示例展示了 CCIP 将传统的单链或多链应用程序转变为强大的新跨链 dApp 的潜力。所有示例都可以在 Chainlink Labs GitHub 上找到,现在可以部署和交互。

1、DeFi:跨链可组合性

DeFi 是一类 Web3 应用程序,可通过跨链智能合约进行转型。在当今的 DeFi 世界中,许多应用程序要么部署在一条链上,要么部署在多条链上,每个实例都需要自己的一组用户和流动性。在每条链中,都有一个 DeFi 可组合性和“货币乐高”的概念,开发人员能够以无需许可的方式连接和集成部署在特定网络上的各种协议,以创建新的用例和金融产品。

通过启用跨链智能合约和跨链代币转移,CCIP 采用了 DeFi 可组合性的概念并将其成倍增加。因为现在,可组合性不再局限于每条链并局限于该链上的 DeFi 协议,所有区块链上的所有 DeFi 应用程序现在都可以以各种方式组合在一起以创建新的金融产品。应用程序和协议不再局限于它们所在的链。

DeFi 应用程序的这种跨链可组合性使 DeFi 生态系统更加零散,连接性更强,所有链上的流动性、用户和金融产品都可用于所有协议。使用货币乐高积木的比喻,CCIP 使您可以将所有不同的乐高积木组合在一起,并用它们构建金融协议,就好像它们是一套统一的积木一样。

一项将从 CCIP 中受益匪浅的特定 DeFi 金融服务是借贷。在当今世界,大多数 DeFi 借贷协议都要求你在要使用的协议所部署的链上存入抵押品。但许多 DeFi 用户在多个区块链上使用多个 DeFi 协议,并将资产分散在所有这些链上。这些用户通常追求最佳回报,调整头寸以最大化收益,但在许多情况下,他们将资产锁定在一条链上的协议中,而另一条链上有更好的收益机会来使用这些资产。如果他们想参与这些更高的收益机会,他们需要清算一条链上的头寸,手动将资产桥接到新链,将该资产存入新链上的协议,然后在他们想将资产返回到原始链时反向执行相同的过程——将资产转移到新协议以追逐收益机会需要很多步骤。

在这种情况下,CCIP 可以帮助 DeFi 协议真正实现跨链,并允许用户无缝地将一条链上的数字资产用作另一条链上 DeFi 协议的抵押品,所有这些都由 CCIP 在协议级别处理,而无需用户执行手动步骤,或由于使用第三方桥接而增加信任假设。使用 CCIP,DeFi 协议可以让借款人将资产存入一条(源)链,或将其直接转移到目标链,然后在目标链上提供这些资产以供借款。然后,当他们希望停止在目标链上使用资产时,DeFi 协议可以使用 CCIP 撤回他们的头寸并将他们的资产移回原始链。这就是 CCIP 支持的 DeFi 的强大之处。

在此示例中,我们在 Avalanche Fuji 测试网上有一个 DeFi 智能合约 Sender.sol。此合约以代币的形式接受用户存款;它可以是包装好的 ETH、稳定币或任何具有实际价值的代币。Sender.sol 有一个 sendMessage 函数,它使用 CCIP 执行指定代币的可编程代币转移以及向目标链发送消息。在本例中,我们将指定的代币发送到以太坊 Sepolia 测试网,消息包含最终用户的 EOA(外部拥有账户):

// Sender.sol
 Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
receiver: abi.encode(receiver), // ABI-encoded receiver contract address
data: data,
tokenAmounts: tokenAmounts,
extraArgs: Client._argsToBytes(
Client.EVMExtraArgsV1({gasLimit: 200_000, strict: false}) // Additional arguments, setting gas limit and non-strict sequency mode
),
feeToken: address(linkToken) // Setting feeToken to LinkToken address, indicating LINK will be used for fees
});


// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());


// Get the fee required to send the message. Fee paid in LINK.
uint256 fees = router.getFee(destinationChainSelector, evm2AnyMessage);


// Approve the Router to pay fees in LINK tokens on contract's behalf.
linkToken.approve(address(router), fees);


// Approve the Router to transfer the tokens on contract's behalf.
IERC20(tokenToTransfer).approve(address(router), transferAmount);


// Send the message through the router and store the returned message ID
messageId = router.ccipSend(destinationChainSelector, evm2AnyMessage);


// Emit an event with message details
emit MessageSent(messageId, destinationChainSelector, receiver, msg.sender, tokenAmount, fees);
注意:本文中的所有代码示例仅供说明之用,且按“原样”提供,不包含任何形式的陈述、保证、承诺或条件。这些代码片段的使用受我们的服务条款(见 chain.link/terms)的约束。

在以太坊 Sepolia 网络上,我们部署了一个 Protocol.sol 智能合约。此合约接收 CCIP 可编程代币转移消息并执行以下操作:

  • 铸造并控制可通过存入抵押品借入的稳定币。
  • 从 CCIP 消息中,读取从源链(作为借款抵押品)发送资金的指定代币合约地址(在目标链上)和存入的金额。
  • 同样从 CCIP 消息内容中,读取最终用户(存款人/借款人)的钱包地址。稳定币铸造到此地址,此地址还用于跟踪存款和借款。
  • 将此信息存储在智能合约中。
// Protocol.sol
   bytes32 messageId = any2EvmMessage.messageId; // fetch the messageId
   uint64 sourceChainSelector = any2EvmMessage.sourceChainSelector; // fetch the source chain identifier (aka selector)
   address sender = abi.decode(any2EvmMessage.sender, (address)); // abi-decoding of the sender address

   // Collect tokens transferred. This increases this contract's balance for that Token.
   Client.EVMTokenAmount[] memory tokenAmounts = any2EvmMessage.destTokenAmounts;
   address token = tokenAmounts[0].token;
   uint256 amount = tokenAmounts[0].amount;

   address depositor = abi.decode(any2EvmMessage.data, (address)); // abi-decoding of the depositor's address

   receivedMessages.push(messageId);
   MessageIn memory detail = MessageIn(sourceChainSelector, sender, depositor, token, amount);
   messageDetail[messageId] = detail;

   emit MessageReceived(messageId, sourceChainSelector, sender, depositor, tokenAmounts[0]);

   // Store depositor data.
   deposits[depositor][token] = amount;

一旦 Protocol.sol 收到并成功处理了此 CCIP 可编程代币转移消息,用户便可以通过执行 borrowUSDC 函数手动发起借入资金的操作。此功能允许用户使用转移的代币作为抵押品来铸造和借入等量的稳定币(例如 USDC)作为借款人的 EOA。在此示例中,我们假设抵押率为 70%,这意味着协议将借出不超过存入代币价值的 70%:

uint256 borrowed = borrowings[msg.sender][address(usdcToken)];
require(borrowed == 0, "Caller has already borrowed USDC");

address transferredToken = messageDetail[msgId].token;
require(transferredToken != address(0), "Caller has not transferred this token");

uint256 deposited = deposits[msg.sender][transferredToken];
uint256 borrowable = (deposited * 70) / 100; // 70% collateralization ratio.

// LINK/USD on Sepolia (https://sepolia.etherscan.io/address/0xc59E3633BAAC79493d908e63626716e204A45EdF)
// Docs: https://docs.chain.link/data-feeds/price-feeds/addresses#Sepolia%20Testnet
AggregatorV3Interface priceFeed = AggregatorV3Interface(0xc59E3633BAAC79493d908e63626716e204A45EdF);

(, int256 price, , , ) = priceFeed.latestRoundData(); 
uint256 price18decimals = uint256(price * (10 ** 10)); // make USD price 18 decimal places from 8


uint256 borrowableInUSDC = borrowable * price18decimals;

// MintUSDC
usdcToken.mint(msg.sender, borrowableInUSDC);

// Update state.
borrowings[msg.sender][address(usdcToken)] = borrowableInUSDC;


return borrowableInUSDC;

一旦用户成功在 Sepolia 上借入 UDSC 并抵押其存入的抵押品,他们就可以在 Sepolia 网络上的任何 DeFi 协议上随意使用这些资金。然后,当他们完成后,他们可以将资金偿还给 Protocol.sol,这将导致稳定币代币被销毁,然后 CCIP 可编程代币转移消息将发送回 Fuji 网络上的 Sender.sol 合约,该合约将锁定的代币返回到 Fuji 网络上的指定地址。请注意,用户必须首先批准 Protocol.sol 作为用户借入的稳定币的“消费者”,以便协议能够销毁借入的金额,这就是偿还的实施方式:

require(amount >= borrowings[msg.sender][address(usdcToken)], "Repayment amount is less than amount borrowed");


// Get the deposit details, so it can be transferred back.
address transferredToken = messageDetail[msgId].token;
uint256 deposited = deposits[msg.sender][transferredToken];


uint256 mockUSDCBal = usdcToken.balanceOf(msg.sender);
require(mockUSDCBal >= amount, "Caller's USDC token balance insufficient for repayment");


if (usdcToken.allowance(msg.sender, address(this)) < borrowings[msg.sender][address(usdcToken)]) {
revert("Protocol allowance is less than amount borrowed");
}


usdcToken.burnFrom(msg.sender, mockUSDCBal);


borrowings[msg.sender][address(usdcToken)] = 0;
// send transferred token and message back to Sepolia Sender contract
sendMessage(destinationChain, receiver, transferredToken, deposited);

此示例的完整源代码和说明可在 CCIP-DeFi Lending GitHub 存储库中找到。

2、DeFi:跨链清算保护

继续 DeFi 和借贷协议的主题,许多 DeFi 用户在多个区块链的多个 DeFi 协议中持有多个头寸。这使得跟踪投资组合和 DeFi 头寸变得困难。有多个第三方平台、跟踪器甚至收益聚合器可用,DeFi 用户可以简单地部署抵押品,让这些第三方平台处理资产的部署和移动,以优化最终用户的收益。虽然这些工具是抽象出 DeFi 的一些复杂性的好方法,以便用户可以轻松获得收益,但它们并没有最小化信任。用户委托协议赚取收益并确保头寸保持抵押以避免清算。除此之外,如果最终用户想要获得某种清算保护,他们需要在他们拥有 DeFi 头寸的所有区块链上部署原生资产,随时准备部署以确保他们在各自链上所持头寸的贷款获得抵押。

通过 CCIP 和跨链代币转移和消息传递,DeFi 协议、头寸监控应用程序和收益聚合器可以得到增强,以实现跨链清算保护。这意味着用户可以在多个区块链上的多个 DeFi 协议上拥有未平仓头寸,然后他们可以在单个链上分配资产,以便在一个或多个贷款需要额外资金来确保抵押的情况下用作额外抵押品。以下是它在高层次上的运作方式:

  • DeFi 最终用户在多个链上的多个协议上拥有债务头寸(例如以太坊、Avalanche、Polygon),但他们将流动性安全地保存在一条链上的保险库中(例如以太坊上的 Aave)。
  • 在用户拥有债务头寸的每条链上,用户实施的 Chainlink Automation 都会监控头寸的债务比率。
  • 如果 Automation 检测到任何贷款接近清算阈值,则 Automation 将向用户的流动性链(例如以太坊)发送 CCIP 消息,请求发送资金来补充债务头寸。
  • 当流动性链上的合约收到 CCIP 消息时,它会从 Aave 提取流动性,并将新的 CCIP 消息连同资金一起发送回请求链。该消息包含足够的信息和代币来为头寸提供资金并避免清算情况。

结果是,用户可以在多条链上拥有债务头寸,同时仍将流动性保持在一条链上。整个过程最小化了信任,用户仍然 100% 控制他们的债务头寸,他们不必手动提取和跨链转移资金。它的工作原理如下:

Chainlink Automation 监控用户拥有借记头寸的所有链,并确定是否需要发送融资消息。如果需要,performUpkeep 函数将向具有流动性的链上的保险库发送 CCIP 消息,请求发送资金。

function checkUpkeep(
       bytes calldata checkData
   )
       external
       view
       override
       returns (bool upkeepNeeded, bytes memory performData)
   {
       upkeepNeeded =
           IMockLending(i_lendingAddress).healthFactor(i_onBehalfOf) <
           i_minHealthFactor &&
           !_isCcipMessageSent;
   }

function performUpkeep(bytes calldata performData) external override {
       require(
           !_isCcipMessageSent,
           "CCIP Message already sent, waiting for a response"
       );
       require(
           IMockLending(i_lendingAddress).healthFactor(i_onBehalfOf) <
               i_minHealthFactor,
           "Account can't be liquidated!"
       );

       // Ask for funds from LPSC on the source blockchain
       Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
           receiver: abi.encode(i_lpsc),
           data: abi.encode(
               i_tokenAddress,
               IMockLending(i_lendingAddress).getBorrowedAmount(i_onBehalfOf)
           ),
           tokenAmounts: new Client.EVMTokenAmount[](0),
           extraArgs: "",
           feeToken: i_link
       });

       bytes32 messageId = IRouterClient(i_sourceChainRouter).ccipSend(
           i_sourceChainSelector,
           message
       );
       _isCcipMessageSent = true;

       emit MessageSent(messageId);
   }

然后,具有流动性的链上的保险库会收到资金请求,并检查是否有足够的资金发送回请求链,或者是否应该从 DeFi 协议(例如 Aave)中提取一些流动性,以确保有足够的资金发送。随后,它启动 CCIP 可编程代币转移,其中包含请求的资金以及最初收到的消息的消息 ID(因此,请求资金的区块链上的目标合约知道资金用于哪个请求):

function _ccipReceive(
       Client.Any2EVMMessage memory receivedMessage
   ) internal override {
       bytes32 messageId = receivedMessage.messageId;
       uint64 sourceChainSelector = receivedMessage.sourceChainSelector;
       address sender = abi.decode(receivedMessage.sender, (address));
       (address tokenAddress, uint256 amount) = abi.decode(
           receivedMessage.data,
           (address, uint256)
       );

       address tokenToReturn = s_destinationToSourceMap[
           keccak256(abi.encodePacked(tokenAddress, sourceChainSelector))
       ];

       uint256 currentBalance = IERC20(tokenToReturn).balanceOf(address(this));

       // If there are not enough funds in LPSC, withdraw additional from Aave vault
       if (currentBalance < amount) {
           withdrawFromVault(tokenToReturn, amount - currentBalance);
       }

       Client.EVMTokenAmount[] memory tokenAmounts;
       tokenAmounts[1] = (Client.EVMTokenAmount(tokenToReturn, amount));

       Client.EVM2AnyMessage memory messageReply = Client.EVM2AnyMessage({
           receiver: abi.encode(sender),
           data: abi.encode(msgId),
           tokenAmounts: tokenAmounts,
           extraArgs: "",
           feeToken: LINK
       });

       bytes32 replyMessageId = IRouterClient(i_router).ccipSend(
           sourceChainSelector,
           messageReply
       );

       //  emit ReplySent(replyMessageId,sourceChainSelector, messageId, sender, tokenToReturn, amount);
   }

最后,链上请求资金补充债务头寸的智能合约接收 CCIP 可编程代币转账,将请求 ID 与原始请求进行匹配,然后获取资金并将转账资金存入债务头寸,以增加贷款抵押并避免清算:

function _ccipReceive(
        Client.Any2EVMMessage memory receivedMessage
    ) internal override {
        _isCcipMessageSent = false;
        bytes32 requestMessageId = abi.decode(receivedMessage.data, (bytes32));
        uint256 amountToRepay = requested[requestMessageId];
        IMockLending(i_lendingAddress).repay(
            i_tokenAddress,
            amountToRepay,
            i_onBehalfOf
        );
    }

此示例演示了如何在 DeFi 协议和 DeFi 头寸监控应用程序中使用 CCIP,为用户提供跨多个区块链的债务头寸信任最小化清算保护,同时允许他们将资金和流动性保留在单个区块链上。

此示例的完整源代码和说明可在 CCIP Liquidation Protector GitHub 存储库中找到。

3、跨链域名服务

ENS 等去中心化域名服务在 Web3 中非常受欢迎,因为它们有助于将人类可读的名称转换为钱包地址。在理想世界中,域名服务不应特定于一条链,而是每个注册的域名都应传播并存在于所有以太坊链、侧链、第 2 层和应用链中。这将允许用户在整个以太坊生态系统中拥有单一、统一的身份,而不必跨多个命名服务注册域名或使用未最小化信任的互操作性解决方案。

但是,要实现这一点,域名服务需要与其他区块链进行通信。区块链上命名服务的每个实例都需要在注册新域名时收到通知,并且需要有一种方法来针对所有区块链中的全局名称注册表执行“查找”。

此示例展示了如何构建简化的跨链命名服务应用程序,用户可以在一条链上注册域名,并让该注册传播到多个其他区块链,以及将名称解析为任何区块链上的地址。

跨链命名架构概述

第一步是将 CrossChainNameServiceRegister 和  CrossChainNameServiceLookup 合约部署到以太坊 Sepolia 网络。该网络将充当所有注册发生的“主”网络,然后传播到其他链。

当你注册新的 .ccns 句柄时, CrossChainNameServiceRegister 合约将使用 CCIP 向其他受支持的区块链发送一条消息,其中包含有关已注册 .ccns 句柄的信息:

uint256 length = s_chains.length;
       for (uint256 i; i < length; ) {
           Chain memory currentChain = s_chains[i];

           Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
               receiver: abi.encode(currentChain.ccnsReceiverAddress),
               data: abi.encode(_name, msg.sender),
               tokenAmounts: new Client.EVMTokenAmount[](0),
               extraArgs: Client._argsToBytes(
                   Client.EVMExtraArgsV1({
                       gasLimit: currentChain.gasLimit,
                       strict: currentChain.strict
                   })
               ),
               feeToken: address(0) // We leave the feeToken empty indicating we'll pay raw native.
           });

           i_router.ccipSend{
               value: i_router.getFee(currentChain.chainSelector, message)
           }(currentChain.chainSelector, message);

           unchecked {
               ++i;
           }
       }

       i_lookup.register(_name, msg.sender);

在所有支持的接收区块链上,都将部署 CrossChainNameServiceReceiver 合约。此合约将从 CrossChainNameServiceRegister 合约接收已注册的 .ccns 域名,并将其存储在部署在该区块链上的 CrossChainNameServiceLookup 合约中:

constructor(
        address router,
        address lookup,
        uint64 sourceChainSelector
    ) CCIPReceiver(router) {
        i_router = IRouterClient(router);
        i_lookup = ICrossChainNameServiceLookup(lookup);
        i_sourceChainSelector = sourceChainSelector;
    }

    function _ccipReceive(
        Client.Any2EVMMessage memory message
    ) internal override onlyFromSourceChain(message.sourceChainSelector) {
        (string memory _name, address _address) = abi.decode(
            message.data,
            (string, address)
        );

        i_lookup.register(_name, _address);
    }

最后, CrossChainNameServiceLookup 合约将部署在所有区块链上,包括注册区块链(在本例中为 Sepolia)以及所有目标区块链。此合约将用于存储所有注册的 .ccns 句柄,并充当执行查找以将名称转换为地址的接口:

function register(
        string memory _name,
        address _address
    ) external onlyCrossChainNameService {
        if (lookup[_name] != address(0)) revert AlreadyTaken();

        lookup[_name] = _address;
    }

使用这种简单的设计模式,可以创建一个简单的跨链域名服务,用户可以注册一次域名,然后在多个区块链上拥有和使用它。

此示例的完整源代码和说明可在跨链名称服务 GitHub 存储库中找到。

4、跨链 NFT

NFT 是 Web3 中最受欢迎的用例之一。每个 NFT 项目通常都在单个区块链上,或者项目本身在多个链上有多个部署,如果最终用户想在多个区块链上拥有它,他们必须多次铸造 NFT。

借助 CCIP 任意消息传递,NFT 项目可以允许其资产在单个链上铸造一次,由铸造者支付一次,然后将其传播给其他区块链上的用户。这意味着用户可以拥有和共享他们的 NFT,无论他们使用哪个网络。 CCIP 还可用于跨链“销毁和铸造”NFT,允许用户将他们的 NFT 从一条链移动到另一条链。以下是第一种场景如何工作的示例:

MyNFT 合约包含一个具有铸造功能的简单 NFT 智能合约:

function mint(address to) public {
    unchecked {
        tokenId++;
    }
    _safeMint(to, tokenId);
}

sourceMinter 合约部署在源链上,其 mint 函数中包含逻辑,用于将带有 ABI 编码的 mint 函数签名的 CCIP 跨链消息从 MyNFT.sol 智能合约发送到目标区块链:

function mint(
        uint64 destinationChainSelector,
        address receiver,
        PayFeesIn payFeesIn
    ) external {
        Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
            receiver: abi.encode(receiver),
            data: abi.encodeWithSignature("mint(address)", msg.sender),
            tokenAmounts: new Client.EVMTokenAmount[](0),
            extraArgs: "",
            feeToken: payFeesIn == PayFeesIn.LINK ? i_link : address(0)
        });

        uint256 fee = IRouterClient(i_router).getFee(
            destinationChainSelector,
            message
        );

        bytes32 messageId;

        if (payFeesIn == PayFeesIn.LINK) {
            LinkTokenInterface(i_link).approve(i_router, fee);
            messageId = IRouterClient(i_router).ccipSend(
                destinationChainSelector,
                message
            );
        } else {
            messageId = IRouterClient(i_router).ccipSend{value: fee}(
                destinationChainSelector,
                message
            );
        }

        emit MessageSent(messageId);

DestinationMinter 智能合约将接收带有 ABI 编码的 mint 函数签名的 CCIP 跨链消息作为有效载荷,并使用它调用 MyNFT 智能合约函数中的 mint 函数。然后,MyNFT 智能合约将从 SourceMinter 智能合约的 mint() 函数将新的 NFT 铸造到 msg.sender 帐户,即铸造到源链上铸造 NFT 的同一帐户地址:

function _ccipReceive(
    Client.Any2EVMMessage memory message
) internal override {
    (bool success, ) = address(nft).call(message.data);
    require(success);
    emit MintCallSuccessfull();
}

最终结果是,铸造 NFT 的用户现在在多个链上拥有 NFT,他们只需铸造并支付一次。如果 NFT 项目希望在跨区块链上保持严格的不可替代性,则可以轻松修改此解决方案以在目标区块链上铸造 NFT,然后在源链上刻录,从而确保在所有区块链上只有一个版本。

此示例的完整源代码和说明可在 Cross-Chain NFT GitHub 存储库中找到。

5、游戏:跨链Tic-Tac-Toe

在过去的几年中,Web3 游戏变得非常流行。然而,与 DeFi 一样,游戏非常分散,游戏及其资产通常特定于某个链。但与传统游戏一样,最终目标或最佳体验是玩家可以一起玩游戏,无论他们使用什么硬件或软件。就像 PC 游戏玩家可以与 Xbox 游戏机所有者一起玩游戏一样,没有理由说有人不能与在 Avalanche 上玩游戏的人一起在 Polygon 上玩游戏。这就是所谓的跨平台游戏

这完美地转化为回合制 Web3 游戏和其他不需要快速实时交互的游戏。 Web3 游戏遭受用户碎片化的困扰,游戏玩家更喜欢在他们选择的链上玩游戏并使用他们喜欢的数字资产。 CCIP 使 Web3 游戏真正成为跨链游戏,促进跨链资产转移,并实现跨多个区块链共享游戏状态,让游戏玩家无论想要使用哪条链都可以互相玩。如果你想接触尽可能多的游戏玩家,那么将你的游戏部署在多个链上并以所有玩家都可以对抗或相互对战的方式进行设计是有意义的。

可以通过回合制策略游戏(例如Tic-Tac-Toe井字游戏)来简单演示这种跨链游戏设计模式。在这个例子中,我们有一个部署在多个区块链上的游戏智能合约。然后,用户可以在自己选择的链上开始游戏,然后与朋友分享游戏会话 ID。如果愿意,他们的朋友可以从另一条链加入游戏。在游戏创建时,CCIP 将与所有其他链共享游戏详细信息和初始状态:

struct GameSession {
        bytes32 sessionId;
        address player_1; // player who starts the game
        address player_2; // the other player in the game
        address winner; // winner of game
        address turn; // check who takes action in next step
        uint8[9] player1Status; // current status for player 1
        uint8[9] player2Status; // current status for player 2
    }
    mapping(bytes32 => GameSession) public gameSessions;
    bytes32[] public sessionIds;


function start(uint64 destinationChainSelector, address receiver) external {
        bytes32 uniqueId = keccak256(abi.encodePacked(block.timestamp, msg.sender));
        sessionIds.push(uniqueId);
        gameSessions[uniqueId]= GameSession(
            uniqueId,
            msg.sender,
            address(0),
            address(0),
            msg.sender,
            initialCombination,
            initialCombination
            );

        sendMessage(destinationChainSelector, receiver, gameSessions[uniqueId]);
    }

当第一个玩家开始游戏并轮到自己时,另一个区块链上的第二个玩家将在成功处理 CCIP 消息后在其游戏智能合约上看到更新的游戏状态。然后,玩家 2 将轮到自己,这将生成一个 CCIP 消息,并发送回玩家 1,并在其链上更新游戏状态:

然后,玩家 1 将看到更新的游戏状态并再次采取行动。随着玩家采取行动,链之间的 CCIP 消息将持续传递,直到游戏结束并宣布获胜者。这里要注意的重要一点是,两条链上的游戏智能合约都维护游戏状态,CCIP 用于发送和接收消息,以确保游戏状态在两条区块链上都得到维护:

function checkWin(bytes32 combination) public view returns (bool) {
        return wcs[combination];
    }

    /// @notice Sends data to receiver on the destination chain.
    /// @dev Assumes your contract has sufficient native asset (e.g, ETH on Ethereum, MATIC on Polygon...).
    /// @param destinationChainSelector The identifier (aka selector) for the destination blockchain.
    /// @param receiver The address of the recipient on the destination blockchain.
    /// @param message The string message to be sent.
    /// @return messageId The ID of the message that was sent.
    function sendMessage(
        uint64 destinationChainSelector,
        address receiver,
        GameSession memory message
    ) public returns (bytes32 messageId) {
        // Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
        Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({
            receiver: abi.encode(receiver), // ABI-encoded receiver address
            data: abi.encode(msg), // ABI-encoded string message
            tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent
            extraArgs: Client._argsToBytes(
                Client.EVMExtraArgsV1({gasLimit: 400_000, strict: false}) // Additional arguments, setting gas limit and non-strict sequency mode
            ),
            feeToken: address(0) // Setting feeToken to zero address, indicating native asset will be used for fees
        });

        // Initialize a router client instance to interact with cross-chain router
        IRouterClient router = IRouterClient(_router);

        // Get the fee required to send the message
        uint256 fees = router.getFee(destinationChainSelector, evm2AnyMessage);

        // Send the message through the router and store the returned message ID
        messageId = router.ccipSend{value: fees}(
            destinationChainSelector,
            evm2AnyMessage
        );

        // Emit an event with message details
        emit MessageSent(
            messageId,
            destinationChainSelector,
            receiver,
            message,
            fees
        );

        // Return the message ID
        return messageId;
    }

此示例的完整源代码和说明可在 CCIP Tic-Tac-Toe GitHub 存储库中找到。

6、结束语

从跨链 DeFi 和 NFT 到跨多个区块链运行的游戏,CCIP 可以实现跨链智能合约、跨所有区块链的真正 DeFi 可组合性以及更加统一的 Web3。


原文链接:5 Ways To Build Cross-Chain Applications With CCIP

DefiPlot翻译整理,转载请标明出处

通过 NowPayments 打赏