사전 요구 사항
컨트랙트 준비
이 가이드 전반에서 다음 예시 컨트랙트를 사용합니다:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract SimpleStorage {
string private storedData;
address public owner;
event DataStored(string data, address indexed by);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(string memory initialData) {
storedData = initialData;
owner = msg.sender;
emit DataStored(initialData, msg.sender);
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can perform this action");
_;
}
function setData(string memory data) public onlyOwner {
storedData = data;
emit DataStored(data, msg.sender);
}
function getData() public view returns (string memory) {
return storedData;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "New owner cannot be zero address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
Foundry로 배포하기
Foundry는 Rust 기반의 빠른 Ethereum 개발 툴킷입니다.
-
Foundry를 설치합니다:
curl -L https://foundry.paradigm.xyz | bash
.bashrc 또는 .zshrc 파일을 source 해야 할 수도 있습니다.
-
새 Foundry 프로젝트를 설정합니다:
foundryup
forge init my-plasma-project
cd my-plasma-project
-
Plasma 테스트넷 설정으로
foundry.toml을 업데이트합니다:
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.28"
optimizer = true
optimizer_runs = 200
[rpc_endpoints]
plasma_testnet = "https://testnet-rpc.plasma.to"
-
프로젝트 루트에
.env 파일을 생성합니다:
PRIVATE_KEY=your_private_key_here
RPC_URL=https://testnet-rpc.plasma.to
-
환경 변수를 로드합니다:
-
컨트랙트 코드를
src/SimpleStorage.sol로 저장한 후 배포합니다:
forge create src/SimpleStorage.sol:SimpleStorage \
--rpc-url $RPC_URL \
--private-key $PRIVATE_KEY \
--constructor-args "Hello, Plasma!"
Foundry는 배포 트랜잭션 해시와 컨트랙트 주소를 출력합니다:
[⠊] Compiling...
[⠢] Compiling 1 files with Solc 0.8.28
[⠆] Solc 0.8.28 finished in 124.81ms
Compiler run successful!
Warning: Dry run enabled, not broadcasting transaction
Contract: SimpleStorage
Transaction: {
"from": "0xbd828f7679656f8f830b89611c933017442f2ebf",
"to": null,
"maxFeePerGas": "0xf",
"maxPriorityFeePerGas": "0x1",
"gas": "0x69f18",
[...]
-
Foundry의 cast 도구를 사용하여 배포된 컨트랙트를 테스트합니다. 먼저 저장된 데이터를 읽습니다:
cast call 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
"getData()" \
--rpc-url $RPC_URL
-
그런 다음 데이터를 업데이트하고 다시 읽습니다:
# 저장된 데이터를 업데이트합니다.
cast send 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
"setData(string)" "Updated from Foundry" \
--private-key $PRIVATE_KEY \
--rpc-url $RPC_URL
# 저장된 데이터를 다시 읽습니다.
cast call 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
"getData()" \
--rpc-url $RPC_URL
다음과 같은 출력이 표시됩니다:
blockHash 0x68fbd2aaf1de9c577869056ca634f2103fa1695673a94d8c049d0b78d3733aac
blockNumber 1200423
contractAddress
cumulativeGasUsed 21712
effectiveGasPrice 8
from 0xBd828F7679656F8f830b89611C933017442F2EbF
gasUsed 21712
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1 (success)
transactionHash 0xcf5b1fcc8d19f7cf7992f6e6a8b3ade74e07afbd0da0b9fce26bda4c9503a12b
transactionIndex 0
type 2
blobGasPrice
blobGasUsed
to 0x742D35Cc6610c7532C8582D4C371aCb1D5F44D7F
0x
Hardhat으로 배포하기
Hardhat은 풍부한 플러그인을 지원하는 완전한 기능의 개발 프레임워크입니다.
- 다음 사항을 확인합니다:
- Node.js 20+ (최신 Hardhat 호환성에 필요)
- Ethereum 개발 경험
- 테스트넷 토큰이 있는 지갑(테스트넷 체인 상세 정보 참조)
Node.js 18 이하는 더 이상 Hardhat에서 지원되지 않으며 ES 모듈 호환성 문제를 일으킬 수 있습니다.
-
Node.js 버전을 확인합니다:
Node.js 18 이하를 사용하고 있다면 Node.js 20+로 업그레이드합니다:
# nvm 사용 (권장)
nvm install 20
nvm use 20
# 또는 NodeSource 저장소 사용 (Ubuntu/Debian)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
-
새 Hardhat 프로젝트를 초기화합니다:
mkdir my-plasma-hardhat-project
cd my-plasma-hardhat-project
npm init -y
npm install --save-dev hardhat@latest @nomicfoundation/hardhat-toolbox@latest
npm install dotenv@latest ethers@latest
npx hardhat init
호환성 문제가 발생하면 Hardhat 문서에서 최신 지원 버전을 확인하세요.
프롬프트가 나타나면 Create a JavaScript project를 선택합니다.
-
hardhat.config.js를 업데이트합니다:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
plasmaTestnet: {
url: process.env.RPC_URL,
chainId: 9746,
accounts: [process.env.PRIVATE_KEY],
gasPrice: 1000000000, // 1 gwei
}
},
etherscan: {
apiKey: {
plasmaTestnet: process.env.ETHERSCAN_API_KEY
},
customChains: [
{
network: "plasmaTestnet",
chainId: 9746,
urls: {
apiURL: "https://testnet.plasmascan.to/api",
browserURL: "https://testnet.plasmascan.to/"
}
}
]
}
};
-
프로젝트 루트에
.env 파일을 생성합니다:
PRIVATE_KEY=your_private_key_here
RPC_URL=https://testnet-rpc.plasma.to
ETHERSCAN_API_KEY=your_api_key_for_verification
-
scripts 디렉터리와 배포 스크립트를 생성합니다:
-
scripts/deploy.js를 생성합니다:
const { ethers } = require("hardhat");
async function main() {
// 컨트랙트 팩토리를 가져옵니다.
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
console.log("Deploying SimpleStorage contract...");
// 생성자 인수를 사용하여 컨트랙트를 배포합니다.
const simpleStorage = await SimpleStorage.deploy("Hello, Plasma from Hardhat!");
// 배포가 완료될 때까지 기다립니다.
await simpleStorage.waitForDeployment();
const contractAddress = await simpleStorage.getAddress();
console.log("SimpleStorage deployed to:", contractAddress);
console.log("Transaction hash:", simpleStorage.deploymentTransaction().hash);
// 트랜잭션이 포함될 때까지 몇 블록을 기다립니다.
console.log("Waiting for block confirmations...");
await simpleStorage.deploymentTransaction().wait(5);
console.log("✅ Deployment completed successfully!");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
-
컨트랙트 코드를
contracts/SimpleStorage.sol로 저장한 후 배포합니다:
npx hardhat run scripts/deploy.js --network plasmaTestnet
-
완료되었습니다!
Ethers.js로 배포하기
Ethers.js는 최소한의 프로그램 방식 배포 흐름을 제공합니다.
-
새 프로젝트를 생성합니다:
mkdir my-plasma-ethers-project
cd my-plasma-ethers-project
npm init -y
npm install ethers dotenv solc
-
프로젝트 루트에
.env 파일을 생성합니다:
PRIVATE_KEY=<your_private_key_here>
RPC_URL=https://testnet-rpc.plasma.to
ETHERSCAN_API_KEY=your_api_key_for_verification
-
Solidity 컨트랙트를 컴파일하기 위한
compile.js를 생성합니다:
const fs = require('fs');
const solc = require('solc');
require('dotenv').config();
// 컨트랙트 소스 코드를 읽습니다.
const contractSource = fs.readFileSync('SimpleStorage.sol', 'utf8');
// Solidity 컴파일러용 입력을 준비합니다.
const input = {
language: 'Solidity',
sources: {
'SimpleStorage.sol': {
content: contractSource,
},
},
settings: {
outputSelection: {
'*': {
'*': ['*'],
},
},
optimizer: {
enabled: true,
runs: 200,
},
},
};
// 컨트랙트를 컴파일합니다.
const output = JSON.parse(solc.compile(JSON.stringify(input)));
// 컴파일 오류를 확인합니다.
if (output.errors) {
output.errors.forEach((error) => {
console.error(error.formattedMessage);
});
}
// 컨트랙트 데이터를 추출합니다.
const contract = output.contracts['SimpleStorage.sol']['SimpleStorage'];
const abi = contract.abi;
const bytecode = contract.evm.bytecode.object;
// 컴파일 아티팩트를 저장합니다.
fs.writeFileSync('SimpleStorage.json', JSON.stringify({
abi: abi,
bytecode: bytecode
}, null, 2));
console.log('Contract compiled successfully!');
-
컨트랙트를
SimpleStorage.sol로 저장한 후 컴파일합니다:
다음과 같이 출력됩니다:
Contract compiled successfully!
-
deploy.js를 생성합니다:
const { ethers } = require('ethers');
const fs = require('fs');
require('dotenv').config();
async function deploy() {
// 프로바이더와 지갑을 설정합니다.
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
console.log('Deploying from account:', wallet.address);
// 계정 잔액을 확인합니다.
const balance = await provider.getBalance(wallet.address);
console.log('Account balance:', ethers.formatEther(balance), 'XPL');
// 컴파일된 컨트랙트를 불러옵니다.
const contractData = JSON.parse(fs.readFileSync('SimpleStorage.json', 'utf8'));
// 컨트랙트 팩토리를 생성합니다.
const factory = new ethers.ContractFactory(
contractData.abi,
contractData.bytecode,
wallet
);
// 컨트랙트를 배포합니다.
console.log('Deploying contract...');
const contract = await factory.deploy("Hello, Plasma from Ethers.js!", {
gasLimit: 500000, // 적절한 가스 한도를 설정합니다.
gasPrice: ethers.parseUnits('1', 'gwei'), // 1 gwei 가스 가격.
});
// 배포를 기다립니다.
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log('Contract deployed to:', contractAddress);
console.log('Transaction hash:', contract.deploymentTransaction().hash);
// 배포 정보를 저장합니다.
const deploymentInfo = {
contractAddress: contractAddress,
transactionHash: contract.deploymentTransaction().hash,
deployer: wallet.address,
network: 'plasmaTestnet',
timestamp: new Date().toISOString()
};
fs.writeFileSync('deployment.json', JSON.stringify(deploymentInfo, null, 2));
return contract;
}
async function interact(contract) {
console.log('\nInteracting with deployed contract...');
// 초기 데이터를 읽습니다.
const initialData = await contract.getData();
console.log('Initial data:', initialData);
// 데이터를 업데이트합니다.
const updateTx = await contract.setData("Updated via Ethers.js");
await updateTx.wait();
console.log('Data updated. Transaction hash:', updateTx.hash);
// 업데이트된 데이터를 읽습니다.
const updatedData = await contract.getData();
console.log('Updated data:', updatedData);
// 소유자를 가져옵니다.
const owner = await contract.owner();
console.log('Contract owner:', owner);
}
// 메인 실행.
deploy()
.then(async (contract) => {
await interact(contract);
console.log('\nDeployment and interaction completed successfully!');
})
.catch((error) => {
console.error('Error:', error);
process.exit(1);
});
-
배포 스크립트를 실행합니다:
다음과 같이 출력됩니다:
Deploying from account: 0xBd828F7679656F8f830b89611C933017442F2EbF
Account balance: 98539.897261933991888304 XPL
Deploying contract...
Contract deployed to: 0xf298A2A7BC526F9228B8C422D38f3c2E0D15449F
Transaction hash: 0x3d16cc55b9148bac9ac20981d9748a0fc89861c6beb92a2f869bb09b75f685b2
Interacting with deployed contract...
Initial data: Hello, Plasma from Ethers.js!
Data updated. Transaction hash: 0xe3328862ece5d0ca4ca84d25c4130d6749ec872c24bf76eea23dfa8c50c22505
Updated data: Updated via Ethers.js
Contract owner: 0xBd828F7679656F8f830b89611C933017442F2EbF
Deployment and interaction completed successfully!
배포 검증
위의 방법 중 하나로 배포한 후 컨트랙트 배포를 검증하세요.
문제 해결
ES 모듈 호환성 문제
Error [ERR_REQUIRE_ESM]: require() of ES Module과 같은 오류가 발생한다면 일반적으로 다음을 의미합니다:
-
Node.js 버전이 너무 오래됨: 사전 요구 사항에 명시된 대로 Node.js 20+로 업그레이드하세요.
-
종속성 버전 불일치: Node.js 업그레이드가 불가능하다면 문제가 되는 종속성을 다운그레이드할 수 있습니다:
npm install micro-eth-signer@0.10.0
Node.js 버전 경고
지원되지 않는 Node.js 버전에 대한 경고가 표시되는 경우:
WARNING: You are currently using Node.js v18.19.1, which is not supported by Hardhat
사전 요구 사항 섹션의 단계를 따라 Node.js 20+로 업그레이드하세요.
자금 부족
Error: insufficient funds for intrinsic transaction cost
지갑에 충분한 테스트넷 토큰이 있는지 확인하세요. 테스트넷 포셋을 방문하세요.
잘못된 네트워크 구성
Error: network with chainId "1" doesn't match the configured chainId "9746"
네트워크 구성이 테스트넷 체인 상세 정보와 일치하는지 확인하세요.
가스 추정 실패
Error: cannot estimate gas
배포 구성에 명시적인 가스 한도를 설정하세요:
// 배포 스크립트에서
const simpleStorage = await SimpleStorage.deploy("Hello, Plasma from Hardhat!", {
gasLimit: 500000
});