LayerZero
소개
지난 몇 년간 블록체인 업계에서 독립적인 블록체인 시스템 간의 데이터와 토큰 전송은 큰 난제로 남아있었습니다. 하지만 LayerZero와 같은 크로스체인 메시징 프로토콜이 등장하면서 고립된 시스템을 안전하고 탈중앙화된 방식으로 연결하는 데 큰 진전이 있었습니다. 이제 여러 거래소에서 변환 과정을 거칠 필요 없이 한 번의 트랜잭션 호출로 토큰을 한 생태계에서 다른 생태계로 원활하게 전송할 수 있습니다.
이 가이드가 끝날 때쯤이면 LayerZero 옴니체인 OFTV1 컨트랙트를 사용하여 한 번의 트랜잭션 호출로 한 블록체인에서 다른 블록체인으로 토큰을 전송할 수 있을 것입니다.
전제조건
전체 프로젝트를 살펴보기 전에, 완성된 형태는 이 저장소에서 찾을 수 있다는 점을 알아두세요: crosschain-oftv1-example. 이 튜토리얼은 Hardhat을 사용하므로 Hardhat에 대한 사전 지식이 있으면 리포지토리 작동 방식을 이해하는 데 도움이 됩니다. 이 튜토리얼은 Hardhat 사용법에 대한 정보는 포함하지 않으며, 대신 스마트 컨트랙트에만 집중할 것입니다. 이 튜토리얼을 따라하려면 다음과 같은 전제 조건이 필요합니다:
- 새로운 Hardhat 프로젝트와 Hardhat 사용법에 대한 지식
- 종속성으로 OpenZeppelin 스마트 컨트랙트 설치가 필요합니다.
- LayerZero 스마트 컨트랙트 설치를 종속성으로 사용합니다.
두 종속성을 모두 설치하려면 다음과 같이 실행하면 됩니다:
npm install @openzeppelin/contracts @layerzerolabs/solidity-examples
LayerZero 옴니체인 컨트랙트
LayerZero(L0)는 옴니체인과 상호 운용 가능한 애플리케이션을 구축하기 위한 오픈소스 프로토콜입니다. L0는 서로 다른 체인 간에 토큰을 원활하게 전송하기 위한 두 가지 표준 옴니체인 컨트랙트를 제공합니다.
-
옴니체인 대체 가능한 토큰(OFT)
1.1. 옴니체인 대체 가능한 토큰(OFT) v1: OFT 표준은 EVM 체인만 지원합니다. 이 표준 에는 ProxyOFT.sol 확장자가 있습니다. 이미 배포된 ERC20을 OFT로 전환하려면 ProxyOFT.sol 확장자를 사용하시기 바랍니다.
1.2. 옴니체인 대체 가능한 토큰(OFT) v2: OFT 표준은 EVM과 비 EVM 체인을 모두 지원합니다. 이 표준은 ProxyOFTV2.sol 확장자를 가집니다. 이미 배포된 ERC20을 OFTV2로 전환하려면 ProxyOFTV2.sol 확장자를 사용하시기 바랍니다.
-
옴니체인 대체 불가능한 토큰
2.1. 옴니체인 대체 불가능한 토큰(ONFT721): 크로스 체인 NFT를 위한 ONFT721 표준. 이 표준의 확장자는 ProxyONFT721.sol입니다. 이미 배포된 ERC721을 ONFT721로 전환하려면 ProxyONFT721.sol 확장자를 사용하시기 바랍니다.
2.2. 옴니체인 대체 불가능한 토큰(ONFT1155): 크로스 체인 멀티 토큰을 위한 ONFT1155 표준입니다. 이 표준의 확장자는 ProxyONFT1155.sol입니다. 이미 배포된 ERC1155를 ONFT1155로 전환하려면 ProxyONFT1155.sol 확장자를 사용하시기 바랍니다.
시작하기
이 가이드에서는 EVM 체인에서 토큰을 원활하게 전송할 수 있는 옴니체인 대체 가능한 토큰(OFT) v1에 초점을 맞출 것입니다. 따라서 Hardhat 스마트 컨트랙트 개발 환경을 사용하여 Kaia Kairos(소스 체인)과 Polygon Mumbai(목적지 체인) 모두에 OFTv1 컨트랙트를 배포할 것입니다.
개발 환경 구성하기
1단계: 변수 구성
Hardhat 프로젝트는 사용자별 값이나 코드 저장소에 포함되지 않아야 하는 데이터에 대해 구성 변수를 사용할 수 있습니다.
예를 들어 PRIVATE_KEY를 구성하려면 hardhat.config.js
파일에서 이 작업을 수행합니다:
const PRIVATE_KEY = vars.get("PRIVATE_KEY");/** @type import('hardhat/config').HardhatUserConfig */ networks: { kairos: { url: `https://klaytn-baobab-rpc.allthatnode.com:8551`, accounts: [PRIVATE_KEY] }, mumbai: { url: `https://polygon-mumbai-pokt.nodies.app`, accounts: [PRIVATE_KEY] } }};
그런 다음 터미널에서 아래 명령을 실행하여 PRIVATE_KEY를 설정합니다:
npx hardhat vars set PRIVATE_KEY
다음으로 PRIVATE_KEY의 값을 입력합니다.
변수 구성에 대한 자세한 내용은 Hardhat 구성 변 수를 참조하세요.
2단계: Hardhat 설정 설정
이 구성을 hardhat.config.js
파일에 붙여넣습니다:
require("@nomicfoundation/hardhat-toolbox");const PRIVATE_KEY = vars.get("PRIVATE_KEY");/** @type import('hardhat/config').HardhatUserConfig */module.exports = { solidity: { compilers: [ { version: "0.8.0", }, { version: "0.8.9", }, { version: "0.8.20", }, ], }, networks: { kairos: { url: `https://klaytn-baobab-rpc.allthatnode.com:8551`, accounts: [PRIVATE_KEY] }, mumbai: { url: `https://polygon-mumbai-pokt.nodies.app`, accounts: [PRIVATE_KEY] } }};
이제 개발 환경이 모두 준비되었으니 크로스체인 토큰 스마트 컨트랙트를 작성해 보겠습니다.
OFTV1 스마트 컨트랙트 생성하기
이 섹션에서는 LayerZero Solidity 예시 라이브러리를 사용하여 크로스체인 토큰 스마트 컨트랙트를 부트스트랩할 것입니다. 다음 단계에 따라 크로스체인 토큰 스마트 컨트랙트를 만들어 보겠습니다:
1단계: 탐색기 창에서 컨트랙트 폴더를 선택하고 새 파일 버튼을 클릭한 후 'crosschain-tokens.sol'이라는 이름의 새 파일을 생성합니다.
2단계: 파일을 열고 다음 코드를 추가합니다:
// SPDX-License-Identifier: Unlicensedpragma solidity ^0.8.0;/* // https://layerzero.gitbook.io/docs/technical-reference/testnet/testnet-addresses Klaytn Baobab lzEndpointAddress = 0x6aB5Ae6822647046626e83ee6dB8187151E1d5ab chainId: 10150 deploymentAddress = Mumbai lzEndpointAddress = 0xf69186dfBa60DdB133E91E9A4B5673624293d8F8 chainId: 10109 deploymentAddress =*/import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@layerzerolabs/solidity-examples/contracts/token/oft/v1/OFTCore.sol";import "@layerzerolabs/solidity-examples/contracts/token/oft/v1/interfaces/IOFT.sol";contract CrossChainToken is OFTCore, ERC20, IOFT { constructor(address _lzEndpointAddress) ERC20("CrossChainTokens", "CCT") OFTCore(_lzEndpointAddress) Ownable(msg.sender) { if (block.chainid == 1001) { // Only mint initial supply on Baobab _mint(msg.sender, 1_000_000 * 10 ** decimals()); } } function supportsInterface(bytes4 interfaceId) public view virtual override(OFTCore, IERC165) returns (bool) { return interfaceId == type(IOFT).interfaceId || interfaceId == type(IERC20).interfaceId || super.supportsInterface(interfaceId); } function token() public view virtual override returns (address) { return address(this); } function circulatingSupply() public view virtual override returns (uint) { return totalSupply(); } function _debitFrom(address _from, uint16, bytes memory, uint _amount) internal virtual override returns(uint) { address spender = _msgSender(); if (_from != spender) _spendAllowance(_from, spender, _amount); _burn(_from, _amount); return _amount; } function _creditTo(uint16, address _toAddress, uint _amount) internal virtual override returns(uint) { _mint(_toAddress, _amount); return _amount; }}