与Next.js,Solidity&Axelar构建完整的堆栈链链DAPP
#javascript #网络开发人员 #nextjs #solidity

今天的区块链生态系统的特征是许多区块链网络独立运行,每个网络都具有独特的功能和功能。但是,这种分裂给这些网络之间的无缝沟通和协作带来了挑战。为了克服这些障碍,出现的重要元素是可编程的区块链互操作性层。

可编程的区块链互操作性层桥接不同的区块链网络,可促进信息和资产的交换。它通过提供跨链通信的标准化框架来实现平稳的交互,从而使各种区块链网络无缝地互动。

在本教程中,您将学习如何使用Next.js,Solidity和Axelar General message passing构建全链接互链分散应用程序,以将消息从一个区块链发送到另一个区块链。

快速启动,您可以在GitHub上找到本教程的完整代码。但是,在下面提供的视频中,您可以看到完整的应用程序,该应用程序为用户提供了以下功能:

  • 连接他们的钱包

  • 输入他们的跨链互动的所需消息

  • 将邮件从Binance发送到Avalanche

开始使用Axelar通用消息传递

Axelar's General Message Passing (GMP)功能通过使开发人员能够无缝地调用互连链的任何功能。

使用GMP,开发人员获得了:

的能力
  1. 拨打链条A的合同并与链上的合同互动。

  2. 通过在链条A上调用合同并将令牌发送到链b。

  3. 来执行跨链交易

先决条件

入门前,您需要以下先决条件:

项目设置和安装

要快速启动项目设置和安装,请克隆此project on GitHub。确保使用以下命令在starter分支上:

git clone <https://github.com/axelarnetwork/fullstack-interchain-dapp>

接下来,使用终端中的以下命令将其克隆后,本地安装该项目。

这是您可以使用npm安装项目的方式:

cd fullstack-interchain-dapp && npm i && npm run dev

Next.js将默认情况下在http://localhost:3000启用热线装饰的开发环境。

Build a Full Stack Interchain dApp with Next.js, Solidity & Axelar

与Hardhat和Axelar GMP建立智能合同

在本节中,您将建立一个链链合同,利用Axelar GMP功能将消息从一个链发送到另一个链。

导航到您在上一步中克隆的项目的根文件夹,然后运行以下命令创建一个新的HardHat Project。

mkdir hardhat
cd hardhat
npm install --save-dev hardhat

让我们通过运行以下命令来获取示例项目:

npx hardhat

我们将选择以下选项:

What do you want to do?

✔ Create A JavaScript Project

✔ Hardhat project root:

? Do you want to add a .gitignore? (Y/n) › y

? Do you want to install this sample project's dependencies with npm (hardhat @nomicfoundation/hardhat-toolbox)? (Y/n) › y

@nomicfoundation/hardhat-toolbox插件捆绑了所有常用的软件包和建议开始使用HardHat开发的HardHat插件。

以防万一它没有自动安装,请使用以下命令安装其他要求:

npm i @nomicfoundation/hardhat-toolbox

下一

npm i @axelar-network/axelar-gmp-sdk-solidity dotenv

要确保一切正常,请在hardhat目录中运行下面的命令。

npx hardhat test

您将在我们的控制台中看到通过的测试结果。

从测试文件夹中删除Lock.js,然后从scripts目录中删除deploy.js。之后,转到合同并删除Lock.sol.

文件夹本身不应删除!

contracts目录中创建一个SendMessage.sol文件,并使用以下代码段对其进行更新。使用HARDHAT时,文件布局至关重要,因此请注意!

// SPDX-License-Identifier: MIT
// SPDX license identifier specifies which open-source license is being used for the contract
pragma solidity 0.8.9;

// Importing external contracts for dependencies
import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol';
import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol';
import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol';
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';

contract SendMessage is AxelarExecutable {

    string public value;
    string public sourceChain;
    string public sourceAddress;

    IAxelarGasService public immutable gasService;

    constructor(address gateway_, address gasReceiver_) AxelarExecutable(gateway_) {
        // Sets the immutable state variable to the address of gasReceiver_
        gasService = IAxelarGasService(gasReceiver_);
    }

    function sendMessage(
        string calldata destinationChain,
        string calldata destinationAddress,
        string calldata value_
    ) external payable {

        bytes memory payload = abi.encode(value_);

        if (msg.value > 0) {
            gasService.payNativeGasForContractCall{ value: msg.value }(
                address(this),
                destinationChain,
                destinationAddress,
                payload,
                msg.sender
            );
        }
        // Calls the Axelar gateway contract with the specified destination chain and address and sends the payload along with the call
        gateway.callContract(destinationChain, destinationAddress, payload);
    }

    function _execute(
        string calldata sourceChain_,
        string calldata sourceAddress_,
        bytes calldata payload_
    ) internal override {
        // Decodes the payload bytes into the string value and sets the state variable for this contract
        (value) = abi.decode(payload_, (string));

sourceChain = sourceChain_;
        sourceAddress = sourceAddress_;
    }
}

