티스토리 뷰
전체 코드
1. Foundry 프로젝트 초기화
forge init hyperlane-v3
2. 라이브러리 설치
$ forge install hyperlane-xyz/hyperlane-monorepo@main
$ forge install OpenZeppelin/openzeppelin-contracts@release-v4.9
$ forge install OpenZeppelin/openzeppelin-contracts-upgradeable@release-v4.9
3. 라이브러리 리맵핑
$ forge remappings > remappings.txt
- remappings.txt 파일 수정
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
ds-test/=lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
@hyperlane-v3/=lib/hyperlane-monorepo/solidity/
openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/
openzeppelin-contracts/=lib/openzeppelin-contracts/
4. CrosschainNft.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import {IMessageRecipient} from "@hyperlane-v3/contracts/interfaces/IMessageRecipient.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC721, ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract CrosschainNft is ERC721URIStorage, IMessageRecipient {
address public mailbox;
uint256 public tokenId;
error NotMailbox();
event Minted(address indexed to, uint256 indexed tokenId, string tokenURI);
modifier onlyMailbox() {
if (msg.sender != mailbox) {
revert NotMailbox();
}
_;
}
constructor(address _mailbox) ERC721("CrosschainNft", "CCNFT") {
mailbox = _mailbox;
}
function handle(
uint32 /*_origin*/,
bytes32 /*_sender*/,
bytes memory _body
) external payable onlyMailbox {
(address to, string memory tokenURI) = abi.decode(
_body,
(address, string)
);
uint256 _tokenId = ++tokenId;
_mint(to, _tokenId);
_setTokenURI(_tokenId, tokenURI);
emit Minted(to, _tokenId, tokenURI);
}
}
5. CrosschainNftRouter.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import {IMailbox} from "@hyperlane-v3/contracts/interfaces/IMailbox.sol";
import {IRouter} from "@hyperlane-v3/contracts/interfaces/IRouter.sol";
import {TypeCasts} from "@hyperlane-v3/contracts/libs/TypeCasts.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract CrosschainNftRouter is Ownable, IRouter {
error RouteAlreadyEnrolled();
error RouteNotFound();
error InvalidInputLength();
address public mailbox;
uint32[] private _innerDomains;
mapping(uint32 => bytes32) private _innerRouters;
constructor(address _mailbox) Ownable(msg.sender) {
mailbox = _mailbox;
}
function domains() external view override returns (uint32[] memory) {
return _innerDomains;
}
function routers(uint32 _domain) external view override returns (bytes32) {
return _innerRouters[_domain];
}
function enrollRemoteRouter(
uint32 _domain,
bytes32 _router
) external onlyOwner {
if (_innerRouters[_domain] != bytes32(0)) {
revert RouteAlreadyEnrolled();
}
_innerRouters[_domain] = _router;
_innerDomains.push(_domain);
}
function enrollRemoteRouters(
uint32[] calldata _domains,
bytes32[] calldata _routers
) external onlyOwner {
if (_domains.length != _routers.length) {
revert InvalidInputLength();
}
for (uint256 i = 0; i < _domains.length; i++) {
if (_innerRouters[_domains[i]] != bytes32(0)) {
revert RouteAlreadyEnrolled();
}
_innerRouters[_domains[i]] = _routers[i];
_innerDomains.push(_domains[i]);
}
}
function sendNft(
uint32 _domainId,
address _to,
string memory _tokenURI
) external payable {
bytes32 recipientAddress = _tryGetrecipientAddress(_domainId);
bytes memory messageBody = abi.encode(_to, _tokenURI);
IMailbox(mailbox).dispatch{value: msg.value}(
_domainId,
recipientAddress,
messageBody
);
}
function estimateFee(
uint32 _domainId,
address _to,
string memory _tokenURI
) external view returns (uint256) {
bytes32 recipientAddress = _tryGetrecipientAddress(_domainId);
bytes memory messageBody = abi.encode(_to, _tokenURI);
return _estimateFee(_domainId, recipientAddress, messageBody);
}
function _tryGetrecipientAddress(
uint32 _domainId
) internal view returns (bytes32 recipientAddress) {
recipientAddress = _innerRouters[_domainId];
if (recipientAddress == bytes32(0)) {
revert RouteNotFound();
}
}
function _estimateFee(
uint32 destinationDomain,
bytes32 recipientAddress,
bytes memory messageBody
) internal view returns (uint256) {
return
IMailbox(mailbox).quoteDispatch(
destinationDomain,
recipientAddress,
messageBody
);
}
}
6. 배포 준비
- .env 파일 생성
MUMBAI_RPC_URL=https://polygon-mumbai.g.alchemy.com/v2/<API_KEY>
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/<API_KEY>
ARBITRUM_RPC_URL=https://arb-sepolia.g.alchemy.com/v2/<API_KEY>
PRIVATE_KEY=<YOUR_PRIVATE_KEY>
SEPOLIA_ETHERSCAN_API_KEY=<YOUR_ETHERSCAN_API_KEY>
POLYGONSCAN_API_KEY=<YOUR_POLYGONSCAN_API_KEY>
- foundry.toml 파일 수정
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"
mumbai = "${MUMBAI_RPC_URL}"
[etherscan]
sepolia = { key = "${SEPOLIA_ETHERSCAN_API_KEY}" }
mumbai = { key = "${POLYGONSCAN_API_KEY}", url = "https://api-testnet.polygonscan.com/api" }
7. CrosschainNftRouter 배포
- Sepolia 네트워크에 배포 및 검증
- Sepolia에 배포된 Hyperlane Mailbox의 주소: 0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766
contract Deploy is Script {
function setUp() public {}
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);
CrosschainNftRouter instance = new CrosschainNftRouter(
0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766
);
console.log("Deployed CrosschainNftRouter at: ", address(instance));
vm.stopBroadcast();
}
}
$ forge script script/CrosschainNftRouter.s.sol:Deploy --rpc-url sepolia --broadcast --verify -vvvv
...
Script ran successfully.
== Logs ==
Deployed CrosschainNftRouter at: 0xe996557C3A4D3586786359ccCaBe9f76842D1783
8. CrosschainNft 배포
- Polygon Mumbai 네트워크에 배포 및 검증
- Polygon Mumbai 에 배포된 Hyperlane Mailbox의 주소: 0x2d1889fe5B092CD988972261434F7E5f26041115
contract Deploy is Script {
function setUp() public {}
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);
CrosschainNft instance = new CrosschainNft(
0x2d1889fe5B092CD988972261434F7E5f26041115
);
console.log("Deployed CrosschainNft at: ", address(instance));
vm.stopBroadcast();
}
}
$ forge script script/CrosschainNftMumbai.s.sol:Deploy --rpc-url mumbai --broadcast --verify -vvvv
...
Script ran successfully.
== Logs ==
Deployed CrosschainNft at: 0x3716B00671B801f34bB4c99Aba5889A13d65c42E
9. CrosschainNftRouter를 통해 NFT 발행
- router 등록
- 가스비 계산
- NFT 전송
contract SendNFT is Script {
function setUp() public {}
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);
CrosschainNftRouter instance = CrosschainNftRouter(
0xe996557C3A4D3586786359ccCaBe9f76842D1783
);
uint32 domainId = 80001;
bytes32 recipientAddress = TypeCasts.addressToBytes32(
0x3716B00671B801f34bB4c99Aba5889A13d65c42E
);
instance.enrollRemoteRouter(domainId, recipientAddress);
string
memory uri = "https://green-main-hoverfly-930.mypinata.cloud/ipfs/QmXbFt5tDifdSgPmhFrwD56iNsJqbxCZ8dSdv4qzp49PNs";
uint256 fee = instance.estimateFee(domainId, vm.addr(privateKey), uri);
console.log("Estimated fee: ", fee);
instance.sendNft{value: fee}(domainId, vm.addr(privateKey), uri);
console.log("Sent NFT to domain: ", domainId);
vm.stopBroadcast();
}
}
$ forge script script/CrosschainNftRouter.s.sol:SendNFT --rpc-url sepolia --broadcast -vvvv
...
Script ran successfully.
== Logs ==
Estimated fee: 1
Sent NFT to domain: 80001
10. Hyperlane Explorer
11. 발행된 NFT 확인
contract CheckNFT is Script {
function setUp() public {}
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);
CrosschainNft instance = CrosschainNft(
0x3716B00671B801f34bB4c99Aba5889A13d65c42E
);
address owner = instance.ownerOf(1);
console.log("NFT Owner: ", owner);
string memory uri = instance.tokenURI(1);
console.log("NFT URI: ", uri);
vm.stopBroadcast();
}
}
$ forge script script/CrosschainNftMumbai.s.sol:CheckNFT --rpc-url mumbai -vvvv
...
Script ran successfully.
== Logs ==
NFT Owner: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF
NFT URI: https://green-main-hoverfly-930.mypinata.cloud/ipfs/QmXbFt5tDifdSgPmhFrwD56iNsJqbxCZ8dSdv4qzp49PNs
'개발 부스러기' 카테고리의 다른 글
Damn Vulnerable DeFi Foundry V3 업데이트 작업 (0) | 2024.02.28 |
---|---|
Solidity 런타임 바이트코드 분해하기 (0) | 2024.02.13 |
mdBook을 사용해 markdown 형식의 정적 웹사이트 만들기 (0) | 2024.01.21 |
WSL에서 Terraform 사용하기 (0) | 2023.12.27 |
WSL에서 Github 사용하기 (0) | 2023.12.26 |