ETH 브릿징하기
이더리움에서 GIWA로, GIWA에서 이더리움으로 ETH를 브릿징해요.
요구사항
아래 항목들이 설치되어 있어야해요.
개발 환경 세팅
이 튜토리얼에서는 viem을 사용해요. Viem은 Node.js 라이브러리이기 때문에 Node.js 프로젝트로 생성합니다.
지갑 준비
ETH 브릿징을 위해 지갑이 필요해요.
세폴리아 ETH 준비
Deposit (Ethereum -> GIWA) 을 위해 이더리움 세폴리아 네트워크에서 ETH가 필요해요. 해당 faucet에서 세폴리아 ETH를 받을 수 있습니다.
Chain Client 설정
ETH 브릿징을 위해 chain client를 설정합니다.
import {defineChain, createPublicClient, http, createWalletClient} from "viem";
import {privateKeyToAccount} from "viem/accounts";
import {publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2} from "viem/op-stack";
import {sepolia} from "viem/chains";
// GIWA 세폴리아 체인 설정
export const giwaSepolia = defineChain({
id: 91342,
name: 'Giwa Sepolia',
nativeCurrency: { name: 'Sepolia Ether', symbol: 'ETH', decimals: 18 },
rpcUrls: {
default: {
http: ['https://sepolia-rpc.giwa.io'],
},
},
contracts: {
multicall3: {
address: '0xcA11bde05977b3631167028862bE2a173976CA11',
},
l2OutputOracle: {},
disputeGameFactory: {
[sepolia.id]: {
address: '0x37347caB2afaa49B776372279143D71ad1f354F6',
},
},
portal: {
[sepolia.id]: {
address: '0x956962C34687A954e611A83619ABaA37Ce6bC78A',
},
},
l1StandardBridge: {
[sepolia.id]: {
address: '0x77b2ffc0F57598cAe1DB76cb398059cF5d10A7E7',
},
},
},
testnet: true,
});
// 지갑 준비
export const PRIVATE_KEY = process.env.TEST_PRIVATE_KEY as `0x${string}`;
export const account = privateKeyToAccount(PRIVATE_KEY);
// 이더리움 세폴리아 체인 데이터를 읽기 위한 client
export const publicClientL1 = createPublicClient({
chain: sepolia,
transport: http(),
}).extend(publicActionsL1())
// 이더리움 세폴리아 체인에 트랜잭션을 전송하기 위한 client
export const walletClientL1 = createWalletClient({
account,
chain: sepolia,
transport: http(),
}).extend(walletActionsL1());
// GIWA 세폴리아 체인 데이터를 읽기 위한 client
export const publicClientL2 = createPublicClient({
chain: giwaSepolia,
transport: http(),
}).extend(publicActionsL2());
// GIWA 세폴리아 체인에 트랜잭션을 전송하기 위한 client
export const walletClientL2 = createWalletClient({
account,
chain: giwaSepolia,
transport: http(),
}).extend(walletActionsL2());
Deposit ETH (이더리움 -> GIWA)
이제 이더리움에서 GIWA로 ETH를 브릿징 해볼까요?
아래 코드를 실행하면 이더리움에 있던 여러분의 ETH가 실제로는 OptimismPortal 컨트랙트로 전송되고, 전송한 수량만큼 여러분의 GIWA 지갑으로 전송되는 것을 확인할 수 있어요. 이러한 방식을 Lock-and-Mint 라고 해요.
코드 작성하기
import {publicClientL1, publicClientL2, account, walletClientL1} from './config';
import {formatEther, parseEther} from "viem";
import {getL2TransactionHashes} from "viem/op-stack";
async function main() {
// GIWA로 브릿징 하기 전, 여러분의 레이어 1 지갑에서 ETH 잔고를 확인해요.
const l1Balance = await publicClientL1.getBalance({ address: account.address });
console.log(`L1 Balance: ${formatEther(l1Balance)} ETH`);
// 레이어 1에서 deposit 트랜잭션을 전송하기 위해 파라미터를 build 합니다.
const depositArgs = await publicClientL2.buildDepositTransaction({
mint: parseEther("0.001"),
to: account.address,
});
// 레이어 1에서 deposit 트랜잭션을 전송해요.
// 이 과정에서 여러분의 ETH가 OptimismPortal 컨트랙트로 전송됩니다.
const depositHash = await walletClientL1.depositTransaction(depositArgs);
console.log(`Deposit transaction hash on L1: ${depositHash}`);
// 위에서 전송한 레이어 1 트랜잭션이 완전히 처리될때까지 기다려요.
const depositReceipt = await publicClientL1.waitForTransactionReceipt({ hash: depositHash });
console.log('L1 transaction confirmed:', depositReceipt);
// 레이어 1 트랜잭션의 결과인 receipt를 통해 레이어 2에서 발생할 deposit 트랜잭션의 hash를 미리 계산해요.
const [l2Hash] = getL2TransactionHashes(depositReceipt);
console.log(`Corresponding L2 transaction hash: ${l2Hash}`);
// 위에서 계산한 레이어 2 deposit 트랜잭션이 처리될때까지 기다려요.
// 이 과정은 대략 1~3분정도 소요됩니다.
const l2Receipt = await publicClientL2.waitForTransactionReceipt({
hash: l2Hash,
});
console.log('L2 transaction confirmed:', l2Receipt);
console.log('Deposit completed successfully!');
}
main().then();
Withdraw ETH (GIWA -> 이더리움)
이더리움에서 GIWA로 ETH가 잘 전송되었나요? 이제 반대로 GIWA에서 이더리움으로 ETH를 브릿징해요.
아래 코드를 실행하면 GIWA에 있던 여러분의 ETH가 실제로는 L2ToL1MessagePasser 컨트랙트로 전송되고, 전송한 수량만큼 여러분의 이더리움 지갑으로 전송되는 것을 확인할 수 있어요. 이때는 Deposit에 의해 OptimismPortal 컨트랙트에 Lock되어있던 ETH가 다시 Unlock되는 형태에요. 이러한 방식을 Burn-and-Unlock 이라고 합니다.
코드 작성하기
import {publicClientL1, publicClientL2, account, walletClientL1, walletClientL2} from './config';
import {formatEther, parseEther} from "viem";
async function main() {
// 이더리움으로 브릿징하기 전, 여러분의 레이어 2 지갑에서 ETH 잔고를 확인해요.
const l2Balance = await publicClientL2.getBalance({ address: account.address });
console.log(`L2 Balance: ${formatEther(l2Balance)} ETH`);
// 레이어 2에서 withdrawal을 개시하는 트랜잭션을 전송하기 위해 파라미터를 build 합니다.
const withdrawalArgs = await publicClientL1.buildInitiateWithdrawal({
to: account.address,
value: parseEther("0.00005"),
});
// 레이어 2에서 withdrawal을 개시해요.
// 이 과정에서 여러분의 ETH가 L2ToL1MessagePasser 컨트랙트로 전송됩니다.
const withdrawalHash = await walletClientL2.initiateWithdrawal(withdrawalArgs);
console.log(`Withdrawal transaction hash on L2: ${withdrawalHash}`);
// 위에서 전송한 레이어 2 트랜잭션이 완전히 처리될때까지 기다려요.
const withdrawalReceipt = await publicClientL2.waitForTransactionReceipt({ hash: withdrawalHash });
console.log('L2 transaction confirmed:', withdrawalReceipt);
// 레이어 2 withdrawawl transaction을 레이어 1에서 증명할 수 있을때까지 기다려요.
// 이 과정은 최대 2시간이 소요될 수 있습니다.
const { output, withdrawal } = await publicClientL1.waitToProve({
receipt: withdrawalReceipt,
targetChain: walletClientL2.chain
});
// 레이어 1에서 withdrawal을 증명하는 트랜잭션을 전송하기 위해 파라미터를 build 합니다.
const proveArgs = await publicClientL2.buildProveWithdrawal({
output,
withdrawal,
});
// 레이어 1에서 withdrawal을 증명해요.
const proveHash = await walletClientL1.proveWithdrawal(proveArgs);
console.log(`Prove transaction hash on L1: ${proveHash}`);
// 위에서 전송한 레이어 1 트랜잭션이 완전히 처리될때까지 기다려요.
const proveReceipt = await publicClientL1.waitForTransactionReceipt({ hash: proveHash });
console.log('Prove transaction confirmed:', proveReceipt);
// Withdrawal을 finalize할 수 있을때까지 기다려요.
// 이 기간을 challenge period라고 부르며 대략 7일이 소요됩니다.
await publicClientL1.waitToFinalize({
targetChain: walletClientL2.chain,
withdrawalHash: withdrawal.withdrawalHash,
});
// 레이어 1에서 withdrawal 과정을 마무리해요.
const finalizeHash = await walletClientL1.finalizeWithdrawal({
targetChain: walletClientL2.chain,
withdrawal,
});
console.log(`Finalize transaction hash on L1: ${finalizeHash}`);
// 위에서 전송한 레이어 1 트랜잭션이 완전히 처리될때까지 기다려요.
const finalizeReceipt = await publicClientL1.waitForTransactionReceipt({
hash: finalizeHash
});
console.log('Finalize transaction confirmed:', finalizeReceipt);
// Withdrawal 상태를 레이어 1에서 읽어옵니다.
// Withdrawal은 완료되기까지 많은 시간이 소요되기 때문에 필요한 경우 해당 함수로 withdrawal 상태를 조회할 수 있어요.
const status = await publicClientL1.getWithdrawalStatus({
receipt: withdrawalReceipt,
targetChain: walletClientL2.chain,
});
console.log('Withdrawal completed successfully!');
}
main().then();
Withdrawal은 왜 이렇게 오래걸리나요?
Prove 대기시간: 레이어 2에서 발생한 withdrawal 트랜잭션을 레이어 1에서 증명하기 위해서는 해당 레이어 2 트랜잭션이 포함된 dispute game이 레이어 1에서 개시되어야해요. 현재 GIWA는 최대 2시간마다 dispute game을 개시하기 때문에, withdrawal을 prove할 수 있을 때까지 최대 2시간이 소요될 수 있습니다.
Challenge Period: GIWA는 Optimistic Rollup 방식을 채택한 OP 스택(OP Stack) 을 기반으로 한 레이어 2에요. 이에 따라 dispute game, 즉 레이어 2의 특정 시점의 상태가 올바르다는 것을 확정 지을 수 있기까지 대략 7일이 소요됩니다. 때문에 withdrawal을 finalize할 수 있을 때까지 대략 7일이 소요될 수 있습니다.
더 알아보기
viem 문서를 통해 더 많은 가이드를 읽어보세요.
Last updated