在上面的代码段中:

  • 创建扩展AxelarExecutable合同的SendMessage合同

  • 导入AxelarExecutableIAxelarGatewayIAxelarGasService来自@axelar-network/axelar-gmp-sdk-solidity库。

  • 定义four状态变量:valuesourceChainsourceAddressgasServicegasService状态变量是不变的,只能在合同部署期间设置。

  • 用提供的gasReceiver_地址初始化gasService变量。

  • 创建一个sendMessage函数,该函数采用三个字符串参数:destinationChaindestinationAddressvalue_,并使用本机燃气(以太煤气)使用gasService.payNativeGasForContractCall

  • 使用指定的destinationChaindestinationAddresspayload参数利用gateway合同的callContract功能。

  • _execute函数将有效载荷解码为value字符串,并更新状态变量sourceChainsourceAddress

设置部署脚本

接下来,在scripts文件夹中创建一个deploy.js文件,然后添加以下代码段:

// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");

async function main() {
  const SendMessage = await hre.ethers.getContractFactory("SendMessage");
  const sendMessage = await SendMessage.deploy(
    "",
    ""
  );

  await sendMessage.deployed();

  console.log(`SendMessage contract deployed to ${sendMessage.address}`);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

在上面的代码段中:

  • main功能具有使用hre.ethers.getContractFactory获得的SendMessage合同工厂。

  • 使用SendMessage.deploy方法以两个字符串作为参数来部署sendMessage合同。

  • await sendMessage.deployed()语句确保部署在继续前进之前已完成。

  • 已部署的合同的地址已记录到控制台。

设置远程过程调用(RPC)到TestNet

远程过程调用(RPC)是用于网络或区块链环境中客户端和服务器系统之间通信的协议。它使客户能够在远程服务器上执行过程或功能并接收结果。 RPC摘要基础网络详细信息,并允许客户在服务器上调用方法,就好像它们是本地的。

在继续设置RPC之前,请使用以下命令创建一个.env文件:

touch .env

在运行上面的命令之前,请确保您在硬汉目录中。

在您刚创建的.env文件中,添加以下键:

PRIVATE_KEY= // Add your account private key here

获取您的私人帐户密钥很容易。查看此post

接下来,通过更新hardhat.config.js文件,使用以下代码段来设置RPC和雪崩网络:

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config({ path: ".env" });
require("solidity-coverage");

const PRIVATE_KEY = process.env.PRIVATE_KEY;

// This is a sample Hardhat task. To learn how to create your own go to
// <https://hardhat.org/guides/create-task.html>
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

// You need to export an object to set up your config
// Go to <https://hardhat.org/config/> to learn more

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.9",
  networks: {
    bsc: {
      url: "<https://data-seed-prebsc-1-s1.binance.org:8545>",
      chainId: 97,
      accounts: [PRIVATE_KEY],
    },
    avalancheFujiTestnet: {
      url: "<https://avalanche-fuji-c-chain.publicnode.com>",
      chainId: 43113,
      accounts: [PRIVATE_KEY],
    },
  },
  mocha: {
    timeout: 10000000000,
  },
};

您已成功配置了binance和雪崩测试网络的RPC,您将在下一步中继续向这些网络进行智能合同部署。

将智能合约部署到binance和雪崩网络

在本节中,您将把智能合约部署到binance和Avalanche Testnet。但是,在进行继续之前,您需要在您之前创建的deploy.js文件中指定Axelar Gateway ServiceGas Service合同。

您可以找到当前所有链条Axelar支持here的Axelar Gas Service和Gateway Contracts列表。

您还需要一个用于二元和雪崩帐户的水龙头,以确保成功部署合同。要获取二元龙头,请访问此link,并获取雪崩水龙头,访问here

部署到Binance Testnet

更新scripts文件夹中的deploy.js文件,以使用以下代码段来部署到binance testnet:

//...

