# ETH 브릿징하기

### 요구사항

아래 항목들이 설치되어 있어야해요.

* [node](https://nodejs.org/ko)
* [pnpm](https://pnpm.io/)

### 개발 환경 세팅

이 튜토리얼에서는 viem을 사용해요. [Viem](https://viem.sh/)은 Node.js 라이브러리이기 때문에 Node.js 프로젝트로 생성합니다.

{% stepper %}
{% step %}

#### 프로젝트 폴더 생성

```bash
mkdir giwa-bridging-eth
cd giwa-bridging-eth
```

{% endstep %}

{% step %}

#### 프로젝트 초기화

```bash
pnpm init
```

{% endstep %}

{% step %}

#### Dependencies 설치

```bash
pnpm add -D tsx @types/node
pnpm add viem@^2.38.0
```

{% endstep %}
{% endstepper %}

### 지갑 준비

ETH 브릿징을 위해 지갑이 필요해요.

{% stepper %}
{% step %}

#### 세폴리아 ETH 준비

Deposit (Ethereum -> GIWA) 을 위해 이더리움 세폴리아 네트워크에서 ETH가 필요해요. [해당 faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia)에서 세폴리아 ETH를 받을 수 있습니다.

{% hint style="info" %}
아직 지갑이 없나요? [cast](https://getfoundry.sh/introduction/installation/) 커맨드로 지갑을 생성하세요.
{% endhint %}
{% endstep %}

{% step %}

#### Private Key 환경변수 세팅

이 튜토리얼에서는 여러 번의 트랜잭션 서명이 필요해요. 이를 위해 지갑 private key 환경변수를 세팅해야합니다.

```bash
export TEST_PRIVATE_KEY=0x...
```

{% endstep %}
{% endstepper %}

### Chain Client 설정

ETH 브릿징을 위해 chain client를 설정합니다.

{% code title="src/config.ts" lineNumbers="true" %}

```typescript
import {defineChain, createPublicClient, http, createWalletClient} from "viem";
import {privateKeyToAccount} from "viem/accounts";
import {publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2} from "viem/op-stack";
import {sepolia, giwaSepolia} from "viem/chains";

// 지갑 준비
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());

```

{% endcode %}

### Deposit ETH (이더리움 -> GIWA)

이제 이더리움에서 GIWA로 ETH를 브릿징 해볼까요?&#x20;

아래 코드를 실행하면 이더리움에 있던 여러분의 ETH가 실제로는 [OptimismPortal](https://docs.giwa.io/network-information/contracts#l1-contracts) 컨트랙트로 전송되고, 전송한 수량만큼 여러분의 GIWA 지갑으로 전송되는 것을 확인할 수 있어요. 이러한 방식을 **Lock-and-Mint** 라고 해요.

{% stepper %}
{% step %}

#### 구성

1. 레이어 1에서 deposit 트랜잭션 전송
2. 이에 대응되는 레이어 2 deposit 트랜잭션이 sequencer에 의해 생성
3. Deposit 완료
   {% endstep %}

{% step %}

#### 코드 작성하기

{% code title="src/deposit\_eth.ts" lineNumbers="true" %}

```typescript
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();

```

{% endcode %}
{% endstep %}

{% step %}

#### 실행하기

```bash
node --import=tsx src/deposit_eth.ts
```

{% endstep %}
{% endstepper %}

{% hint style="info" %}
왜 레이어 1 deposit 트랜잭션을 전송하고, 레이어 2 지갑 잔고에 반영되기까지 수 분이 소요되나요?

여러분이 레이어 1 deposit 트랜잭션을 전송하면, 레이어 2 sequencer가 이를 확인하고 레이어 2 deposit 트랜잭션을 생성해요. 이 과정에서 레이어 1 체인이 reorg가 발생할 수 있기 때문에 시스템 안정성을 위해 레이어 2 sequencer는 레이어 1 deposit 트랜잭션이 발생하고 대략 N 블록 이후에 이를 처리하는 형태입니다.
{% endhint %}

### Withdraw ETH (GIWA -> 이더리움)

이더리움에서 GIWA로 ETH가 잘 전송되었나요? 이제 반대로 GIWA에서 이더리움으로 ETH를 브릿징해요.

아래 코드를 실행하면 GIWA에 있던 여러분의 ETH가 실제로는 [L2ToL1MessagePasser](https://docs.giwa.io/network-information/contracts#l2-contracts) 컨트랙트로 전송되고, 전송한 수량만큼 여러분의 이더리움 지갑으로 전송되는 것을 확인할 수 있어요. 이때는 Deposit에 의해 [OptimismPortal](https://docs.giwa.io/network-information/contracts#l1-contracts) 컨트랙트에 Lock되어있던 ETH가 다시 Unlock되는 형태에요. 이러한 방식을 **Burn-and-Unlock** 이라고 합니다.

{% hint style="info" %}
L2ToL1MessagePasser 컨트랙트로 전송된 ETH는 사실상 소각(burn)되었다고 볼 수 있어요. Balance를 조회하면 남아있지만 언제든 해당 컨트랙트의 `burn()` 함수를 호출하면 모든 balance가 소각되는 구조에요.
{% endhint %}

{% stepper %}
{% step %}

#### 구성

1. 레이어 2에서 withdrawal 개시 트랜잭션 전송
2. 레이어 1에서 withdrawal 증명 트랜잭션 전송
3. 레이어 1에서 withdrawal 완료 트랜잭션 전송
4. Withdrawal 완료
   {% endstep %}

{% step %}

#### 코드 작성하기

{% code title="src/withdraw\_eth.ts" lineNumbers="true" %}

```typescript
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();

```

{% endcode %}
{% endstep %}

{% step %}

#### 실행하기

```bash
node --import=tsx src/withdraw_eth.ts
```

{% endstep %}
{% endstepper %}

{% hint style="warning" %}
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일이 소요될 수 있습니다.
  {% endhint %}

## 더 알아보기

[viem 문서](https://viem.sh/op-stack)를 통해 더 많은 가이드를 읽어보세요.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.giwa.io/get-started/bridging/eth.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