async function main() {
  //...

  // Update arguments with the Axelar gateway and
  // gas service on Binance testnet
  const sendMessage = await SendMessage.deploy(
    "0x4D147dCb984e6affEEC47e44293DA442580A3Ec0",
    "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6"
  );

  //...
}

//..

要在Binance TestNet上部署合同,请运行以下命令:

npx hardhat run scripts/deploy.js --network bsc

例如,合同地址将显示在您的控制台中:0xC1b8fC9208E51aC997895626b0f384153E94f2A7

部署到雪崩Fuji Testnet

更新scripts文件夹中的deploy.js文件,以部署到Avalanche testnet,并使用以下代码段:

//...

async function main() {
  //...

  // Update arguments with the Axelar gateway and
  // gas service on Avalanche testnet
  const sendMessage = await SendMessage.deploy(
    "0xC249632c2D40b9001FE907806902f63038B737Ab",
    "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6"
  );

  //...
}

//..

要在雪崩TestNet上部署合同,请运行以下命令:

npx hardhat run scripts/deploy.js --network avalancheFujiTestnet

合同地址将显示在您的控制台上;例如,0x2a03DCB9B24431d02839822209D58262f5e5df52。确保保存两个已部署的合同地址,因为您需要它们进行前端集成。

将NextJS前端应用程序与智能合约集成

在上一步中,您成功地构建并部署了智能合约。现在,是时候从前端与它进行交互,就像您通常会在网络上与分散的应用程序进行交互。

您已经有一个克隆的next.js前端项目,并且设置了WAGMIRainbowkit的配置。这意味着您可以继续更新现有应用程序并将您的智能合约连接到测试中。

实施写智能合同功能

WAGMIRainbowKitethers相互互动,与我们的合同相互作用非常简单。

使用以下命令在根目录中创建一个.env.local文件:

touch .env.local

在运行上面的命令之前,请确保您在根目录中。

在您刚创建的.env.local文件中,添加以下键:

NEXT_PUBLIC_AVALANCHE_RPC_URL=https://avalanche-fuji-c-chain.publicnode.com
NEXT_PUBLIC_BSC_CONTRACT_ADDRESS=<BSC_CONTRACT_ADDRESS>
NEXT_PUBLIC_AVALANCHE_CONTRACT_ADDRESS=<AVALANCHE_CONTRACT_ADDRESS>

<BSC_CONTRACT_ADDRESS>替换为合同地址,您已部署到Binance testnet,然后用合同地址替换<AVALANCHE_CONTRACT_ADDRESS>,您已将其部署到本教程早些时候的Avalanche Fuji Testnet。

接下来,实现智能合约的写入功能,将以下代码段添加到位于pages目录中的index.js文件中。

//...

const BSC_CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_BSC_CONTRACT_ADDRESS;
const AVALANCHE_CONTRACT_ADDRESS =
  process.env.NEXT_PUBLIC_AVALANCHE_CONTRACT_ADDRESS;
const AVALANCHE_RPC_URL = process.env.NEXT_PUBLIC_AVALANCHE_RPC_URL;

export default function Home() {
//...

const [message, setMessage] = useState(""); 
const [sourceChain, setSourceChain] = useState(""); 

const { config } = usePrepareContractWrite({ // Calling a hook to prepare the contract write configuration
  address: BSC_CONTRACT_ADDRESS, // Address of the BSC contract
  abi: SendMessageContract.abi, // ABI (Application Binary Interface) of the contract
  functionName: "sendMessage", // Name of the function to call on the contract
  args: ["Avalanche", AVALANCHE_CONTRACT_ADDRESS, message], // Arguments to pass to the contract function
  value: ethers.utils.parseEther("0.01"), // Value to send along with the contract call for gas fee
});

const { data: useContractWriteData, write } = useContractWrite(config); 

const { data: useWaitForTransactionData, isSuccess } = useWaitForTransaction({ 
  hash: useContractWriteData?.hash, // Hash of the transaction obtained from the contract write data
});

const handleSendMessage = () => {
  write(); // Initiating the contract call

  toast.info("Sending message...", {
    position: "top-right",
    autoClose: 5000,
    hideProgressBar: false,
    closeOnClick: true,
    pauseOnHover: false,
    draggable: true,
  });
};

useEffect(() => {
    const body = document.querySelector("body");
    darkMode ? body.classList.add("dark") : body.classList.remove("dark");

    isSuccess
      ? toast.success("Message sent!", {
          position: "top-right",
          autoClose: 7000,
          closeOnClick: true,
          pauseOnHover: false,
          draggable: true,
        })
      : useWaitForTransactionData?.error || useContractWriteData?.error
      ? toast.error("Error sending message")
      : null;
  }, [darkMode, useContractWriteData, useWaitForTransactionData]);

return (
    //..
   )
}

在上面的代码段中:

  1. 使用useState挂钩声明两个状态变量,messagesourceChain,分别持有消息内容和源链。

  2. 呼叫usePrepareContractWrite挂钩来准备合同写操作的配置。它需要几个参数,例如BSC合同地址,ABI,功能名称,参数和值。

  3. useContractWrite钩子根据从上一步获得的配置检索合同写数据和写功能。

  4. useWaitForTransaction钩被要求等待开采交易。它采用从合同写数据获得的交易的哈希。

  5. handleSendMessage函数被定义,该功能通过调用write函数来启动合同调用。

  6. useEffect在某些依赖性更改以显示成功或失败消息发送的吐司通知时,钩执行操作。

使用以下代码段更新Send按钮和textarea,以发送消息并检索要发送的数据。

//...

return (
    //...
    <div className="border border-gray-300 rounded-lg p-8 m-2 ">
        <h2 className="text-2xl font-bold mb-4">Send Message 📓 </h2>
        <textarea
            //...
            onChange={(e) => setMessage(e.target.value)}
        />
        <button
            //...
            onClick={() => handleSendMessage()}
         >
            Send
        </button>
     </div>
    //...
  );
}

实施阅读智能合同功能

在上一步中,您实现了从前端写入智能合约的功能。本节将教您如何实施从智能合约中读取数据的功能。

使用以下代码段更新index.js

//...

export default function Home() {
  //...

  const [value, setValue] = useState("");

  const provider = new ethers.providers.JsonRpcProvider(AVALANCHE_RPC_URL); // Create an instance of JsonRpcProvider with Avalanche RPC URL

  const contract = new ethers.Contract(
    AVALANCHE_CONTRACT_ADDRESS, 
    SendMessageContract.abi,

    provider
  );

  async function readDestinationChainVariables() {
    try {
      const value = await contract.value(); 
      const sourceChain = await contract.sourceChain();

      setValue(value.toString()); // Convert the value to a string and store it
      setSourceChain(sourceChain); // Store the source chain
    } catch (error) {
      toast.error("Error reading message"); // Display an error toast if reading fails
    }
  }

  useEffect(() => {
    readDestinationChainVariables(); // Call the function to read destination chain variables

    //...
  }, [darkMode, useContractWriteData, useWaitForTransactionData]);

  return (
    //...

        {value ? ( // Add value here
              <>
                //...
              </>
            ) : (
              <span className="text-red-500 ">waiting for response...</span>
         )}
  );
}

在上面的代码中,

  1. 从ethers.js库中的JsonRpcProvider类的实例是使用雪崩rpc url(AVALANCHE_RPC_URL)作为参数创建的。

  2. 创建了ethers.js库的Contract类的实例,代表雪崩网络上的合同。它采用参数,例如合同地址(AVALANCHE_CONTRACT_ADDRESS),ABI(SendMessageContract.abi)和提供商。

  3. 定义了一个名为readDestinationChainVariables的异步函数。它试图分别使用value()sourceChain()函数读取合同的价值和源链变量。

  4. 如果在阅读过程中发生错误,则显示带有消息“错误阅读消息”的吐司通知。

  5. 当某些依赖关系发生变化时,useEffect挂钩调用readDestinationChainVariables函数。

尝试应用程序

hurrayð¥³,您已经成功构建并部署了全链接链分散应用程序。

Build a Full Stack Interchain dApp with Next.js, Solidity & Axelar

您可以在Axelarscan Testnet here上找到GMP交易,并在GitHub上找到该项目的完整代码。

接下来是什么?

这篇文章涵盖了Axelar通过callContract传递的一般消息的利用,但这并不是传递的一般消息可以做的。

您总是可以探索其他功能

如果您做到了这么远,那就太棒了!您还可以发推文有关您的经验建设或跟随本教程,以向作者表示支持并标记@axelarcore

结论

本教程教您如何使用Next.js,Solidity和Axelar的一般消息传递来构建全栈互链分散的应用程序。您了解了如何部署和发送消息从Binance到Avalanche测试网络,并通过Next.js Frontend应用程序与它们进行交互。

参考