# Documentation

<figure><img src="/files/uv9r2APP4F1XvrdYDOEy" alt=""><figcaption></figcaption></figure>

디카르고 (dKargo)는 아비트럼(Layer2) 위에 구축된 EVM 기반의 Layer3 블록체인입니다.&#x20;

이더리움에서 수행하는 모든 작업 (컨트랙트 배포, web3.js, hardhat 등)을 디카르고 메인넷에서 동일하게 진행할 수 있으며, 트랜잭션 전송은 더욱 저렴하고 빠릅니다.

본 문서는 디카르고 네트워크에 접근하기 위한 Node 구축 가이드, Snapshot, RPC URI, Validator 등에 대해 안내합니다. 또한, 테스트를 위한 사용 방법을 구체적인 예시로 설명며, 컨트랙트 개발을 위한 프레임워크에 대해 공유합니다.

### ❖ 노드 구축

{% content-ref url="/pages/9GXWI5WmuAL4eiQ491Yl" %}
[체인 RPC](/undefined/rpc)
{% endcontent-ref %}

{% content-ref url="/pages/etmHf2J2LebQFJ41iSeL" %}
[공통 설치 가이드](/undefined/undefined)
{% endcontent-ref %}

{% content-ref url="/pages/Go8k3IBmQKn1nNLRnTvP" %}
[체인 노드 종류](/undefined/undefined-1)
{% endcontent-ref %}

{% content-ref url="/pages/HWn0mVCYQnRBhv8BuoSz" %}
[풀 노드](/undefined/undefined-2)
{% endcontent-ref %}

{% content-ref url="/pages/GtyE9NmajrJwimE4Xy8V" %}
[아카이브 풀 노드](/undefined/undefined-3)
{% endcontent-ref %}

{% content-ref url="/pages/qykWPOpEichkWkI6K4dW" %}
[밸리데이터 노드](/undefined/undefined-4)
{% endcontent-ref %}

### 지갑 설정

{% content-ref url="/pages/mfhLJ3PvDzwsln2sSxVm" %}
[MetaMask 연결](/undefined-1/metamask)
{% endcontent-ref %}

{% content-ref url="/pages/xxqA2GvN13TOdty5dH8H" %}
[지갑 생성](/undefined-1/undefined)
{% endcontent-ref %}

{% content-ref url="/pages/5wSYBeraUKbPoI5tXybG" %}
[지갑 가져오기](/undefined-1/undefined-1)
{% endcontent-ref %}

{% content-ref url="/pages/Yv66sKPlaARAFzXJMyLv" %}
[네트워크 추가](/undefined-1/undefined-2)
{% endcontent-ref %}

### 밸리데이터 운영

{% content-ref url="/pages/bV6PowGSo4Gp6DjhIVVl" %}
[밸리데이터 스테이킹](/undefined-2/undefined)
{% endcontent-ref %}

{% content-ref url="/pages/1oRN4gmRtyeLz3JVntcm" %}
[스테이킹](/undefined-2/undefined-1)
{% endcontent-ref %}

{% content-ref url="/pages/sozmWFyZD0DgsXS53tNI" %}
[언스테이킹](/undefined-2/undefined-2)
{% endcontent-ref %}

{% content-ref url="/pages/lg2FVbG9xrrGQ6i01CoL" %}
[클레임](/undefined-2/undefined-3)
{% endcontent-ref %}

### 컨트랙트 배포

{% content-ref url="/pages/YIyLFEjXyLH8AIfxvXAD" %}
[컨트랙트 배포 방식](/undefined-3/undefined)
{% endcontent-ref %}

{% content-ref url="/pages/mEVgBAi6mFqrnMZji3xj" %}
[Remix-IDE를 이용한 컨트랙트 배포](/undefined-3/remix-ide)
{% endcontent-ref %}

{% content-ref url="/pages/e4UMWRHvWKv5qlq1PKvp" %}
[Hardhat을 이용한 컨트랙트 배포](/undefined-3/hardhat)
{% endcontent-ref %}

{% content-ref url="/pages/uso5vo83wo1Y3B0HPuDO" %}
[Foundry를 이용한 컨트랙트 배포](/undefined-3/foundry)
{% endcontent-ref %}

### ERC-20 BRIDGING

{% content-ref url="/pages/FGnHygr7nzD0gBBLr4EN" %}
[ERC-20 브릿징이란?](/erc-20/erc-20)
{% endcontent-ref %}

{% content-ref url="/pages/OcJVbXzAK0AR9Zd4Nixq" %}
[표준 게이트웨이](/erc-20/undefined)
{% endcontent-ref %}

{% content-ref url="/pages/E87hjRIqDC66xjl2pu7Z" %}
[범용적 커스텀 게이트웨이](/erc-20/undefined-1)
{% endcontent-ref %}

{% content-ref url="/pages/679QXqDoMh5KwbFik9ah" %}
[커스텀 게이트웨이](/erc-20/undefined-2)
{% endcontent-ref %}

### DKA 브릿징

{% content-ref url="/pages/yM0oggGMarpMGMXKFPMt" %}
[DKA 브릿징이란?](/dka/dka)
{% endcontent-ref %}

{% content-ref url="/pages/WX5bbHjs4wTJ9YEgjI3q" %}
[DKA 입금](/dka/dka-1)
{% endcontent-ref %}

{% content-ref url="/pages/2U8AInxwjoQm8F4cDa0B" %}
[DKA 출금](/dka/dka-2)
{% endcontent-ref %}

### 포우셋

{% content-ref url="/pages/xU34lUJfga02GfmYm4Ig" %}
[포우셋이란?](/undefined-4/undefined)
{% endcontent-ref %}

{% content-ref url="/pages/cefvVm4opxyIAfJwn50z" %}
[포우셋 사용하기](/undefined-4/undefined-1)
{% endcontent-ref %}

### 체인 스냅샷

{% content-ref url="/pages/a1w6lvo2cVQOiMcxDAim" %}
[체인 스냅샷 다운로드](/undefined-5/undefined)
{% endcontent-ref %}

### 컨트랙트 주소

{% content-ref url="/pages/WR4hqqyw65oQ9k4a3WgJ" %}
[디카르고 컨트랙트 주소](/undefined-6/undefined)
{% endcontent-ref %}


# 체인 RPC

<figure><img src="/files/uIWBzRSCPWoIcfMFIa8J" alt=""><figcaption></figcaption></figure>

디카르고 체인 (dKargo Chain)에 상호작용을 하거나 애플리케이션을 구축하려면 해당 디카르고 체인 노드 (dKargo Chain Node)에 액세스해야 합니다.

디카르고 체인 RPC (원격 프로시저 호출)는 개발자와 애플리케이션이 프로그래밍 방식으로 디카르고 체인 노드와 상호 작용을 할 수 있는 JSON-RPC 인터페이스입니다.&#x20;

이를 통해 블록체인 데이터 (예: 잔액, 블록, 트랜잭션) 쿼리 및 쓰기 작업 (예: 컨트랙트 배포 또는 트랜잭션 전송) 제출과 같은 작업을 수행할 수 있습니다.&#x20;

EVM과 호환되는 디카르고 체인 RPC는 이더리움과 유사한 방법을 지원하므로, 상호 작용을 위해 ethers.js 또는 Web3.js와 같은 도구를 쉽게 사용할 수 있습니다.

### RPC Providers

<table><thead><tr><th width="109.03515625">Name</th><th width="340.015625">RPC Urls(s)</th><th width="109.3125">Chain Id</th><th>Underlying chain</th></tr></thead><tbody><tr><td>dKargo<br>(Mainnet)</td><td>https://mainnet-rpc.dkargo.io</td><td>61022894</td><td>arbitrum One</td></tr><tr><td>Warehouse<br>(Testnet)</td><td>https://rpc.warehouse.dkargo.io</td><td>61022448</td><td>arbitrum sepolia</td></tr></tbody></table>


# 공통 설치 가이드

<figure><img src="/files/4JTXX7TJfAj7ASWd3Sl4" alt=""><figcaption></figcaption></figure>

## L2 Arbitrum RPC EndPoints

디카르고의 부모 체인인 아비트럼 (Arbitrum)과 통신하기 위한 URL 정보입니다.

<table><thead><tr><th width="230.3203125">Name</th><th width="297.984375">RPC Url</th><th>Chain Id</th></tr></thead><tbody><tr><td>Arbitrum One</td><td><a href="https://arb1.arbitrum.io/rpc	">https://arb1.arbitrum.io/rpc	</a></td><td>42161</td></tr><tr><td>Arbitrum Sepolia (Testnet)</td><td><a href="https://sepolia-rollup.arbitrum.io/rpc	">https://sepolia-rollup.arbitrum.io/rpc	</a></td><td>421614</td></tr></tbody></table>

> 속도 제한을 받지 않으려면 Arbitrum One RPC URL을 개인 URL로 바꾸세요.

## Docker Images

디카르고 노드를 실행하기 위한 Docker Images 경로입니다.

<table><thead><tr><th width="120.19140625">Type</th><th width="305.9921875">테스트넷</th><th>메인넷</th></tr></thead><tbody><tr><td>풀 노드</td><td>repo.dkargo.io:59083/warehouse-fullnode:v0.0.1</td><td>repo.dkargo.io:59083/mainnet-fullnode:v0.0.1</td></tr><tr><td>아카이브 노드</td><td>repo.dkargo.io:59083/warehouse-archive:v0.0.</td><td>repo.dkargo.io:59083/mainnet-archivenode:v0.0.1</td></tr><tr><td>밸리데이터</td><td>repo.dkargo.io:59083/warehouse-validator:v0.0.1</td><td>repo.dkargo.io:59083/mainnet-validator:v0.0.1</td></tr></tbody></table>

## L3 RPC EndPoints

디카르고 체인으로부터 정보를 받거나 트랜잭션을 전송하기 위한 노드 정보입니다.

<table><thead><tr><th width="156.75">Type</th><th>테스트넷</th><th>메인넷</th></tr></thead><tbody><tr><td>https endpoint</td><td>https://rpc.warehouse.dkargo.io</td><td>https://mainnet-rpc.dkargo.io</td></tr><tr><td>wss endpoint</td><td>wss://ws.warehouse.dkargo.io</td><td>wss://mainnet-ws.dkargo.io</td></tr></tbody></table>

## **Node Port**

노드와 통신하기 위한 포트 정보입니다. 기본적으로 아래와 같은 정보로 설정되어 있습니다.

* 8547: http
* 8548: websocket

## Environment

* `LOCAL_DATA_PATH`: 노드 데이터가 저장될 로컬 PC 경로입니다.
* `SNAPSHOT_URL`: 스냅샷 데이터가 제공되는 URL 경로입니다. 자세한 내용은 [체인 스냅샷](/undefined-5/undefined) 을 참고해주세요.

{% hint style="danger" %}
노드 구축 시 사용자 본인의 환경에 맞게 수동 입력 필요
{% endhint %}


# 체인 노드 종류

<figure><img src="/files/vufMs1B5XOZtubVyqyBu" alt=""><figcaption></figcaption></figure>

로컬에서 실행할 수 있는 디카르고 체인 노드 (dKargo Chain Noe)의 종류는 세 가지입니다.

### 노드 종류

{% content-ref url="/pages/HWn0mVCYQnRBhv8BuoSz" %}
[풀 노드](/undefined/undefined-2)
{% endcontent-ref %}

{% content-ref url="/pages/GtyE9NmajrJwimE4Xy8V" %}
[아카이브 풀 노드](/undefined/undefined-3)
{% endcontent-ref %}

{% content-ref url="/pages/qykWPOpEichkWkI6K4dW" %}
[밸리데이터 노드](/undefined/undefined-4)
{% endcontent-ref %}


# 풀 노드

풀 노드 (Full Node)는 체인의 상태를 관리하며, 다른 사용자들이 체인과 상호작용을 할 수 있는 API를 제공합니다.&#x20;

이는 사용자가 서비스 제공자로서 노드를 운영하려는 경우에 유용하게 활용됩니다.

## 1. 메인넷 (Mainnet)

### 1-1. Public L2 Arbitrum RPC EndPoints

디카르고의 부모 체인인 아비트럼 (Arbitrum)과 통신하기 위한 URL 정보입니다.

* [https://arb1.arbitrum.io/rpc](<https://arb1.arbitrum.io/rpc	>)

> 속도 제한을 받지 않으려면 Arbitrum One RPC URL을 개인 URL로 바꾸세요.

### 1-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/mainnet-fullnode:v0.0.1`</mark>

### 1-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://mainnet-rpc.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://mainnet-ws.dkargo.io`</mark>

### 1-4. Required Custom Options

* `--init.url`
  * 빠른 동기화를 위해 초기화 데이터를 다운로드할 스냅샷 URL

* `--parent-chain.connection.url`

  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

### 1-5. Running a Node via Docker

{% code overflow="wrap" %}

```shell
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-fullnode:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: Fullnode 데이터가 저장될 로컬 PC의 경로입니다.

### 1-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-fullnode:v0.0.1 --init.url=<SNAPSHOT_URL> 
```

{% endcode %}

> `SNAPSHOT_URL`: 스냅샷 데이터가 제공되는 URL 경로입니다. 자세한 내용은 [체인 스냅샷](/undefined-5/undefined) 을 참고해주세요.

### 1-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-fullnode:v0.0.1 --init.url="https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}

## 2. 웨어하우스 (Testnet)

웨어하우스 (Warehouse)란, 디카르고 메인넷 운영 전 시험을 해볼 수 있는 테스트 네트워크입니다.

### 2-1. Public L2 Arbitrum RPC EndPoints

디카르고의 부모 체인인 아비트럼 (Arbitrum)과 통신하기 위한 URL 정보입니다.

* [https://sepolia-rollup.arbitrum.io/rpc](https://sepolia-rollup.arbitrum.io/rpc**)

> 속도 제한을 받지 않으려면 Arbitrum One RPC URL을 개인 URL로 바꾸세요.

### 2-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/warehouse-fullnode:v0.0.1`</mark>

### 2-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://rpc.warehouse.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://ws.warehouse.dkargo.io`</mark>

### 2-4. Required Custom Options

* `--init.url`
  * 빠른 동기화를 위해 초기화 데이터를 다운로드할 스냅샷 URL

* `--parent-chain.connection.url`

  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

### 2-5. Running a Node via Docker

{% code overflow="wrap" %}

```shell
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-fullnode:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: Fullnode 데이터가 저장될 로컬 PC의 경로입니다.

### 2-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-fullnode:v0.0.1 --init.url=<SNAPSHOT_URL> 
```

{% endcode %}

> `SNAPSHOT_URL`: 스냅샷 데이터가 제공되는 URL 경로입니다. 자세한 내용은 [체인 스냅샷](/undefined-5/undefined) 을 참고해주세요.

### 2-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-fullnode:v0.0.1 --init.url="https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}


# 아카이브 풀 노드

아카이브 노드 (Archive Node)는 풀 노드 (Full Node)의 기능을 그대로 유지하면서도 체인의 모든 과거 기록(history)을 삭제하지 않고 보관하며, 디버그 인터페이스를 호출할 수 있습니다.&#x20;

이러한 특성 덕분에 아카이브 노드는 심층적인 데이터 분석이나 디버깅 작업이 있어야 하는 경우에 적합합니다.

## 1. 메인넷 (Mainnet)

### 1-1. Public L2 Arbitrum RPC EndPoints

디카르고의 부모 체인인 아비트럼 (Arbitrum)과 통신하기 위한 URL 정보입니다.

* [https://arb1.arbitrum.io/rpc](<https://arb1.arbitrum.io/rpc	>)

> 속도 제한을 받지 않으려면 Arbitrum One RPC URL을 개인 URL로 바꾸세요.

### 1-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/mainnet-archivenode:v0.0.1`</mark>

### 1-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://mainnet-rpc.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://mainnet-ws.dkargo.io`</mark>

### 1-4. Required Custom Options

* `--init.url`
  * 빠른 동기화를 위해 초기화 데이터를 다운로드할 스냅샷 URL

* `--parent-chain.connection.url`

  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

### 1-5. Running a Node via Docker

{% code overflow="wrap" %}

```shell
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-archivenode:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: Fullnode 데이터가 저장될 로컬 PC의 경로입니다.

### 1-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-archivenode:v0.0.1 --init.url=<SNAPSHOT_URL> 
```

{% endcode %}

> `SNAPSHOT_URL`: 스냅샷 데이터가 제공되는 URL 경로입니다. 자세한 내용은 [체인 스냅샷](/undefined-5/undefined) 을 참고해주세요.

### 1-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-archivenode:v0.0.1 --init.url="https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}

## 2. 웨어하우스 (Testnet)

웨어하우스 (Warehouse)란, 디카르고 메인넷 운영 전 시험을 해볼 수 있는 테스트 네트워크입니다.

### 2-1. Public L2 Arbitrum RPC EndPoints

디카르고의 부모 체인인 아비트럼 (Arbitrum)과 통신하기 위한 URL 정보입니다.

* [https://sepolia-rollup.arbitrum.io/rpc](https://sepolia-rollup.arbitrum.io/rpc**)

> 속도 제한을 받지 않으려면 Arbitrum One RPC URL을 개인 URL로 바꾸세요.

### 2-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/warehouse-archive:v0.0.1`</mark>

### 2-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://rpc.warehouse.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://ws.warehouse.dkargo.io`</mark>

### 2-4. Required Custom Options

* `--init.url`
  * 빠른 동기화를 위해 초기화 데이터를 다운로드할 스냅샷 URL

* `--parent-chain.connection.url`

  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

### 2-5. Running a Node via Docker

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-archive:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: Fullnode 데이터가 저장될 로컬 PC의 경로입니다.

### 2-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-archive:v0.0.1 --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: 스냅샷 데이터가 제공되는 URL 경로입니다. 자세한 내용은 [체인 스냅샷](/undefined-5/undefined) 을 참고해주세요.

### 2-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-archive:v0.0.1 --init.url="https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/archive/latest/archive.tar.gz"
```

{% endcode %}


# 밸리데이터 노드

밸리데이터 노드 (Validator Node)는 롤업 프로토콜의 진행 상황을 지켜보고 해당 프로토콜에 참여하여 체인 상태를 안전하게 발전시키는 역할을 합니다.

## 1. 구축 이전 준비 사항

밸리데이터 노드를 운영하기 위해서는 사전 준비 사항이 몇 가지 필요합니다. 밸리데이터로 사용하려 하는 계정 (Account), 해당 계정이 사용할 지갑 컨트랙트, 그리고 해당 지갑 컨트랙트에 대한 심사 및 권한 부여 과정이 필요합니다.

### 1-1. 사용자의 밸리데이터 지갑 컨트랙트 생성

사용자는 밸리데이터 노드를 가동하기 위해 본인의 계정을 통해서 사용자 스스로 컨트랙트 지갑을 생성해야 합니다.&#x20;

해당 컨트랙트는 생성을 진행한 계정를 통해서만 제어할 수 있습니다. 이 시점에서 밸리데이터는 아직 권한이 부여되지 않은 상태이며 추가 과정을 통해서 권한을 부여받아야 합니다.&#x20;

지갑 컨트랙트는 동일한 계정으로 여러 번 발행할 수 있지만 여러 지갑을 소유한 경우 해당 계정으로 노드를 가동할 수 없으니 주의하십시오. 지갑 생성은 아래 repository를 참고해 주세요.

* <https://github.com/dKargo/dkargo-validator-utils>

### 1-2. 허가 진행

생성한 지갑 컨트랙트의 주소 (Address)를 제출하여 권한을 부여받습니다. 디카르고는 해당 요청에 대해서 검토한 뒤 밸리데이터 권한을 부여합니다. 질문 사항이나 문의는 [디카르고 문의 사이트](https://dkargo.io/ko/about/contact)를 통해 전달해 주세요.

## 2. 메인넷 (Mainnet)

### 2-1. Public L2 Arbitrum RPC EndPoints

디카르고의 부모 체인인 아비트럼 (Arbitrum)과 통신하기 위한 URL 정보입니다.

* [https://arb1.arbitrum.io/rpc](<https://arb1.arbitrum.io/rpc	>)

> 속도 제한을 받지 않으려면 Arbitrum One RPC URL을 개인 URL로 바꾸세요.

### 2-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/mainnet-validator:v0.0.1`</mark>

### 2-3. Required Custom Options

* `--node.staker.parent-chain-wallet.private-key`
  * Validator를 운용할 지갑의 개인 키

* `--init.url`
  * 빠른 동기화를 위해 초기화 데이터를 다운로드할 스냅샷 URL

* `--parent-chain.connection.url`
  * 부모 체인 엔드포인트 URL(default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

### 2-4. Running a Node via Docker

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY>
```

{% endcode %}

> `LOCAL_DATA_PATH`:  밸리데이터 노드의 데이터가 저장될 로컬 PC의 경로입니다.\
> `PRIVATE_KEY` : 밸리데이터 노드를 운용할 지갑의 개인 키 입니다.

### 2-5. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: 스냅샷 데이터가 제공되는 URL 경로입니다. 자세한 내용은 [체인 스냅샷](/undefined-5/undefined) 을 참고해주세요.

### 2ㅁ-6. Example (with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url="https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}

## 3. 웨어하우스 (Testnet)

웨어하우스 (Warehouse)란, 디카르고 메인넷 운영 전 시험을 해볼 수 있는 테스트 네트워크입니다.

### 3-1. Public L2 Arbitrum RPC EndPoints

디카르고의 부모 체인인 아비트럼 (Arbitrum)과 통신하기 위한 URL 정보입니다.

* [https://sepolia-rollup.arbitrum.io/rpc](https://sepolia-rollup.arbitrum.io/rpc**)

> 속도 제한을 받지 않으려면 Arbitrum One RPC URL을 개인 URL로 바꾸세요.

### 3-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/warehouse-validator:v0.0.1`</mark>

### 3-3. Required Custom Options

* `--node.staker.parent-chain-wallet.private-key`
  * Validator를 운용할 지갑의 개인 키

* `--init.url`
  * 빠른 동기화를 위해 초기화 데이터를 다운로드할 스냅샷 URL

* `--parent-chain.connection.url`
  * 부모 체인 엔드포인트 URL(default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * 부모 체인 엔드포인트 URL (default: Arbitrum public RPC endpoints)

### 3-4. Running a Node via Docker

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY>
```

{% endcode %}

> `LOCAL_DATA_PATH`:  밸리데이터 노드의 데이터가 저장될 로컬 PC의 경로입니다.\
> `PRIVATE_KEY` : 밸리데이터 노드를 운용할 지갑의 개인 키 입니다.

### 3-5. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: 스냅샷 데이터가 제공되는 URL 경로입니다. 자세한 내용은 [체인 스냅샷](/undefined-5/undefined) 을 참고해주세요.

### 3-6. Example (with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url="https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}


# MetaMask 연결

메타마스크 (MetaMask)는 블록체인 지갑으로 다양한 체인에 연결하여 사용할 수 있습니다.&#x20;

메타마스크가 사용하는 지갑 체계는 비트코인 표준인 [BIP-39](https://en.bitcoin.it/wiki/BIP_0039)를 따라 생성되며 니모닉 (mnemonic), 혹은 시드 문구라는 문자열을 통해서 관리하게 됩니다.&#x20;

디카르고의 경우에도 이 주소 체계를 따르며 다양한 체인에서 호환이 가능합니다.


# 지갑 생성

해당 내용은 크롬 (Chrome) 브라우저를 기준으로 작성되었으며 메타마스크 확장 프로그램 (MetaMask extension)을 설치하는 과정을 설명합니다.

## STEP 1 - 설치

<https://metamask.io/> 에 접속하여 메타마스크를 설치합니다.

<figure><img src="/files/L2men9GEyvJgDCNu8xz6" alt="" width="563"><figcaption><p>우측 상단의 Add to Chrome을 클릭</p></figcaption></figure>

## STEP 2 - 실행

메타마스크를 실행한 후, 새 지갑 생성을 진행합니다. 약관 동의 이후 비밀번호를 설정합니다.

<figure><img src="/files/aZ8ZM1EiKx8RZvjB5iMK" alt=""><figcaption><p>새 지갑 생성 후 비밀번호 설정</p></figcaption></figure>

## STEP 3 - 니모닉 (mnemonic) 확인

니모닉을 확인하고 안전하게 기록한 뒤, 확인 절차를 통해 지갑 생성을 완료합니다.&#x20;

{% hint style="warning" %}
니모닉은 지갑에 대한 복구를 진행할때 사용되며 이를 공유하는 것은 지갑을 공유하는 것과 동일합니다. 또한, 분실 시 니모닉을 복구하는 것은 불가능하므로 안전하게 보관해 주세요.
{% endhint %}

<figure><img src="/files/DBjf94gLnkioD4D3lRDG" alt="" width="375"><figcaption></figcaption></figure>


# 지갑 가져오기

메타마스크 (MetaMask)에서는 외부에서 생성한 지갑을 가져와서 사용하는 것이 가능합니다.

지갑을 가져오기 위해 사용자의 개인키 (Private key), 혹은 키스토어 (Keystore) 파일이 필요합니다.

## STEP - 1 새로운 지갑 추가

지갑 목록을 선택하여 현재 지갑 목록을 확인합니다. 계정 또는 하드웨어 지갑 추가 선택하고, 계정 가져오기를 통해 새로운 지갑을 추가합니다.

이후 지갑을 불러올 방식에 따라 [개인키 입력](#step-2-private-key) 혹은 [키스토어 파일 불러오기](#step-3-keystore)의 단계를 진행합니다.

<figure><img src="/files/jNl2bcBzoWcTwqUOLC9N" alt=""><figcaption></figcaption></figure>

## STEP - 2 개인키 입력

유형 선택 중 비공개 키를 선택합니다. 사용자가 불러올 지갑의 개인키를 입력하여 지갑 가져오기를 완료합니다.

<figure><img src="/files/VjjwsUPaXdQsQjXbDF4m" alt="" width="346"><figcaption></figcaption></figure>

## STEP - 3 키스토어 파일 불러오기

유형 선택 중 JSON 파일을 선택합니다. 해당 키스토어를 생성할 때 입력했던 비밀번호를 입력 가져오기를 완료합니다.

<figure><img src="/files/fCIAf9p4YIO5qy9lEgMS" alt="" width="350"><figcaption></figcaption></figure>


# 네트워크 추가

메타마스크 (MetaMask)에서는 기본적으로 이더리움 (Ethereum) 및 몇몇 네트워크에 대한 연결을 기본적으로 진행합니다.&#x20;

사용자는 서비스 프로바이더 (Service Provider) 및 체인에 대한 정보를 제공함으로써 새로운 네트워크를 추가할 수 있습니다.&#x20;

아래 가이드 문서를 통해서 디카르고 네트워크 환경을 추가할 수 있습니다.

## 메인넷 (Mainnet)

{% hint style="info" %}
**2025년 내에 공개될 예정입니다.**
{% endhint %}

## 웨어하우스 (Testnet)

웨어하우스 (Warehouse)란, 디카르고 메인넷 운영 전 시험을 해볼 수 있는 테스트 네트워크입니다.

| Index           | Details                           |
| --------------- | --------------------------------- |
| Network Name    | Warehouse                         |
| RPC URL         | <https://rpc.warehouse.dkargo.io> |
| Chain ID        | 61022448                          |
| Currency Symbol | DKA                               |
| Block Explorer  | <https://warehouse.dscanner.io/>  |

#### Layer2 DKA Token Details

아비트럼 세폴리아에 배포된 DKA 토큰 정보입니다. 컨트랙트 정보를 통해 현재 Layer2에 보유중인 DKA 잔액을 조회할 수 있습니다.

* Name: dKargo
* Symbol: DKA
* Dicimals: 18
* Address : [0x6d9c6F7445C4D3a8fa3fC269f020e12eBF3ACD66](https://sepolia.arbiscan.io/token/0x6d9c6F7445C4D3a8fa3fC269f020e12eBF3ACD66)

## 네트워크 추가&#x20;

메타마스크에는 다양한 네트워크에 대한 접속을 제공하며, 제공된 네트워크 이외의 체인에 대해서도 추가, 삭제가 가능합니다.&#x20;

해당 내용은 메타마스크에서 네트워크를 추가하는 과정을 설명합니다.

### STEP 1 - 네트워크 목록 확인

네트워크 목록을 선택하여 현재 불러온(import) 환경들을 확인합니다.&#x20;

만약 디카르고 웨어하우스가 이미 추가되어 있다면, 네트워크 변경을 통해 이용할 수 있습니다.&#x20;

<figure><img src="/files/uPesf2DW3YTPvbjt7bUw" alt="" width="343"><figcaption></figcaption></figure>

### STEP 2 - 네트워크 추가

사용자 지정 네트워크 추가 혹은 Add a custom network를 선택하여 새로운 네트워크를 추가합니다.

앞서 제공된 [네트워크에 대한 상세 내용](#mainnet)을 입력하고 저장하여 네트워크를 추가합니다.

<figure><img src="/files/jCafwNHJJYOUNsL7MR6b" alt="" width="563"><figcaption></figcaption></figure>


# 밸리데이터 스테이킹

<figure><img src="/files/nQQApsSwaDxNjWl5QmXj" alt=""><figcaption></figcaption></figure>

스테이킹 (Staking)은 밸리데이터 (Validator)가 체인에 참여하고 악의적인 공격에 대응하기 위해 필요한 작업입니다.

디카르고 체인에서 발생한 데이터들에 대하여 악의적인 공격이 발생하는 경우 챌린지 (Challenge)를 위해 사용됩니다.


# 스테이킹

각각의 밸리데이터 (Validator)는 롤업을 통해서 전달된 배치(batch)에 대한 확인을 진행하게 되며 이 결과로써 스테이킹 (Staking)을 진행하고 이를 유지합니다.

밸리데이터 가동 시 정상 동작하였다면, 현재 롤업 컨트랙트에 생성된  RBlock에 스테이킹을 자동으로 시도합니다.

{% hint style="info" %}
스테이킹을 하기 위해서는 Git Repository에서 제공하는 코드를 사용자가 직접 실행해야 합니다.

* [https://github.com/dKargo/dkargo-validator-utils](<&#xA;https://github.com/dKargo/dkargo-validator-utils>)
  {% endhint %}

&#x20;

권한이 없다면 다음과 같은 에러가 발생합니다. 해당 경우 [디카르고 문의 사이트](https://dkargo.io/ko/about/contact)를 통해 알려주세요.

{% code overflow="wrap" %}

```jsx
WARN [12-12|02:46:42.480] validator address isn't whitelisted      address=0xf2A21a7EF1CD2e85b7Fb251d68ac98f523c5030b txSender=0xB31EbC1bAA9d165E5fbdF55DDfF60AC0a68f3362
```

{% endcode %}

다음과 같은 에러문구는 계정에 스테이킹을 위한 토큰이 부족한 경우에 발생합니다. 밸리데이터를 가동 시에 사용하는 계정에 ETH를 충전해 주세요.

{% code overflow="wrap" %}

```jsx
WARN [12-12|02:46:42.480] insufficient funds for intrinsic transaction cost
```

{% endcode %}


# 언스테이킹

언스테이킹 (Unstaking)은 밸리데이터 (Validator)를 운용하기 위해서 진행한 스테이킹 (Staking)을 회수하는 작업의 일부입니다.

작업을 수행하게 되면 롤업 컨트랙트에 제공했던 금액이 밸리데이터 지갑으로 이동합니다.&#x20;

이를 위해서 아래의 몇 가지 사항을 확인하고, 스크립트를 실행해야 합니다.

{% hint style="info" %}
언스테이킹을 하기 위해서는 Git Repository에서 제공하는 코드를 사용자가 직접 실행해야 합니다.

* [https://github.com/dKargo/dkargo-validator-utils](<&#xA;https://github.com/dKargo/dkargo-validator-utils>)
  {% endhint %}

## STEP 1 - 출금 가능 여부 확인

롤업 컨트랙트에 스테이킹한 금액을 출금하기 위해서는 현재 스테이킹중인 Rblock이 확정될 때까지 해당 스테이크(Stake)를 유지해야 합니다.&#x20;

즉, 마지막 스테이크 시점으로부터 6.4일 동안 스테이크를 이동하지 않아야 출금이 가능해집니다. 이를 위해서 밸리데이터 가동을 중지하고 출금 가능 시점까지 대기해야 합니다.

## STEP 2 - ETH 언스테이킹

출금 가능 시점 이후에 함수 호출을 통해서 스테이크 토큰을 언스테이크할 수 있습니다. 언스테이크된 ETH는 바로 지갑으로 이동되는 것이 아닙니다. 롤업 컨트랙트에 보관되어 withdrawableFunds 변수로 관리됩니다.

```jsx
const res = await rollup.returnOldDeposit(stakerWalletAddress);

const receipt = await res.wait();

console.log(receipt.transactionHash)
```


# 클레임

클레임 (Claim)은 밸리데이터 지갑 (Validator Wallet)에 들어있는 사용자의 토큰을 출금하는 과정입니다.&#x20;

앞서 진행한 언스테이킹 (unstaking)을 통해 지갑 컨트랙트로 이동한 ETH를 사용자의 지갑으로 이동시키는 과정입니다.

{% hint style="info" %}
Claim을 하기 위해서는 Git Repository에서 제공하는 코드를 사용자가 직접 실행해야 합니다.

* [https://github.com/dKargo/dkargo-validator-utils](<&#xA;https://github.com/dKargo/dkargo-validator-utils>)
  {% endhint %}

## STEP 1 - WithdrawStakerFunds

이전 과정을 통해서 롤업 컨트랙트에 WithrawableFunds로 보관 중인 ETH를 호출자의 밸리데이터 지갑 컨트랙트로 출금합니다. 해당 동작을 통해서 ETH는 밸리데이터 지갑 컨트랙트로 이동됩니다.

```jsx
const res = await rollup.withdrawStakerFunds();

const receipt = await res.wait();

console.log(receipt.transactionHash)
```

## STEP 2 - Withdraw ETH

밸리데이터 지갑 컨트랙트로 출금하였던 스테이킹용 ETH를 출금하는 과정입니다. 함수 호출을 통해서 밸리데이터 지갑에 보관된 ETH를 사용자 지갑으로 전송합니다.

```jsx
const withdrawReq = await walletContract.withdrawEth(
	ethers.utils.parseEther(withdrawAmount), wallet.address
);

const reciept = await withdrawReq.wait();

console.log(receipt.transactionHash)
```


# 컨트랙트 배포 방식

<figure><img src="/files/ObB5JmXHjD2iVX7sdzLY" alt=""><figcaption></figcaption></figure>

디카르고 체인에서 컨트랙트를 배포하는 방식은 세 가지가 존재합니다.

* Remix-IDE를 이용한 컨트랙트 배포
* HardHat을 이용한 컨트랙트 배포
* Foundry를 이용한 컨트랙트 배포


# Remix-IDE를 이용한 컨트랙트 배포

리믹스 (Remix Project)는 컨트랙트 개발의 전 과정에서 필요한 기술 및 기능을 개발자가 사용할 수 있도록 지원하는 개발 도구이며, 이더리움을 배우고 실험할 수 있는 교육 플랫폼으로도 사용됩니다.

{% hint style="info" %}
모든 블록체인에 컨트랙트를 배포할 때는 해당 체인의 네이티브 토큰을 사용하여 배포 가스 수수료를 지불해야 하며, 이는 디카르고도 마찬가지입니다. 디카르고 테스트넷 Warehouse에 배포하기 전에 [faucet](/undefined-4/undefined)을 통해 $DKA 테스트넷 토큰을 획득할 수 있습니다.

Remix는 [메타마스크](/undefined-1/undefined)에 등록된 지갑을 이용하여 블록체인과 상호작용을 합니다.&#x20;
{% endhint %}

## STEP 1 - 리믹스 시작

1. [리믹스 (Remix)](https://remix.ethereum.org/)에 접속합니다. **File Explorer**로 이동한 후 **≡** 를 선택합니다. 옵션 중에서 **\[ + Create Blank ]**&#xB97C; 선택하여 새로운  워크 스페이스 (Workspace)를 생성합니다.
2. 새 파일 <mark style="color:blue;">`counter.sol`</mark>을 생성하고 컨트랙트 코드를 입력하거나 아래 샘플 코드를 사용합니다.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
contract Counter {
    uint256 public number;
 
    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }
 
    function increment() public {
        number++;
    }
}
```

3. **\[ Solidity Compile ]** 사이드바 옵션으로 이동하여 <mark style="color:blue;">`counter.sol`</mark> 컴파일을 선택합니다.

## STEP 2 - 컨트랙트 배포

1. 사이드바 옵션 **\[ Deploy & run transactions ]** 으로 이동합니다.
2. 드롭 다운 **ENVIRONMENT**을 **"Injected Provider - MetaMask"**&#xB85C; 변경합니다.
3. 메타마스크에서 **\[ Connect ]** 을 눌러 리믹스 액세스를 허용합니다.

<figure><img src="/files/2Mk6VQr1c6Ev8wuKUZHY" alt="" width="563"><figcaption></figcaption></figure>

4. 메타마스크 연결이 완료되면 **\[ Deploy ]** 버튼을 클릭하여 컨트랙트를 배포합니다.

<figure><img src="/files/RzlayEjM8r9E4cTrRSLe" alt="" width="563"><figcaption></figcaption></figure>

5. 배포된 컨트랙트는[ 디스캐너 (dScanner)의 Verified Contracts](https://warehouse.dscanner.io/contracts-verified) 메뉴에서 확인할 수 있습니다.

## STEP 3 - 컨트랙트와 상호 작용

1. 컨트랙트를 배포한 후에는 왼쪽 아래 탭에서 컨트랙트와 상호 작용을 할 수 있습니다.

<figure><img src="/files/o2BAoJeSFVzdr9Vm0o95" alt="" width="563"><figcaption></figcaption></figure>

2. **\[ Number ]** 를 선택하여 컨트랙트에 저장된 숫자를 조회합니다. 컨트랙트 배포 후, 숫자는 0으로 초기화된 상태입니다.

<figure><img src="/files/8pAriL2y1181a8h4hfk0" alt="" width="279"><figcaption></figcaption></figure>

3. **\[ increment ]**&#xB97C; 클릭하여 숫자에 +1을 동작하는 트랜잭션을 전송합니다. 메타마스크에서 트랜잭션을 확인(Confirm)하여 트랜잭션을 디카르고 체인에 전송합니다.

<figure><img src="/files/Y5HM02EP8ZLyRGfHKvIm" alt="" width="563"><figcaption></figcaption></figure>

4. **\[ number ]** 를 선택하여 1이 증가한 숫자를 확인합니다.

<figure><img src="/files/vw08jJrZQINkIVqhU861" alt="" width="280"><figcaption></figcaption></figure>


# Hardhat을 이용한 컨트랙트 배포

하드햇 (Hardhat)은 이더리움 소프트웨어를 위한 개발 프레임워크입니다. 컨트랙트 디앱 개발, 컴파일, 디버깅, 배포 등 이더리움을 개발하기 위한 다양한 기능이 구성되어 있습니다.&#x20;

디카르고는 이더리움의 EVM과 상호 작용을 할 수 있기에, 디카르고 네트워크에 컨트랙트를 배포할 수 있습니다.

하드햇과 디카르고 웨어하우스를 사용하여 지갑 디앱 (Wallet dApp)을 구축하는 방법을 살펴보겠습니다.

{% hint style="info" %}
모든 블록체인에 컨트랙트를 배포할 때는 해당 체인의 네이티브 토큰을 사용하여 배포 가스 수수료를 지불해야 하며, 이는 디카르고도 마찬가지입니다. 디카르고 웨어하우스에 배포하기 전에 [포우셋 (faucet)](/undefined-4/undefined)을 통해 $DKA 테스트넷 토큰을 획득할 수 있습니다.
{% endhint %}

## 개발 환경

사전 준비 사항 (Prerequisites)

* **Node.js & npm/yarn**:
  * Installed from [Node.js](https://nodejs.org/).
* **Hardhat**: Development environment for Ethereum.

  * Install with:

  ```bash
  npm install --save-dev hardhat
  ```

## Step 1 - 하드햇 프로젝트 설정

1. 하드햇 프로젝트를 새로 초기화합니다.

```solidity
mkdir helloDka
cd helloDka
npx hardhat init
```

2. 추가로 개발에 필요한 [dotenv](https://github.com/motdotla/dotenv) 를 설치합니다.

```bash
npm i dotenv
touch .env
```

3. 다음은 하드햇 프로젝트의 폴더 구조에 대한 예시입니다.

```bash
/helloDka
├── README.md
├── contracts
|  └── Lock.sol
├── hardhat.config.js
├── ignition
|  └── modules
|     └── Lock.js
├── package-lock.json
├── package.json
└── test
   └── Lock.js
```

## Step 2 - Counter.sol 컨트랙트 작성

contracts 폴더 안에 <mark style="color:blue;">`Counter.sol`</mark> 파일을 생성한 다음 아래 코드를 복사하여 붙여 넣습니다.&#x20;

해당 코드는 변수 Number를 설정할 수 있는 간단한 기능이 구현되어 있습니다.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
contract Counter {
    uint256 public number;
 
    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }
 
    function increment() public {
        number++;
    }
}
```

## Step 3 - 이그니션 파일 업데이트

<mark style="color:blue;">`./ignition/modules`</mark> 폴더 안에 <mark style="color:blue;">`Counter.js`</mark> 파일을 생성한 다음 아래 코드를 복사하여 붙혀 넣습니다.&#x20;

해당 코드는[ 하드햇 이그니션(hardhat ignition) ](https://hardhat.org/ignition/docs/getting-started#overview)표준 코드이며, 컨트랙트 배포를 수행합니다.

```jsx
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
 
module.exports = buildModule("Counter", (m) => {
  const lock = m.contract("Counter");
 
  return { lock };
});
```

## Step 4 - 하드햇 설정

1. 필요한 플러그인 및 구성을 <mark style="color:blue;">`hardhat.config.js`</mark>에 작성합니다.
   * <mark style="color:blue;">`require('dotenv').config()`</mark> :  <mark style="color:blue;">`.env`</mark>를 불러옵니다.
   * accounts: \[process.env.PRIVATE\_KEY] : <mark style="color:blue;">`.env`</mark> 에 작성된 개인키를 전달합니다.

```jsx
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.24",
  networks: {
    warehouse: {
      url: "<https://it-full.dknote.net>",
      accounts: [process.env.PRIVATE_KEY],
    },
  },
  ignition: {
    requiredConfirmations: 1
  }
};

```

2. <mark style="color:blue;">`.env`</mark> 파일 안에 다음을 추가합니다. <mark style="color:blue;">`gitignore`</mark> 파일에 <mark style="color:blue;">`.env`</mark>가 추가되었는지 확인합니다.

```bash
PRIVATE_KEY=YOUR KEY HERE WITH NO QUOTES
```

## Step 5 - 컴파일과 배포

1. <mark style="color:blue;">`counter.sol`</mark> 을 컴파일합니다.

```bash
npx hardhat compile
```

2. <mark style="color:blue;">`counter.sol`</mark> 코드를 디카르고 웨어하우스에 배포합니다.

```bash
npx hardhat ignition deploy ./ignition/modules/Counter.js --network warehouse
```

## Step 6 - 배포 트랜잭션 확인

1. 배포된 컨트랙트의 트랜잭션 정보를 조회합니다.

```jsx
npx hardhat ignition transactions chain-2465001
```

2. [dScanner](https://warehouse.dscanner.io/)에서 트랜잭션 정보 검색할 수 있습니다.

## 컨트랙트 검증

이더리움이나 EVM 기반 블록체인에서 스마트 컨트랙트를 개발해 보셨다면, 아마 하드햇의 검증 (verify) 기능을 사용해 보셨을 겁니다.

하지만 저희 디카르고 체인은 물류 서비스에 최적화된 블록 익스플로러 (Explorer)인 [dScanner](https://warehouse.dscanner.io/)를 자체적으로 개발했기 때문에, 하드햇의 검증 기능과는 호환되지 않습니다.

대신, 여러분이 개발한 컨트랙트의 투명성을 보장하기 위해 [dScanner](https://warehouse.dscanner.io/)에서 제공하는 [Verify Contract](https://warehouse.dscanner.io/verify-contract)를 활용할 수 있습니다.


# Foundry를 이용한 컨트랙트 배포

파운드리 (Foundry)는 개발자가 명령 줄 인터페이스(CLI)를 통해 솔리디티 (Solidity)종속성을 관리하고, 컨트랙트를 컴파일, 테스트, 배포하며 블록체인과 상호 작용을 할 수 있도록 도와주는 러스트 (Rust)기반의 이더리움 개발 도구입니다.

파운드리과 디카르고 웨어하우스을 사용하여 지갑 디앱 (dApp)을 구축하는 방법을 살펴보겠습니다.

{% hint style="info" %}
모든 블록체인에 컨트랙트를 배포할 때는 해당 체인의 네이티브 토큰을 사용하여 배포 가스 수수료를 지불해야 하며, 이는 디카르고도 마찬가지입니다. 디카르고 웨어하우스에 배포하기 전에 [포우셋 (faucet)](/undefined-4/undefined)을 통해 $DKA 테스트넷 토큰을 획득할 수 있습니다.
{% endhint %}

## STEP 1 - 파운드리 시작

파운드리 설치를 설치하기 위해서 다음 명령을 실행합니다.

* Linux or MacOS

```
curl -L <https://foundry.paradigm.xyz> | bash foundryup
```

* Windows

```
curl --proto '=https' --tlsv1.2 -sSf <https://sh.rustup.rs/> | sh cargo install --
git <https://github.com/foundry-rs/foundry> foundry-cli anvil --bins --locked
```

## STEP 2 - 파운드리 프로젝트 설정

파운드리를 설치했으면 다음 단계는 새 프로젝트의 초기화입니다.

```bash
mkdir helloDka
cd helloDka
forge init .
```

다음은 파운드리의 폴더 구조에 대한 예시입니다.

```bash
/helloDka
├── README.md
├── foundry.toml
├── lib
|  └── forge-std
├── script
|  └── Counter.s.sol
├── src
|  └── Counter.sol
└── test
   └── Counter.t.sol
```

## Step 3 - 예시 컨트랙트: Counter.sol

파운드리의 새로운 프로젝트는 기본적으로 샘플 컨트랙트 <mark style="color:blue;">`counter.sol`</mark>와 샘플 테스트 파일을 포함합니다. <mark style="color:blue;">`counter.sol`</mark> 코드는 변수 Number를 설정할 수 있는 간단한 기능이 구현되어 있습니다.

```jsx
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}

```

## Step 4 - 컨트랙트 컴파일

오류 없이 컴파일되면 컨트랙트가 올바른 것이며, 디카르고 웨어하우스에 배포할 준비가 된 것입니다.

```jsx
forge build
```

## Step 5 - 컨트랙트 배포

컨트랙트 배포는 Forge CLI 명령어 한 번으로 간단하게 완료할 수 있습니다.&#x20;

다만, 이 과정에서는 웨어하우스 RPC 엔드포인트와 $DKA를 보유한 개인키가 필요합니다.&#x20;

아래 제공된 RPC 엔드포인트 URL을 사용하시고, 개인 키를 준비해 주세요.

* 웨어하우스 RPC 엔드포인트 : <https://it-full.dknote.net>

```jsx
forge create --rpc-url "<https://it-full.dknote.net>" --private-key YOUR_PRIVATE_KEY  src/Counter.sol:Counter
```

명령어 성공 시, 다음과 같은 응답이 표시됩니다.

<figure><img src="/files/RMsLI4PmQAsgBBj4H1IZ" alt=""><figcaption></figcaption></figure>

컨트랙트를 디카르고 웨어하우스에 성공적으로 배포했습니다.

“Deployed to” 에 명시된 컨트랙트 주소를 복사하여 [디스캐너 (dScanner)](https://warehouse.dscanner.io/)에서 컨트랙트 배포 정보를 검색해보세요.


# ERC-20 브릿징이란?

<figure><img src="/files/5D4DrSyY880ToOddsHVT" alt=""><figcaption></figcaption></figure>

아비트럼 체인(Layer 2, L2)에는 WETH, UNI와 같은 다양한 ERC-20 형태의 토큰 자산이 존재합니다.

이러한 자산은 디카르고가 제공하는 토큰 브릿지 (Token Bridge)를 통해 디카르고 체인(Layer 3, L3)으로 허가 없이(permissionless) 브릿지할 수 있습니다.

아비트럼 체인에서 디카르고 체인으로 ERC-20 토큰 자산을 브릿지하는 과정은 DKA 네이티브 토큰을 브릿지할 때와는 다른 절차를 따릅니다.

## 토큰 브릿지의 설계 근거

토큰 브릿지는 아비트럼 체인과 디카르고 체인에 컨트랙트가 배포된 dApp입니다. 토큰 브릿지는 원하는 ERC-20 토큰 자산을 안전하고 신뢰할 수 있게 브릿징하기 위해 아비트럼의 [크로스체인 메시지 전송 시스템(retryable ticket)](https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging)을 활용합니다.

디카르고에서 제공하는 토큰 브릿지는 [**Offchain Labs**](https://www.offchainlabs.com/)에서 구현한 [**Canonical Bridge**](https://github.com/OffchainLabs/token-bridge-contracts)를 기반으로 구축되었으며, 아비트럼에서 수백 개의 ERC-20 토큰 자산이 브릿징된 경험을 토대로 높은 안전성과 우수한 사용성을 보장합니다.

## 토큰 브릿지 표준

토큰 브릿지는 아래와 같이 3가지 컨트랙트로 구성되어 있습니다.

1. **ERC-20 컨트랙트**

* ERC-20 표준을 준수한 토큰 컨트랙트입니다.
* ERC-20 컨트랙트는 L2와 L3에 한쌍으로 배포되어 있습니다.

2. **게이트웨이 (Gateways) 컨트랙트**

* 독립적인 L2와 L3 체인에 배포된 토큰 컨트랙트를 페어링하여 두 체인 간의 입출금 메시지를 주고받는 역할을 합니다.
* L2의 게이트웨이는 입금 시 L2 토큰을 잠그고(lock), 출금 시 잠긴 토큰을 해제(release)하는 역할을 수행합니다.
* L3의 게이트웨이는 입금 시 L3 토큰을 발행(mint)하고, 출금 시 해당 토큰을 소각(burn)하는 역할을 수행합니다.

3. **게이트웨이 라우터 (GatewayRouters) 컨트랙트**

* 사용자로부터의 모든 입출금 요청은 게이트웨이 라우터를 통해 처리되며, 각 토큰에 지정된 게이트웨이로 요청을 라우팅합니다.

## 토큰 브릿지 시작

디카르고의 토큰 브릿지를 이용하기 위해서 토큰에 따라 고려할 수 있는 3가지 옵션이 있습니다.

1. [표준 게이트웨이 (Standard gateway)](/erc-20/undefined)
2. [범용적 커스텀 게이트웨이 (Generic-custom gateway)](/erc-20/undefined-1)
3. [커스텀 게이트웨이 (Custom gateway)](/erc-20/undefined-2)

개발자 또는 프로젝트 빌더는 자신의 프로젝트 특성이나 ERC-20 토큰의 특성에 따라 3가지 옵션 중 하나를 선택하여 아비트럼 체인에서 보유한 ERC-20 토큰을 디카르고 체인으로 유동성 공급할 수 있습니다.

지금부터 토큰 브릿지 가이드를 [@dkargo/sdk](https://github.com/dKargo/dkargo-sdk)에서 제공하는 함수들을 활용해 알아보겠습니다.

## 레퍼런스

### Offchain Labs Token Bridge Contract

아비트럼과 디카르고 사이에 구축된 토큰 브릿지는 [**Offchain Labs**](https://www.offchainlabs.com/) 토큰 브릿지와 매우 유사한 메커니즘으로 동작합니다. 아비트럼에서 작성한 내용을 통해 심도 있는 분석이 가능합니다.

* <https://docs.arbitrum.io/build-decentralized-apps/token-bridging/overview>

### **@dKargo/sdk tutorial** <a href="#dkargo-sdk-tutorial" id="dkargo-sdk-tutorial"></a>

디카르고 sdk를 활용하면 개발자가 직접 작성한 코드를 통해 토큰 브릿지 기능을 이용할 수 있습니다.

디카르고 튜토리얼 (dkargo-tutorials)은 `ethers.ts`와 `hardhat.ts`를 함께 사용하여 디카르고 sdk의 사용 방법에 대해 상세히 설명하고 있습니다.

* <https://github.com/dKargo/dkargo-tutorials>

<br>


# 표준 게이트웨이

L2와 L3에서 ERC-20 토큰이 유동된다는 것은 두 체인에 각각 ERC-20 토큰 컨트랙트가 존재한다는 것을 의미합니다.&#x20;

따라서 개발자 또는 프로젝트 빌더는 L3에 ERC-20 토큰 컨트랙트를 추가로 배포해야 하며, 게이트웨이 라우터 (GatewayRouter)가 사용자의 요청을 지정된 게이트웨이로 라우팅할 수 있도록 게이트웨이 라우터 컨트랙트에 관련 정보를 별도로 등록해야 합니다.

그러나 표준 게이트웨이 (Standard Gateway)를 사용할 경우, 이러한 일련의 과정이나 추가 작업 없이 즉시 브릿지를 이용할 수 있습니다.

최초 입금 요청 시 L3에 페어링된 ERC-20 컨트랙트가 없다면, 해당 컨트랙트가 자동으로 배포되며, 제공되는 표준 게이트웨이를 통해 라우팅됩니다. 이러한 모든 과정이 최초 입금 요청 시 자동으로 수행되기 때문에 간편하고 효율적인 온보딩을 제공합니다.

{% hint style="info" %}
디카르고가 제공하는 토큰 브릿지는 아비트럼의 Retryable ticket을 활용하여 구축된 디앱 (dApp)입니다.

Retryable ticket은 L2에서 트랜잭션을 생성하고 이를 L3에서 실행할 수 있도록 전달하는 메커니즘입니다.

사용자는 이 과정을 통해 L3에서 원하는 작업을 L2에서 수행할 수 있으며, 트랜잭션 처리에 필요한 수수료는 L2의 ERC-20 DKA로 지불됩니다.
{% endhint %}

## ERC20 입금

입금(Deposit)은 아비트럼 체인(L2)에 보유 중인 ERC-20 토큰을 디카르고 체인(L3)으로 전송하는 과정을 의미합니다.

이 과정은 아비트럼에 배포된 토큰 브릿지 컨트랙트와 디카르고 시퀀서의 협력을 통해 단계 별로 수행됩니다.

### STEP 1 - L2 ERC-20 토큰 승인 & L2 ERC-20 DKA

L2에 보유 중인 ERC-20 토큰을 디카르고 체인으로 전송하려면, 먼저 표준 게이트웨이 컨트랙트가 사용자의 ERC-20 토큰에 접근할 수 있도록 승인을 해야 합니다.

{% code overflow="wrap" %}

```tsx
const depositAmount = parseEther('1');
const res = await tokenBridge.approveToken({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
  amount:depositAmount
});

const receipt = await res.wait();      
console.log(`approve ERC20 token to L2 Standard Gateway Contract tx hash: ${receipt.transactionHash}`)

const allowance = await tokenBridge.allowanceTokenToGateway(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

{% endcode %}

또한, 입금 과정에서 L3에서 발생하는 트랜잭션 수수료는 L2의 ERC-20 DKA로 지불되기 때문에, 표준 게이트웨이 컨트랙트가 ERC-20 DKA에도 접근할 수 있도록 별도로 승인을 해주어야 합니다.

{% code overflow="wrap" %}

```tsx
const res = await tokenBridge.approveGasToken({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
});

const receipt = await res.wait();      
console.log(`approve DKA token to L2 Standard Gateway Contract tx hash: ${receipt.transactionHash}`)

const allowance = await tokenBridge.allowanceGasTokenToGateway(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

{% endcode %}

{% hint style="info" %}
파라미터에 <mark style="color:blue;">`depositAmount`</mark> 대신 <mark style="color:blue;">`null`</mark>을 입력하면, 최대 가능한 수량을 승인하게 됩니다.&#x20;

브리지를 사용할 때마다 매번 입금 금액을 개별적으로 승인해야 하지만, 이렇게 설정하면 최초 한 번만 승인하면 되고 이후에는 추가 승인이 필요 없습니다.
{% endhint %}

### STEP 2 - 토큰 입금

승인이 완료되면, L2 게이트웨이 라우터 컨트랙트의 `outboundTransfer()` 메서드를 호출하여 토큰 입금을 진행합니다. L2 게이트웨이 라우터는 사용자의 입금 요청을 L2 표준 게이트웨이로 라우팅합니다.

표준 게이트웨이는 사용자의 입금 요청에 따라 ERC-20 토큰을 자신의 컨트랙트로 전송한 후, 트랜잭션 수수료로 필요한 L2 ERC-20 DKA를 브릿지 컨트랙트로 전송합니다.

{% code overflow="wrap" %}

```tsx
const res = await tokenBridge.deposit({
  amount: depositAmount,
  erc20ParentAddress: parentERC20.address,
  parentSigner,
  childProvider,
});

const receipt = await res.wait();      
console.log(`deposit erc20 token L2 tx hash: ${receipt.transactionHash}`)
```

{% endcode %}

입금된 ERC-20 토큰은 아비트럼에 배포된 표준 게이트웨이 컨트랙트에 보관되며, 해당 토큰 자산은 잠기게(lock) 됩니다.

{% hint style="info" %}
최초로 입금이 시도된 경우, L3에 ERC-20 컨트랙트가 자동으로 배포됩니다.&#x20;

배포된 ERC-20 컨트랙트는 [**StandardArbERC20.sol**](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol)을 기반으로 구현되어 있습니다.
{% endhint %}

### STEP 3 - 입금 상태

표준 게이트웨이로 ERC-20 토큰을 전송했다고 해서 L3에 즉시 입금되지는 않습니다. 입금 상태는 "대기"로 표시되며, 약 10분의 시간이 지난 후 최종적으로 디카르고 체인 계정으로 토큰이 발행(mint) 됩니다.

```tsx
// 메시지가 디카르고 체인에서 호출될 때까지 대기합니다.
await receipt.waitForChildTransactionReceipt(childProvider);
```

입금 상태는 [dScanner의 L2 ➔ L3 Transactions 페이지](https://warehouse.dscanner.io/txs-deposits)에서 확인할 수 있습니다.

{% hint style="info" %}
사용자가 L2의 표준 게이트웨이로 전송한 입금 요청에는 L3 ERC-20의 <mark style="color:blue;">`bridgeMint()`</mark>를 호출하는 메시지가 포함되어 있습니다. 이 메시지는 시퀀서에 의해 검증된 후 L3의 표준 게이트웨이를 통해 실행되어 토큰이 발행되며, 최종적으로 사용자의 계정에 입금됩니다.
{% endhint %}

## ERC-20 출금

출금(Withdraw)은 디카르고 체인(L3)에 보유 중인 ERC-20 토큰을 아비트럼 체인(L2)으로 전송하는 과정을 의미합니다. 이 과정은 아비트럼에 배포된 토큰 브릿지 컨트랙트와 디카르고 시퀀서가 협력하여 단계적으로 수행됩니다.

### STEP 1 - **ERC-20 출금**

ERC-20 토큰 출금은 L3 게이트웨이 라우터 (GatewayRouter)의 <mark style="color:blue;">`outboundTransfer()`</mark> 메서드를 호출하여 진행됩니다.&#x20;

L3 게이트웨이 라우터는 사용자의 출금 요청을 L3 표준 게이트웨이로 라우팅합니다.

L3 표준 게이트웨이는 출금 요청에 따라 ERC-20 토큰을 소각하며, L2 표준 게이트웨이에 보관된 L2 ERC-20 토큰이 이후 절차를 통해 소각된 만큼 사용자에게 반환됩니다.

```tsx
const res = await tokenBridge.withdraw({
  erc20ParentAddress: parentERC20.address,
  childSigner,
  amount,
  destinationAddress: parentSigner.address,
});

const receipt = await res.wait();
console.log(`withdraw erc20 token L3 tx hash: ${receipt.transactionHash}`)
```

{% hint style="info" %}
사용자가 L3 표준 게이트웨이로 전송한 출금 요청에는 L3 ERC-20의 <mark style="color:blue;">`bridgeBurn()`</mark> 메서드를 호출하는 메시지가 포함되어 있습니다. 이 메시지를 통해 출금 요청된 L3 토큰은 소각되며, 동시에 L2 표준 게이트웨이에 잠긴 L2 토큰을 소각된 만큼 해제(release)할 수 있는 권한이 부여됩니다.
{% endhint %}

### STEP 2: 출금 상태

출금 요청 후 L2에서 ERC-20 토큰을 수령하려면 약 6.4일의 분쟁 기간이 경과해야 하며, 그 기간 동안은 대기 상태로 유지됩니다.

```tsx
const message = await receipt.getChildToParentMessages(parentProvider);

// 6.4일의 분쟁 기간 지날때 까지 대기합니다.
await message[0].waitUntilReadyToExecute(childProvider);
```

출금 상태는 [dScanner의 L3 ➔ L2 Transactions 페이지](https://warehouse.dscanner.io/txs-exit)에서 확인할 수 있습니다.

### STEP 3: **ERC-20 청구**

분쟁 기간이 종료되면, 사용자는 L2 표준 게이트웨이에서 출금을 요청한 ERC-20 토큰을 청구할 수 있는 자격을 얻게 됩니다. 사용자는 outbox 컨트랙트를 통해 출금 요청한 ERC-20 토큰을 청구할 수 있습니다.

```jsx
const message = await receipt.getChildToParentMessages(parentSigner);
const res = await message[0].execute(childProvider);
const executeReceipt = await res.wait();
console.log(`claim erc20 token L2 tx hash: ${executeReceipt.transactionHash}`)
```


# 범용적 커스텀 게이트웨이

아비트럼 체인과 디카르고 체인 간의 토큰 유동에는 표준 게이트웨이 방식만으로도 충분합니다.&#x20;

그러나 표준 게이트웨이 방식에서는 입금 시 디카르고 체인에 대응되는 ERC-20 토큰 컨트랙트가 자동으로 배포되며, 이는 [**StandardArbERC20.sol**](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol)로 구현된 컨트랙트를 강제합니다.

개발자 또는 프로젝트 빌더가 자신이 구현한 ERC-20 컨트랙트에 추가적인 기능을 포함하거나, 디카르고 체인에서 특정 ERC-20 컨트랙트와 페어링되도록 설정하기를 원한다면, 범용적 커스텀 게이트웨이 (Generic-Custom Gateway)를 선택하여 더 큰 자유도를 얻을 수 있습니다.

{% hint style="info" %}
솔리디티로 구현된 컨트랙트 이름에는 L1, L2가 prefix로 명시 되어 있습니다. 이는 아비트럼에서 운영되는 [토큰 브릿지 컨트랙트](https://github.com/OffchainLabs/token-bridge-contracts)를 디카르고에서 사용하기 때문입니다. 디카르고 토큰 브릿지 컨트랙트의 L1 = 아비트럼(L2), L2 = 디카르고(L3)로 해석됩니다.
{% endhint %}

## 범용적 커스텀 게이트웨이를 통한 토큰 설정

범용적 커스텀 게이트웨이를 사용하기 위해서는 L2에 배포된 ERC-20 컨트랙트는 다음과 같은 규칙을 준수해야합니다.

### 1. 인터페이스

L2에 배포된 ERC20 토큰 컨트랙트는 [ICustomToken](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/ICustomToken.sol) 인터페이스를 준수해야 합니다.

<mark style="color:blue;">`isArbitrumEnabled()`</mark> 메서드는 토큰을 등록하는 과정에서 호출되며, 범용적 커스텀 게이트웨이를 이용하기 위해서는 <mark style="color:blue;">`uint8(0xb1)`</mark> 값을 반환해야 합니다.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ICustomToken} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/ICustomToken.sol";
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol";

import {L1OrbitGatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol";
import {L1OrbitCustomGateway} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol";
import {IL1GatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/IL1GatewayRouter.sol";
import { IERC20Bridge } from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/IERC20Bridge.sol";

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract L2TokenCustomGas is Ownable, ERC20, ICustomToken {
    using SafeERC20 for IERC20;

    address public gateway;
    address public router;
    bool internal shouldRegisterGateway;

    constructor(string memory name_, string memory symbol_,uint256 _initialSupply,address _gateway, address _router) ERC20(name_, symbol_) {
        gateway = _gateway;
        router = _router;
        _mint(msg.sender, _initialSupply * 10 ** decimals());
    }

    /// @dev See {ERC20-transferFrom}
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override(ICustomToken, ERC20) returns (bool) {
        return super.transferFrom(sender, recipient, amount);
    }

    /// @dev See {ERC20-balanceOf}
    function balanceOf(address account) public view override(ICustomToken, ERC20) returns (uint256) {
        return super.balanceOf(account);
    }

    /// @dev we only set shouldRegisterGateway to true when in `registerTokenOnL2`
    function isArbitrumEnabled() external view override returns (uint8) {
        require(shouldRegisterGateway, "NOT_EXPECTED_CALL");
        return uint8(0xb1);
    }

    function registerTokenOnL2(
        address l2CustomTokenAddress,
        uint256 maxSubmissionCostForCustomGateway,
        uint256 maxSubmissionCostForRouter,
        uint256 maxGasForCustomGateway,
        uint256 maxGasForRouter,
        uint256 gasPriceBid,
        uint256 valueForGateway,
        uint256 valueForRouter,
        address creditBackAddress
    ) public payable override onlyOwner {
        // we temporarily set `shouldRegisterGateway` to true for the callback in registerTokenToL2 to succeed
        bool prev = shouldRegisterGateway;
        shouldRegisterGateway = true;

        address inbox = IL1GatewayRouter(router).inbox();
        address bridge = address(IInbox(inbox).bridge());

        // transfer fees from user to here, and approve router to use it
        {
            address nativeToken = IERC20Bridge(bridge).nativeToken();

            IERC20(nativeToken).safeTransferFrom(
                msg.sender,
                address(this),
                valueForGateway + valueForRouter
            );
            IERC20(nativeToken).approve(router, valueForRouter);
            IERC20(nativeToken).approve(gateway, valueForGateway);
        }

        L1OrbitCustomGateway(gateway).registerTokenToL2(
            l2CustomTokenAddress,
            maxGasForCustomGateway,
            gasPriceBid,
            maxSubmissionCostForCustomGateway,
            creditBackAddress,
            valueForGateway
        );

        L1OrbitGatewayRouter(router).setGateway(
            gateway,
            maxGasForRouter,
            gasPriceBid,
            maxSubmissionCostForRouter,
            creditBackAddress,
            valueForRouter
        );

        // reset allowance back to 0 in case not all approved native tokens are spent
        {
            address nativeToken = IERC20Bridge(bridge).nativeToken();

            IERC20(nativeToken).approve(router, 0);
            IERC20(nativeToken).approve(gateway, 0);
        }

        shouldRegisterGateway = prev;
    }
}

```

{% endcode %}

L3에 배포된 ERC20 토큰 컨트랙트는 [IArbToken](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) 인터페이스를 준수해야 합니다.

입출금 과정에서 범용적 커스텀 게이트웨이 컨트랙트에서만 호출 가능한(onlyL2Gateway) <mark style="color:blue;">`bridgeMint`</mark> 및 <mark style="color:blue;">`bridgeBurn`</mark> 메서드와 L2 ERC-20 토큰 컨트랙트 주소를 저장하는 <mark style="color:blue;">`l1Address`</mark> 변수가 구현되어 있어야 합니다.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

contract L3Token is ERC20, ERC20Permit, IArbToken {
    address public l2Gateway;
    address public override l1Address;

    modifier onlyL2Gateway() {
        require(msg.sender == l2Gateway, "NOT_GATEWAY");
        _;
    }

    constructor(string memory name_, string memory symbol_,address _l2Gateway, address _l1TokenAddress) ERC20(name_, symbol_) ERC20Permit(name_) {
        l2Gateway = _l2Gateway;
        l1Address = _l1TokenAddress;
    }

    /**
     * @notice should increase token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeMint(address account, uint256 amount) external override onlyL2Gateway {
        _mint(account, amount);
    }

    /**
     * @notice should decrease token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeBurn(address account, uint256 amount) external override onlyL2Gateway {
        _burn(account, amount);
    }
}

```

{% endcode %}

### 2. 승인

다음 단계에서는 범용적 커스텀 게이트웨이를 통해 L2 ERC-20 토큰과 L3 ERC-20 토큰을 등록하여 페어링을 설정할 예정입니다.&#x20;

두 토큰은 독립적인 두 체인에 배포되어 있기 때문에, [**retryable ticket**](https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging)을 활용하여 페어링을 진행하며, 이 과정에서 L2 ERC20 DKA가 수수료로 지불됩니다.

페어링을 설정하기 위한 등록 과정은 L2 ERC20 토큰 컨트랙트의 메서드를 호출하면서 시작되며, \
이를 위해 L2 ERC-20 토큰 컨트랙트가 L2 ERC-20 DKA를 사용할 수 있도록 사전에 승인을 완료해야 합니다.

{% hint style="info" %}
디카르고가 제공하는 토큰 브릿지는 아비트럼의 **Retryable ticket**을 활용하여 구축된 디앱입니다. Retryable ticket은 L2에서 트랜잭션을 생성하고 이를 L3에서 실행할 수 있도록 전달하는 메커니즘입니다.

사용자는 이 과정을 통해 L3에서 원하는 작업을 L2에서 수행할 수 있으며, **트랜잭션 처리에 필요한 수수료는 L2의 ERC-20 DKA로 지불됩니다.**
{% endhint %}

{% code overflow="wrap" %}

```tsx
const res = await customTokenBridge.approveGasTokenForCustomTokenRegistration({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
});

const receipt = await res.wait();
console.log(`approve gas token to L2 ERC20 Contract tx hash: ${receipt.transactionHash}`

const allowance = await customTokenBridge.allowanceGasTokenToParentERC20(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

{% endcode %}

### 3. 등록

범용적 커스텀 게이트웨이에 L2 ERC-20 토큰과 L3 ERC-20 토큰을 등록합니다.

\[L2 ERC20 Example]을 보면 <mark style="color:blue;">`registerTokenOnL2`</mark> 메서드를 통해 <mark style="color:blue;">`customGateway.registerTokenToL2`</mark> 메서드와 <mark style="color:blue;">`customGateway.setGateway`</mark> 메서드가 모두 L2 ERC-20 컨트랙트에서 호출되는 것을 확인할 수 있습니다.

* c<mark style="color:blue;">`ustomGateway.registerTokenToL2`</mark> 메서드는 L2 범용적 커스텀 게이트웨이에서 두 토큰을 페어링하는 작업을 수행하며, 디카르고 체인으로 메시지를 전달하여 L3 범용적 커스텀 게이트웨이에서도 동일한 작업을 수행하도록 합니다.
* <mark style="color:blue;">`customGateway.setGateway`</mark> 메서드는 L2 게이트웨이 라우터에 해당 토큰의 게이트웨이가 범용적 커스텀 게이트웨이임을 등록하는 작업을 수행합니다. 이 과정 역시 디카르고 체인으로 메시지를 전달하여 L3 게이트웨이 라우터에서 동일하게 적용됩니다.

```tsx
const res = await customTokenBridge.registerCustomToken(
  parentERC20.address,
  childERC20.address,
  parentSigner,
  childProvider
);

const receipt = await res.wait();
console.log(`register tx hash: ${receipt.transactionHash}`
```

두 토큰이 각각 독립적인 두 체인에 등록되고 최종적으로 페어링되기까지는 일정 시간이 필요합니다. 등록 요청 상태는 "대기"로 표시되며, 약 10분 후 최종적으로 디카르고 체인에서 호출됩니다.

```tsx
const l2ToL3Msgs = await receipt.getParentToChildMessages(childProvider);

// 메시지가 디카르고 체인에서 호출될 때 까지 대기합니다.
await l2ToL3Msgs[0].waitForStatus();
await l2ToL3Msgs[1].waitForStatus();
```

상태는 [dScanner의 L2 ➔ L3 Transactions 페이지](https://warehouse.dscanner.io/txs-deposits)에서 확인할 수 있습니다.

## 토큰 입출금

이제 범용적 커스텀 게이트웨이를 통해 아비트럼 체인과 디카르고 체인 간의 입출금을 수행할 준비가 완료되었습니다. 이후 입출금 과정은 모두 동일한 방식으로 동작하며, [**표준 게이트웨이 (Standard Gateway)**](https://www.notion.so/160f208f1da980478de7d5372ebc5919?pvs=21)의 입출금 방식을 참고하여 진행하면 됩니다.

여기서 주의해야 할 점이 있습니다. 표준 게이트웨이를 선택한 경우, 최초 입금 시 디카르고 체인에 대응되는 ERC-20 토큰 컨트랙트가 자동으로 배포되며, 이는 [**StandardArbERC20.sol**](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol)로 구현된 컨트랙트를 강제한다고 설명드렸습니다.

반면, 범용적 커스텀 게이트웨이를 선택한 경우에는 반드시 위 과정을 먼저 완료한 후 첫 입금을 수행해야 한다는 점을 강조드립니다.


# 커스텀 게이트웨이

표준 게이트웨이 (Standard Gateway)는 별도의 추가 작업이나 허가 없이 입출금을 수행할 수 있는 옵션으로, 간편하고 효율적인 온보딩을 제공합니다.

**범용적 커스텀 게이트웨이** (Generic-Custom Gateway)는 개발자가 구현한 ERC-20 컨트랙트에 추가 기능을 포함하거나, 디카르고 체인에서 특정 ERC-20 컨트랙트와 페어링되도록 설정하기 위해 선택할 수 있는 옵션입니다.

두 게이트웨이는 모두 디카르고의 토큰 브릿지에서 제공하는 스마트 컨트랙트입니다. 개발자 또는 프로젝트 빌더가 기존에 배포된 게이트웨이가 아닌, 추가적인 기능이나 커스텀된 기능이 포함된 게이트웨이를 사용하고자 할 경우 커스텀 게이트웨이 (Custom Gateway)를 선택하면 됩니다.

{% hint style="warning" %}
**정말 커스텀 게이트웨이가 필요할까요?**

커스텀 게이트웨이를 구현하고 배포하기 전에, 디카르고 토큰 브릿지가 제공하는 표준 게이트웨이와 범용적 커스텀 게이트웨이를 분석할 것을 강력히 권장합니다.

먼저, 아비트럼의 [토큰 브릿지 문서](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-custom-gateway)를 학습한 후 커스텀 게이트웨이를 검토하면, 이후 다루는 코드와 내용을 훨씬 수월하게 이해할 수 있습니다.
{% endhint %}

## STEP 1 - 사용 예시

앞서 [토큰 브릿지](/erc-20/erc-20)의 기능에 대해 설명한 것처럼, L2 게이트웨이는 입금 시 L2 토큰을 잠그고(lock), 출금 시 잠긴 토큰을 해제(release)하며, L3 게이트웨이는 입금 시 L3 토큰을 발행(mint)하고 출금 시 해당 토큰을 소각(burn)합니다.

이러한 역할을 수행하는 게이트웨이에 추가적인 기능을 구현하고자 할 경우, 커스텀 게이트웨이를 이용하면 적합합니다.

아래는 커스텀 게이트웨이를 통해 추가할 수 있는 기능의 예시입니다.

* 입출금 시 허가된 사용자인지를 검증하는 화이트리스트 기능
* 입출금 시 브릿지 이용에 대한 수수료를 사용자로부터 청구하는 기능
* 입출금 시 유동된 잔액을 기록하는 기능

커스텀 게이트웨이를 활용하면 이러한 요구 사항을 만족하는 맞춤형 기능을 구현할 수 있습니다.

## STEP 2 - 커스텀 게이트웨이 설정

{% hint style="info" %}
입금과 출금을 관리자(owner)가 활성화하거나 비활성화할 수 있는 기능이 추가된 커스텀 게이트웨이를 구현한 예시를 통해 활용하는 방법을 안내하겠습니다.
{% endhint %}

게이트웨이는 기본적으로 L2와 L3에 배포되어 있어야 합니다. 사용자의 입금 요청은 L2 커스텀 게이트웨이를 통해 처리되므로, 입금 활성화/비활성화 기능은 L2 커스텀 게이트웨이에 구현합니다. 반면, 출금 요청은 L3 커스텀 게이트웨이를 통해 처리되므로, 출금 활성화/비활성화 기능은 L3 커스텀 게이트웨이에 구현합니다.

### L2 커스텀 게이트웨이 예시

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./interfaces/ICustomGateway.sol";
import "./CrosschainMessenger.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import {L1ArbitrumMessenger} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/L1ArbitrumMessenger.sol";
import {GatewayMessageHandler} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/gateway/GatewayMessageHandler.sol";
import {IERC20Inbox} from "@arbitrum/nitro-contracts/src/bridge/IERC20Inbox.sol";
import {IERC20Bridge} from "@arbitrum/nitro-contracts/src/bridge/IERC20Bridge.sol";

/**
 * @title Example implementation of a custom gateway to be deployed on L2
 * @dev Inheritance of Ownable is optional. In this case we use it to call the function setTokenBridgeInformation
 * and simplify the test
 */
contract ParentChainCustomGateway is L1ArbitrumMessenger, IL2CustomGateway, L2CrosschainMessenger, Ownable {
    using Address for address;
    using SafeERC20 for IERC20;

    // Token bridge state variables
    address public l2CustomToken;
    address public l3CustomToken;
    address public l3Gateway;
    address public router;

    // Custom functionality
    bool public allowsDeposits;

    /**
     * Contract constructor, sets the L2 router to be used in the contract's functions and calls L1CrosschainMessenger's constructor
     * @param router_ l3GatewayRouter address
     * @param inbox_ Inbox address
     */
    constructor(
        address router_,
        address inbox_
    ) L2CrosschainMessenger(inbox_) {
        router = router_;
        allowsDeposits = false;
    }

    /**
     * Sets the information needed to use the gateway. To simplify the process of testing, this function can be called once
     * by the owner of the contract to set these addresses.
     * @param l2CustomToken_ address of the custom token on L2
     * @param l3CustomToken_ address of the custom token on L3
     * @param l3Gateway_ address of the counterpart gateway (on L3)
     */
    function setTokenBridgeInformation(
        address l2CustomToken_,
        address l3CustomToken_,
        address l3Gateway_
    ) public onlyOwner {
        require(l2CustomToken == address(0), "Token bridge information already set");
        l2CustomToken = l2CustomToken_;
        l3CustomToken = l3CustomToken_;
        l3Gateway = l3Gateway_;

        // Allows deposits after the information has been set
        allowsDeposits = true;
    }

    /// @dev See {ICustomGateway-outboundTransfer}
    function outboundTransfer(
        address l2Token,
        address to,
        uint256 amount,
        uint256 maxGas,
        uint256 gasPriceBid,
        bytes calldata data
    ) public payable override returns (bytes memory) {
        return outboundTransferCustomRefund(l2Token, to, to, amount, maxGas, gasPriceBid, data);
    }

    function outboundTransferCustomRefund(
        address _l2Token,
        address _refundTo,
        address _to,
        uint256 _amount,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        bytes calldata _data
    ) public payable virtual override returns (bytes memory res) {
        // Only execute if deposits are allowed
        require(allowsDeposits == true, "Deposits are currently disabled");

        // Only allow calls from the router
        require(msg.sender == router, "Call not received from router");

        // Only allow the custom token to be bridged through this gateway
        require(_l2Token == l2CustomToken, "Token is not allowed through this gateway");

        // This function is set as public and virtual so that subclasses can override
        // it and add custom validation for callers (ie only whitelisted users)
        address _from;
        uint256 seqNum;
        bytes memory extraData;
        {
            // unpack user encoded data
            uint256 maxSubmissionCost;
            uint256 tokenTotalFeeAmount;
            if (isRouter(msg.sender)) {
                // router encoded
                (_from, extraData) = GatewayMessageHandler.parseFromRouterToGateway(_data);
            } else {
                _from = msg.sender;
                extraData = _data;
            }
            (maxSubmissionCost, extraData, tokenTotalFeeAmount) = _parseUserEncodedData(extraData);

            // the inboundEscrowAndCall functionality has been disabled, so no data is allowed
            require(extraData.length == 0, "EXTRA_DATA_DISABLED");

            require(_l2Token.isContract(), "L1_NOT_CONTRACT");
            address l2Token = calculateL2TokenAddress(_l2Token);
            require(l2Token != address(0), "NO_L2_TOKEN_SET");

            _amount = outboundEscrowTransfer(_l2Token, _from, _amount);

            // we override the res field to save on the stack
            res = getOutboundCalldata(_l2Token, _from, _to, _amount, extraData);

            seqNum = _initiateDeposit(
                _refundTo,
                _from,
                _amount,
                _maxGas,
                _gasPriceBid,
                maxSubmissionCost,
                tokenTotalFeeAmount,
                res
            );
        }
        emit DepositInitiated(_l2Token, _from, _to, seqNum, _amount);
        return abi.encode(seqNum);
    }

    /// @dev See {ICustomGateway-finalizeInboundTransfer}
    function finalizeInboundTransfer(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes calldata data
    ) public payable override onlyCounterpartGateway(l3Gateway) {
        // Only allow the custom token to be bridged through this gateway
        require(l2Token == l2CustomToken, "Token is not allowed through this gateway");

        // Decoding exitNum
        (uint256 exitNum, ) = abi.decode(data, (uint256, bytes));

        // Releasing the tokens in the gateway
        IERC20(l2Token).transfer(to, amount);

        emit WithdrawalFinalized(l2Token, from, to, exitNum, amount);
    }

    /// @dev See {ICustomGateway-getOutboundCalldata}
    function getOutboundCalldata(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) public pure override returns (bytes memory outboundCalldata) {
        bytes memory emptyBytes = "";

        outboundCalldata = abi.encodeWithSelector(
            ICustomGateway.finalizeInboundTransfer.selector,
            l2Token,
            from,
            to,
            amount,
            abi.encode(emptyBytes, data)
        );

        return outboundCalldata;
    }

    /// @dev See {ICustomGateway-calculateL2TokenAddress}
    function calculateL2TokenAddress(address l2Token) public view override returns (address) {
        if (l2Token == l2CustomToken) {
            return l3CustomToken;
        }

        return address(0);
    }

    /// @dev See {ICustomGateway-counterpartGateway}
    function counterpartGateway() public view override returns (address) {
        return l3Gateway;
    }

    function _parseUserEncodedData(bytes memory data)
        internal
        pure
        returns (
            uint256 maxSubmissionCost,
            bytes memory callHookData,
            uint256 tokenTotalFeeAmount
        )
    {
        (maxSubmissionCost, callHookData, tokenTotalFeeAmount) = abi.decode(
            data,
            (uint256, bytes, uint256)
        );
    }

    function outboundEscrowTransfer(
        address _l2Token,
        address _from,
        uint256 _amount
    ) internal virtual returns (uint256 amountReceived) {
        // this method is virtual since different subclasses can handle escrow differently
        // user funds are escrowed on the gateway using this function
        uint256 prevBalance = IERC20(_l2Token).balanceOf(address(this));
        IERC20(_l2Token).safeTransferFrom(_from, address(this), _amount);
        uint256 postBalance = IERC20(_l2Token).balanceOf(address(this));
        return postBalance - prevBalance;
    }
    function _initiateDeposit(
        address _refundTo,
        address _from,
        uint256, // _amount, this info is already contained in _data
        uint256 _maxGas,
        uint256 _gasPriceBid,
        uint256 _maxSubmissionCost,
        uint256 tokenTotalFeeAmount,
        bytes memory _data
    ) internal returns (uint256) {
        return
        sendTxToL2CustomRefund(
                address(inbox),
                counterpartGateway(),
                _refundTo,
                _from,
                tokenTotalFeeAmount,
                0,
                L2GasParams({
                    _maxSubmissionCost: _maxSubmissionCost,
                    _maxGas: _maxGas,
                    _gasPriceBid: _gasPriceBid
                }),
                _data
            );
    }

    function _createRetryable(
        address _inbox,
        address _to,
        address _refundTo,
        address _user,
        uint256 _totalFeeAmount,
        uint256 _l2CallValue,
        uint256 _maxSubmissionCost,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        bytes memory _data
    ) internal override returns (uint256) {
        {
            // Transfer native token amount needed to pay for retryable fees to the inbox.
            // Fee tokens will be transferred from user who initiated the action - that's `_user` account in
            // case call was routed by router, or msg.sender in case gateway's entrypoint was called directly.
            address nativeFeeToken = IERC20Bridge(address(getBridge(_inbox))).nativeToken();
            uint256 inboxNativeTokenBalance = IERC20(nativeFeeToken).balanceOf(_inbox);
            if (inboxNativeTokenBalance < _totalFeeAmount) {
                address transferFrom = isRouter(msg.sender) ? _user : msg.sender;
                IERC20(nativeFeeToken).safeTransferFrom(
                    transferFrom,
                    _inbox,
                    _totalFeeAmount - inboxNativeTokenBalance
                );
            }
        }

        return
            IERC20Inbox(_inbox).createRetryableTicket(
                _to,
                _l2CallValue,
                _maxSubmissionCost,
                _refundTo,
                _user,
                _maxGas,
                _gasPriceBid,
                _totalFeeAmount,
                _data
            );
    }

    function isRouter(address _target) internal view returns (bool isTargetRouter) {
        return _target == router;
    }

    // --------------------
    // Custom methods
    // --------------------
    /**
     * Disables the ability to deposit funds
     */
    function disableDeposits() external onlyOwner {
        allowsDeposits = false;
    }

    /**
     * Enables the ability to deposit funds
     */
    function enableDeposits() external onlyOwner {
        require(l2CustomToken != address(0), "Token bridge information has not been set yet");
        allowsDeposits = true;
    }
}
```

{% endcode %}

### L3 커스텀 게이트웨이 예시

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./interfaces/ICustomGateway.sol";
import "./CrosschainMessenger.sol";
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title Example implementation of a custom gateway to be deployed on L3
 * @dev Inheritance of Ownable is optional. In this case we use it to call the function setTokenBridgeInformation
 * and simplify the test
 */
contract ChildChainCustomGateway is IL3CustomGateway, L3CrosschainMessenger, Ownable {
    // Exit number (used for tradeable exits)
    uint256 public exitNum;

    // Token bridge state variables
    address public l2CustomToken;
    address public l3CustomToken;
    address public l2Gateway;
    address public router;

    // Custom functionality
    bool public allowsWithdrawals;

    /**
     * Contract constructor, sets the L3 router to be used in the contract's functions
     * @param router_ L3GatewayRouter address
     */
    constructor(address router_) {
        router = router_;
        allowsWithdrawals = false;
    }

    /**
     * Sets the information needed to use the gateway. To simplify the process of testing, this function can be called once
     * by the owner of the contract to set these addresses.
     * @param l2CustomToken_ address of the custom token on L2
     * @param l3CustomToken_ address of the custom token on L3
     * @param l2Gateway_ address of the counterpart gateway (on L2)
     */
    function setTokenBridgeInformation(
        address l2CustomToken_,
        address l3CustomToken_,
        address l2Gateway_
    ) public onlyOwner {
        require(l2CustomToken == address(0), "Token bridge information already set");
        l2CustomToken = l2CustomToken_;
        l3CustomToken = l3CustomToken_;
        l2Gateway = l2Gateway_;

        // Allows withdrawals after the information has been set
        allowsWithdrawals = true;
    }

    /// @dev See {ICustomGateway-outboundTransfer}
    function outboundTransfer(
        address l2Token,
        address to,
        uint256 amount,
        bytes calldata data
    ) public payable returns (bytes memory) {
        return outboundTransfer(l2Token, to, amount, 0, 0, data);
    }

    /// @dev See {ICustomGateway-outboundTransfer}
    function outboundTransfer(
        address l2Token,
        address to,
        uint256 amount,
        uint256, /* _maxGas */
        uint256, /* _gasPriceBid */
        bytes calldata data
    ) public payable override returns (bytes memory res) {
        // Only execute if deposits are allowed
        require(allowsWithdrawals == true, "Withdrawals are currently disabled");

        // The function is marked as payable to conform to the inheritance setup
        // This particular code path shouldn't have a msg.value > 0
        require(msg.value == 0, "NO_VALUE");
        
        // Only allow the custom token to be bridged through this gateway
        require(l2Token == l2CustomToken, "Token is not allowed through this gateway");

        (address from, bytes memory extraData) = _parseOutboundData(data);

        // The inboundEscrowAndCall functionality has been disabled, so no data is allowed
        require(extraData.length == 0, "EXTRA_DATA_DISABLED");

        // Burns L2 tokens in order to release escrowed L1 tokens
        IArbToken(l3CustomToken).bridgeBurn(from, amount);

        // Current exit number for this operation
        uint256 currExitNum = exitNum++;

        // We override the res field to save on the stack
        res = getOutboundCalldata(l2Token, from, to, amount, extraData);

        // Trigger the crosschain message
        uint256 id = _sendTxToL2(
            from,
            l2Gateway,
            res
        );

        emit WithdrawalInitiated(l2Token, from, to, id, currExitNum, amount);
        return abi.encode(id);
    }

    /// @dev See {ICustomGateway-finalizeInboundTransfer}
    function finalizeInboundTransfer(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes calldata data
    ) public payable override onlyCounterpartGateway(l2Gateway) {
        // Only allow the custom token to be bridged through this gateway
        require(l2Token == l2CustomToken, "Token is not allowed through this gateway");

        // Abi decode may revert, but the encoding is done by L2 gateway, so we trust it
        (, bytes memory callHookData) = abi.decode(data, (bytes, bytes));
        if (callHookData.length != 0) {
            // callHookData should always be 0 since inboundEscrowAndCall is disabled
            callHookData = bytes("");
        }

        // Mints L3 tokens
        IArbToken(l3CustomToken).bridgeMint(to, amount);

        emit DepositFinalized(l2Token, from, to, amount);
    }

    /// @dev See {ICustomGateway-getOutboundCalldata}
    function getOutboundCalldata(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) public view override returns (bytes memory outboundCalldata) {
        outboundCalldata = abi.encodeWithSelector(
            ICustomGateway.finalizeInboundTransfer.selector,
            l2Token,
            from,
            to,
            amount,
            abi.encode(exitNum, data)
        );

        return outboundCalldata;
    }

    /// @dev See {ICustomGateway-calculateL2TokenAddress}
    function calculateL2TokenAddress(address l2Token) public view override returns (address) {
        if (l2Token == l2CustomToken) {
            return l3CustomToken;
        }
        
        return address(0);
    }

    /// @dev See {ICustomGateway-counterpartGateway}
    function counterpartGateway() public view override returns (address) {
        return l2Gateway;
    }

    /**
     * Parse data received in outboundTransfer
     * @param data encoded data received
     * @return from account that initiated the deposit,
     *         extraData decoded data
     */
    function _parseOutboundData(bytes memory data)
    internal
    view
    returns (
        address from,
        bytes memory extraData
    )
    {
        if (msg.sender == router) {
            // Router encoded
            (from, extraData) = abi.decode(data, (address, bytes));
        } else {
            from = msg.sender;
            extraData = data;
        }
    }

    // --------------------
    // Custom methods
    // --------------------
    /**
     * Disables the ability to deposit funds
     */
    function disableWithdrawals() external onlyOwner {
        allowsWithdrawals = false;
    }

    /**
     * Enables the ability to deposit funds
     */
    function enableWithdrawals() external onlyOwner {
        require(l2CustomToken != address(0), "Token bridge information has not been set yet");
        allowsWithdrawals = true;
    }
}
```

{% endcode %}

또한, 토큰 브릿지와 호환 가능한 인터페이스와 메서드는 아래 제공된 코드를 통해 참고할 수 있습니다.

### CrosschainMessenger.sol

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
import "@arbitrum/nitro-contracts/src/libraries/AddressAliasHelper.sol";
import {IERC20Inbox} from "@arbitrum/nitro-contracts/src/bridge/IERC20Inbox.sol";
import {IBridge} from "@arbitrum/nitro-contracts/src/bridge/IBridge.sol";

/**
 * @title Interface needed to call function `l2ToL1Sender` of the Outbox
 */
interface IOutbox {
    function l2ToL1Sender() external view returns (address);
}

/**
 * @title Minimum expected implementation of a crosschain messenger contract to be deployed on L1
 */
abstract contract L2CrosschainMessenger {
    IERC20Inbox public immutable inbox;

    constructor(address inbox_) {
        inbox = IERC20Inbox(inbox_);
    }

    modifier onlyCounterpartGateway(address l3Counterpart) {
        // A message coming from the counterpart gateway was executed by the bridge
        IBridge bridge = inbox.bridge();
        require(msg.sender == address(bridge), "NOT_FROM_BRIDGE");

        // And the outbox reports that the L2 address of the sender is the counterpart gateway
        address l2ToL1Sender = IOutbox(bridge.activeOutbox()).l2ToL1Sender();
        require(l2ToL1Sender == l3Counterpart, "ONLY_COUNTERPART_GATEWAY");

        _;
    }
}

/**
 * @title Minimum expected implementation of a crosschain messenger contract to be deployed on L3
 */
abstract contract L3CrosschainMessenger {
    address internal constant ARB_SYS_ADDRESS = address(100);

    /**
     * Emitted when calling sendTxToL1
     * @param from account that submits the L3-to-L2 message
     * @param to account recipient of the L3-to-L2 message
     * @param id id for the L3-to-L2 message
     * @param data data of the L3-to-L2 message
     */
    event TxToL1(
        address indexed from,
        address indexed to,
        uint256 indexed id,
        bytes data
    );

    modifier onlyCounterpartGateway(address l2Counterpart) {
        require(
            msg.sender == AddressAliasHelper.applyL1ToL2Alias(l2Counterpart),
            "ONLY_COUNTERPART_GATEWAY"
        );

        _;
    }

    /**
     * Creates an L3-to-L2 message to send over to L2 through ArbSys
     * @param from account that is sending funds from L3
     * @param to account to be credited with the tokens in the destination layer
     * @param data encoded data for the L3-to-L2 message
     * @return id id for the L3-to-L2 message
     */
    function _sendTxToL2(
        address from,
        address to,
        bytes memory data
    ) internal returns (uint256) {
        uint256 id = ArbSys(ARB_SYS_ADDRESS).sendTxToL1(to, data);

        emit TxToL1(from, to, id, data);
        return id;
    }
}
```

{% endcode %}

### ICustomGateway.sol

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/**
 * @title Minimum expected interface for a custom gateway
 */
interface ICustomGateway {
    function outboundTransfer(
        address l1Token,
        address to,
        uint256 amount,
        uint256 maxGas,
        uint256 gasPriceBid,
        bytes calldata data
    ) external payable returns (bytes memory);

    function finalizeInboundTransfer(
        address l1Token,
        address from,
        address to,
        uint256 amount,
        bytes calldata data
    ) external payable;

    function getOutboundCalldata(
        address l1Token,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) external view returns (bytes memory);

    function calculateL2TokenAddress(address l1Token) external view returns (address);

    function counterpartGateway() external view returns (address);
}

/**
 * @title Minimum expected interface for a custom gateway to be deployed on L1
 */
interface IL2CustomGateway is ICustomGateway {
    event DepositInitiated(
        address l1Token,
        address indexed from,
        address indexed to,
        uint256 indexed sequenceNumber,
        uint256 amount
    );

    event WithdrawalFinalized(
        address l1Token,
        address indexed from,
        address indexed to,
        uint256 indexed exitNum,
        uint256 amount
    );

    function outboundTransferCustomRefund(
        address l1Token,
        address refundTo,
        address to,
        uint256 amount,
        uint256 maxGas,
        uint256 gasPriceBid,
        bytes calldata data
    ) external payable returns (bytes memory);
}

/**
 * @title Minimum expected interface for a custom gateway to be deployed on L2
 */
interface IL3CustomGateway is ICustomGateway {
    event WithdrawalInitiated(
        address l1Token,
        address indexed from,
        address indexed to,
        uint256 indexed l2ToL1Id,
        uint256 exitNum,
        uint256 amount
    );

    event DepositFinalized(
        address indexed l1Token,
        address indexed from,
        address indexed to,
        uint256 amount
    );
}
```

{% endcode %}

## STEP 3 - 토큰 설정

커스텀 게이트웨이를 이용하기 위한 ERC20 토큰 컨트랙트는 [범용적 커스텀 게이트웨이](/erc-20/undefined-1) 와 매우 흡사하지만, 게이트웨이에 토큰을 등록하는 과정에 생략되어 있습니다. 이는 사용자가 직접 커스텀 게이트웨이를 배포하면서 직접 ERC-20 토큰을 등록하기 때문입니다.

### L2 ERC20 토큰 예시

예시 코드의 <mark style="color:blue;">`registerTokenOnL2()`</mark> 메서드를 보면 <mark style="color:blue;">`L1OrbitCustomGateway(gateway).registerTokenToL2()`</mark> 메서드가 생략되고, <mark style="color:blue;">`L1OrbitGatewayRouter(router).setGateway()`</mark>만 호출되는 것을 확인할 수 있습니다.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ICustomToken} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/ICustomToken.sol";
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol";

import {L1OrbitGatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol";
import {L1OrbitCustomGateway} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol";
import {IL1GatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/IL1GatewayRouter.sol";
import { IERC20Bridge } from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/IERC20Bridge.sol";

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract L2TokenCustomGasToken is Ownable, ERC20, ICustomToken {
    using SafeERC20 for IERC20;

    address public gateway;
    address public router;
    bool internal shouldRegisterGateway;

    constructor(string memory name_, string memory symbol_,uint256 _initialSupply,address _gateway, address _router) ERC20(name_, symbol_) {
        gateway = _gateway;
        router = _router;
        _mint(msg.sender, _initialSupply * 10 ** decimals());
    }

    function mint() external {
        _mint(msg.sender, 50000000);
    }

    /// @dev See {ERC20-transferFrom}
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override(ICustomToken, ERC20) returns (bool) {
        return super.transferFrom(sender, recipient, amount);
    }

    /// @dev See {ERC20-balanceOf}
    function balanceOf(address account) public view override(ICustomToken, ERC20) returns (uint256) {
        return super.balanceOf(account);
    }

    /// @dev we only set shouldRegisterGateway to true when in `registerTokenOnL2`
    function isArbitrumEnabled() external view override returns (uint8) {
        require(shouldRegisterGateway, "NOT_EXPECTED_CALL");
        return uint8(0xb1);
    }

    /**
     * @dev See {ICustomToken-registerTokenOnL2}
     * In this case, we don't need to call IL1CustomGateway.registerTokenToL2, because our
     * custom gateway works for a single token it already knows.
     */
    function registerTokenOnL2(
        address, /* l2CustomTokenAddress */
        uint256, /* maxSubmissionCostForCustomGateway */
        uint256 maxSubmissionCostForRouter,
        uint256, /*  maxGasForCustomGateway */
        uint256 maxGasForRouter,
        uint256 gasPriceBid,
        uint256, /* valueForGateway */
        uint256 valueForRouter,
        address creditBackAddress
    ) public override payable onlyOwner {
        // we temporarily set `shouldRegisterGateway` to true for the callback in registerTokenToL2 to succeed
        bool prev = shouldRegisterGateway;
        shouldRegisterGateway = true;

        address inbox = IL1GatewayRouter(router).inbox();
        address bridge = address(IInbox(inbox).bridge());

        // transfer fees from user to here, and approve router to use it
        {
            address nativeToken = IERC20Bridge(bridge).nativeToken();

            IERC20(nativeToken).safeTransferFrom(
                msg.sender,
                address(this),
                valueForRouter
            );
            IERC20(nativeToken).approve(router, valueForRouter);
        }

        L1OrbitGatewayRouter(router).setGateway(
            gateway,
            maxGasForRouter,
            gasPriceBid,
            maxSubmissionCostForRouter,
            creditBackAddress,
            valueForRouter
        );

        shouldRegisterGateway = prev;
    }
}

```

{% endcode %}

L3에 배포되는 ERC-20 토큰은 범용적 커스텀 게이트웨이와 똑같이 구현합니다.

### L3 ERC-20 토큰 예시

핵심 포인트는 사용자가 입출금 시 게이트웨이가 L3 ERC-20 토큰을 발행(mint)하거나 소각(burn)할 수 있도록 <mark style="color:blue;">`bridgeMint()`</mark>와 <mark style="color:blue;">`bridgeBurn()`</mark> 메서드가 반드시 구현되어 있어야 한다는 점입니다.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract L3Token is ERC20, IArbToken {
    address public l3Gateway;
    address public override l1Address; /** override by arbitrum */

    modifier onlyL3Gateway() {
        require(msg.sender == l3Gateway, "NOT_GATEWAY");
        _;
    }

    constructor(string memory name_, string memory symbol_,address _l3Gateway, address _l2TokenAddress) ERC20(name_, symbol_) {
        l3Gateway = _l3Gateway;
        l1Address = _l2TokenAddress;
    }

    /**
     * @notice should increase token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeMint(address account, uint256 amount) external override onlyL3Gateway {
        _mint(account, amount);
    }

    /**
     * @notice should decrease token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeBurn(address account, uint256 amount) external override onlyL3Gateway {
        _burn(account, amount);
    }
}

```

{% endcode %}

## STEP 4 - 토큰 등록과 커스텀 게이트웨이

커스텀 게이트웨이와 ERC-20 토큰을 L2, L3에 각각 배포했다면 (총 4개의 컨트랙트가 배포됬습니다. ) 게이트웨이와 게이트웨이 라우터에게 컨트랙트에 대한 정보를 등록할 차례입니다.

### 컨트랙트 주소 저장

직접 배포한 커스텀 게이트웨이에서 <mark style="color:blue;">`setTokenBridgeInformation()`</mark> 메서드를 통해 서로의 주소와 토큰 컨트랙트 주소를 저장합니다.

```tsx
let res = await parentCustomGateway.setTokenBridgeInformation(
  parentERC20.address,
  childERC20.address,
  childCustomGateway.address
);
let receipt = await res.wait();
console.log(`setTokenBridgeInformation L2 tx hash: ${receipt.transactionHash}`

res = await childCustomGateway.setTokenBridgeInformation(
  parentERC20.address,
  childERC20.address,
  parentCustomGateway.address
);
receipt = await res.wait();
console.log(`setTokenBridgeInformation L3 tx hash: ${receipt.transactionHash}`
```

### 페어링 설정

직접 배포한 커스텀 게이트웨이를 사용하기 위해서는 각 체인의 게이트웨이 라우터에 등록 및 페어링 설정이 필요합니다. 이 과정에서 게이트웨이 라우터는 두 체인에 배포되어 있기 때문에, [**retryable ticket**](https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging)을 활용하여 페어링을 진행하며, 이 과정에서 L2 ERC-20 DKA가 수수료로 지불됩니다.

페어링을 설정하기 위한 등록 과정은 L2 ERC-20 토큰 컨트랙트의 메서드를 호출하면서 시작되며, \
이를 위해 L2 ERC-20 토큰 컨트랙트가 L2 ERC-20 DKA를 사용할 수 있도록 사전에 승인을 완료해야 합니다.

{% hint style="info" %}
디카르고가 제공하는 토큰 브릿지는 아비트럼의 Retryable ticket을 활용하여 구축된 디앱입니다.

Retryable ticket은 L2에서 트랜잭션을 생성하고 이를 L3에서 실행할 수 있도록 전달하는 메커니즘입니다.

사용자는 이 과정을 통해 L3에서 원하는 작업을 L2에서 수행할 수 있으며, 트랜잭션 처리에 필요한 수수료는 L2의 ERC-20 DKA로 지불됩니다.
{% endhint %}

{% code overflow="wrap" %}

```tsx
const res = await customTokenBridge.approveGasTokenForCustomTokenRegistration({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
});

const receipt = await res.wait();
console.log(`approve gas token to L2 ERC20 Contract tx hash: ${receipt.transactionHash}`

const allowance = await customTokenBridge.allowanceGasTokenToParentERC20(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

{% endcode %}

### Gateway 등록

L2 ERC-20에 구현된 <mark style="color:blue;">`registerTokenOnL2`</mark> 메서드를 통해 <mark style="color:blue;">`router.setGateway`</mark> 메서드를 호출하여, L2 게이트웨이 라우터에 L2 ERC-20 토큰의 게이트웨이가 직접 배포한 L2 커스텀 게이트웨이임을 등록합니다. 이 과정은 디카르고 체인으로 메시지를 전달하여, L3 커스텀 게이트웨이를 L3 게이트웨이 라우터에 등록하는 동일한 작업을 수행합니다.

```tsx
const res = await customTokenBridge.registerCustomToken(
  parentERC20.address,
  childERC20.address,
  parentSigner,
  childProvider
);

const receipt = await res.wait();
console.log(`register tx hash: ${receipt.transactionHash}`
```

두 커스텀 게이트웨이는 각각 독립적인 두 체인에 등록되고 최종적으로 페어링되기까지는 일정 시간이 필요합니다. 등록 요청 상태는 "대기"로 표시되며, 약 10분 후 최종적으로 디카르고 체인에서 호출됩니다.

```tsx
const l2ToL3Msgs = await receipt.getParentToChildMessages(childProvider);

// 메시지가 디카르고 체인에서 호출될 때 까지 대기합니다.
await l2ToL3Msgs[0].waitForStatus();
```

상태는 [dScanner의 L2 ➔ L3 Transactions 페이지](https://warehouse.dscanner.io/txs-deposits)에서 확인할 수 있습니다.

## 토큰 입출금

이제 커스텀 게이트웨이를 통해 아비트럼 체인과 디카르고 체인 간의 입출금을 수행할 준비가 완료되었습니다.&#x20;

이후 입출금 과정은 모두 동일한 방식으로 동작하며, [표준 게이트웨이](/erc-20/undefined)의 입출금 방식을 참고하여 진행하면 됩니다.


# DKA 브릿징이란?

<figure><img src="/files/48v4NfFwmjQHwNY1lC3Z" alt=""><figcaption></figcaption></figure>

DKA는 디카르고 체인의 네이티브 토큰으로, 트랜잭션 실행에 필요한 수수료를 지불하는 데 사용됩니다.&#x20;

이를 위해 먼저 아비트럼 체인(Layer 2, L2)에 보유 중인 ERC-20 DKA 토큰을 디카르고 체인(Layer 3, L3)의 DKA 네이티브 토큰으로 전환해야 합니다.&#x20;

이 과정을 돕는 기능을 **브릿지**라고 합니다.

지금부터 DKA 브릿지 가이드를 [@dkargo/sdk](https://github.com/dKargo/dkargo-sdk)에서 제공하는 함수들을 활용해 알아보겠습니다.

## 레퍼런스

### **아비트럼 네이티브 브릿지 (Arbitrum Native Bridge)**

아비트럼과 디카르고 사이에 구축된 브릿지는 아비트럼의 ETH Bridge와 매우 유사한 메커니즘으로 동작합니다.&#x20;

아비트럼에서 작성한 내용을 통해 심도 있는 분석이 가능합니다.

* [**ETH bridgeing**](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/token-bridge-ether)

### **@dKargo/sdk tutorial**

디카르고 sdk를 활용하면 개발자가 직접 작성한 코드를 통해 DKA 브릿지 기능을 이용할 수 있습니다.&#x20;

디카르고 튜토리얼 (dkargo-tutorials)은 <mark style="color:blue;">`ethers.ts`</mark>와 <mark style="color:blue;">`hardhat.ts`</mark>를 함께 사용하여 디카르고 sdk의 사용 방법에 대해 상세히 설명하고 있습니다.

* [**dkargo-tutorials**](https://github.com/dKargo/dkargo-tutorials)


# DKA 입금

입금(Deposit)은 아비트럼 체인(L2)에 보유 중인 ERC-20 DKA를 디카르고 체인(L3)으로 전송하는 과정을 의미합니다.

이 과정은 아비트럼에 배포된 [bridge 컨트랙트](https://sepolia.arbiscan.io/address/0xC7e932238A2d9fccFa33FF5e8Deed966F0460Ea7)와 디카르고 시퀀서의 협력을 통해 단계별로 수행됩니다.

## STEP 1 - L2 ERC-20 DKA 승인

L2에 보유 중인 ERC-20 DKA를 디카르고 체인으로 전송하려면, 먼저 인박스 (Inbox)컨트랙트가 사용자의 DKA 토큰에 접근할 수 있는 승인 작업이 필요합니다.

```jsx
const depositAmount = parseEther('1');
const res = await dkaBridge.approveGasToken({
  parentSigner,
  amount:depositAmount
});
const receipt = await res.wait();
console.log(`approve DKA token to Inbox Contract tx hash: ${receipt.transactionHash}`)

const allowance = await dkaBridge.allowanceGasTokenToInbox(
  parentSigner.address,
  parentProvider
);

console.log(`allowance amount: ${allowance}`
```

{% hint style="info" %}
파라미터에 <mark style="color:blue;">`depositAmount`</mark> 대신 <mark style="color:blue;">`null`</mark>을 입력하면, 최대 가능한 수량을 승인하게 됩니다.&#x20;

브리지를 사용할 때마다 매번 입금 금액을 개별적으로 승인해야 하지만, 이렇게 설정하면 최초 한 번만 승인하면 되고 이후에는 추가 승인이 필요 없습니다.
{% endhint %}

## STEP 2 - DKA 입금

승인이 완료되면, 인박스 컨트랙트의 <mark style="color:blue;">`depositEth()`</mark> 메서드를 호출하여 입금을 진행합니다.&#x20;

인박스는 사용자의 L2 DKA를 브릿지 컨트랙트로 전송합니다.

```jsx
const res = await dkaBridge.deposit({
  parentSigner,
  amount: depositAmount,
});
const receipt = await res.wait();
console.log(`deposit DKA L2 tx hash: ${receipt.transactionHash}`)
```

입금된 DKA은 아비트럼에 배포된 브릿지 컨트랙트에 보관되며, 해당 자산은 잠기게(lock) 됩니다.

## STEP 3 - 입금 상태

브릿지 컨트랙트로 L2 DKA를 전송했다고 해서 L3에 DKA가 즉시 입금되지는 않습니다.&#x20;

입금 상태는 "대기"로 표시되며, 약 10분의 시간이 지난 후 최종적으로 디카르고 체인 계정으로 잔액이 전송됩니다.

```jsx
// 메시지가 디카르고 체인에서 호출될 때 까지 대기합니다.
await receipt.waitForChildTransactionReceipt(childProvider);
```


# DKA 출금

출금(Withdraw)은 디카르고 체인(L3)에 보유 중인 DKA를 아비트럼 체인(L2)으로 전송하는 과정을 의미합니다.&#x20;

이 과정은 아비트럼에 배포된 [브릿지 컨트랙트](https://sepolia.arbiscan.io/address/0x68Cdd5F1D270C2Ea2F309E310E52aC2Ad1e4e780#code)와 디카르고 시퀀서가 협력하여 단계적으로 수행됩니다.

## STEP 1 - Withdraw DKA

DKA 출금은 **ArbSys** 프리컴파일의 <mark style="color:blue;">`withdrawEth()`</mark> 메서드를 사용하여 수행할 수 있습니다.

출금 요청된 디카르고 체인의 DKA는 소각되며, L2 브릿지 컨트랙트에 보관된 L2 DKA는 이후 단계를 거쳐 사용자에게 반환됩니다.

```jsx
const res = await dkaBridge.withdraw({
  childSigner,
  amount,
  destinationAddress: parentSigner.address,
  from: childSigner.address,
});

const receipt = await res.wait();
console.log(`withdraw DKA L3 tx hash: ${receipt.transactionHash}`)
```

## STEP 2: Withdraw Status

출금 요청 후 L2에서 DKA를 수령하려면 약 1시간의 분쟁 기간이 경과해야 하며, 그 기간 동안은 대기 상태로 유지됩니다.

```jsx
const timeToWaitMs = 1000 * 60
const message = await receipt.getChildToParentMessages(parentProvider);

// 6.4일의 분쟁 기간 지날때 까지 대기합니다.
await message[0].waitUntilReadyToExecute(childProvider, timeToWaitMs);
```

출금 상태는 [**디스캐너 (dScanner)의 L3 ➔ L2 Transactions**](https://warehouse.dscanner.io/txs-exit) 페이지에서 확인할 수 있습니다.

## STEP 3: Claim DKA

대기 기간이 종료되면, 사용자는 브릿지 컨트랙트에서 출금을 요청한 DKA를 청구할 수 있는 자격을 얻게 됩니다.&#x20;

이후, 사용자는 아웃박스 (outbox)컨트랙트를 통해 출금 요청한 DKA를 청구할 수 있습니다.

```jsx
const message = await receipt.getChildToParentMessages(parentSigner);
const res = await message[0].execute(childProvider);
const executeReceipt = await res.wait();
console.log(`claim DKA L2 tx hash: ${executeReceipt.transactionHash}`)
```

*최종 수정 : **2026년 5월  6일***


# 포우셋이란?

<figure><img src="/files/Xa2RMg6r2Vws16BNk5Zt" alt=""><figcaption></figcaption></figure>

포우셋 (Faucet)은 수도꼭지라는 뜻을 가지고 있는 애플리케이션입니다.&#x20;

포우셋은 디카르고 웨어하우스에서 컨트랙트를 테스트하기 위한 테스트넷 토큰을 제공합니다.&#x20;

메인넷에 디앱 (dApp)을 배포하기 전에 포우셋을 통해 획득한 테스트넷 토큰을 사용하여 테스트넷에서 컨트랙트 배포 및 트랜잭션 전송 등 다양한 활동을 테스트할 수 있습니다.

테스트넷 토큰은 실제 가치가 없으며, 메인넷에서는 사용할 수 없습니다.

* [디카르고 포우셋 바로가기](https://dkargo.io/developers/faucet)


# 포우셋 사용하기

포우셋 (Faucet)을 받기 위해서는 디카르고 웨어하우스에서 사용할 수 있는 형태의 주소 (Address)가 필요합니다.&#x20;

기존 EVM 계열 체인에서 사용하던 주소는 동일하게 사용할 수 있습니다.

하나의 주소에 대해 24시간에 1회, 1 $DKA (Arbitrum Sepolia)를 수령할 수 있습니다.

[포우셋 페이지](https://dkargo.io/developers/faucet)에 접속하여 $DKA를 요청합니다.

기존에 보유하신 주소를 사용하시거나 새로운 주소를 생성하여 사용할 수 있습니다.

&#x20;해당 페이지에서는 별도의 지갑 생성을 지원하지 않습니다.

<figure><img src="/files/6Hc1RiwtZHDWTPxQBRtg" alt="" width="563"><figcaption></figcaption></figure>

유효하지 않은 주소를 입력한 경우 아래와 같은 오류가 발생합니다.&#x20;

주소를 잘못입력하였거나 호환되지 않는 주소인지 확인해 주세요.

<figure><img src="/files/SkOeG6cu7o9id8WnmGQZ" alt="" width="563"><figcaption></figcaption></figure>

성공적으로 포우셋이 진행되었다면 Success로 표시된 트랜잭션을 확인할 수 있습니다. 이 경우, 주소에 $DKA 전송이 완료된 상태입니다. 지급 내역은 총 5개까지 표시되며 가장 오래된 내역은 삭제됩니다.

<figure><img src="/files/P12E1fQemQK5QECKpatc" alt="" width="563"><figcaption></figcaption></figure>

네트워크 장애나 통신 문제로 $DKA 발급에 문제가 생겼다면 다음과 같이 Fail로 표시된 Tx가 생성되거나, Tx가 생성되지 않을 수 있습니다. 다시 한번 $DKA를 요청해 주세요.

<figure><img src="/files/vGjvfh1uSnAjSiUFV8dC" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/yxDvVhIB20IXGKxYsRza" alt="" width="563"><figcaption></figcaption></figure>

지급받은 $DKA는 [dScanner](https://warehouse.dscanner.io/)나 개인 지갑에서 [디카르고 테스트 네트워크](/undefined-1/undefined-2)를 추가하여 확인할 수 있습니다.


# 체인 스냅샷 다운로드

사용자가 풀 노드 (Full Node)를 운영하게 되면 초기 실행 시 디카르고 체인으로부터 블록 정보를 받아 동기화를 진행합니다.&#x20;

기록된 블록 정보가 많을수록 동기화에 필요한 시간이 늘어납니다. 따라서, 디카르고는 최근 특정 지점에서 저장된 스냅샷 (Snapshot) 데이터를 제공하고 있습니다. 최신 스냅샷 주소는 다음과 같습니다.

## 메인넷 풀 노드 (Mainnet Full Node)

* S3 Snapshot Link
  * <https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz>

## 웨어하우스 풀 노드 (Warehouse Full Node)

* S3 Snapshot Link
  * <https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz>


# 디카르고 컨트랙트 주소

<figure><img src="/files/EpaNba2WXFvBO6NLaHBu" alt=""><figcaption></figcaption></figure>

체인 운영을 위한 아비트럼 L2 컨트랙트와 디카르고 L3 컨트랙트 정보입니다.

## 1. 메인넷 (Mainnet)

### 1.1 체인 정보

<table><thead><tr><th width="284.66015625">Name</th><th width="460.390625">Address</th></tr></thead><tbody><tr><td>ChainId</td><td>61022894</td></tr><tr><td>ERC20 DKA (L1)</td><td>0x5dc60C4D5e75D22588FA17fFEB90A63E535efCE0</td></tr><tr><td>Arbitrum ERC20 DKA (L2)</td><td>0x1E2C41d3fF045488D0921591e6B5532583e54F1C</td></tr></tbody></table>

### 1.2 코어 컨트랙트

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>rollup</td><td>0x11e3D0e9604a0AD8a8B32068B95e83d7C63b3af7</td></tr><tr><td>inbox</td><td>0xB17a5495FA25FBcA887083b7048Bc60A796A201B</td></tr><tr><td>outbox</td><td>0x947fe294C167A6e9b7bc5c328AeF0aCe9ac83584</td></tr><tr><td>adminProxy</td><td>0x2582E771BC50f6273b17349780d3f8eB98A2b143</td></tr><tr><td>sequencerInbox</td><td>0x48781bAec9B5f9eBCf6fd96134f24231c6987Aa0</td></tr><tr><td>bridge</td><td>0x42C4b496edA79215872De91f71D77F434098e162</td></tr><tr><td>utils</td><td>0x7C100c97a54e2D309a194752Df2f66922A802be3</td></tr><tr><td>validatorWalletCreator</td><td>0x69A6dE0B9BeC9edb33805167327a6ABEf0C69Fd2</td></tr></tbody></table>

### 1.3 L2 컨트랙트

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0x03141af6d5ca65E8D3Ee5CbC17f9ff26D81D47E3</td></tr><tr><td>multicall</td><td>0x90B02D9F861017844F30dFbdF725b6aa84E63822</td></tr><tr><td>proxyAdmin</td><td>0x2582E771BC50f6273b17349780d3f8eB98A2b143</td></tr><tr><td>router</td><td>0xcF6298ca74B278e5CB02B75f100D766BDfAC11A2</td></tr><tr><td>standardGateway</td><td>0x306485BFA7c6c0b533A9Bd3C3B363bE848c7A289</td></tr><tr><td>weth</td><td>0x0000000000000000000000000000000000000000</td></tr></tbody></table>

### 1.4 L3 컨트랙트

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0xd8a9B86bBcd534C69CE1DcdeC7B463BfE0378526</td></tr><tr><td>multicall</td><td>0xE5183093695E95368902DD7c018F4ae910d79AAD</td></tr><tr><td>proxyAdmin</td><td>0x5CF9C1795bA901aC40aBF6Ca1BfBD40a0E7E6c5A</td></tr><tr><td>router</td><td>0x8e7f3c9a7743a673C2395eD58bcb5b57168291e4</td></tr><tr><td>standardGateway</td><td>0xE400CAeDEf6F46E6C9aF10324F2C308363A1e246</td></tr><tr><td>weth</td><td>0x0000000000000000000000000000000000000000</td></tr></tbody></table>

## 2. 웨어하우스 (Testnet)

### 2.1 체인 정보

<table><thead><tr><th width="284.66015625">Name</th><th width="460.390625">Address</th></tr></thead><tbody><tr><td>parentChainId</td><td>61022448</td></tr><tr><td>Sepolia ERC20 DKA (L1)</td><td>0xB55F1261a635919fEEba99e4142608D589d72842</td></tr><tr><td>Arbitrum Sepolia ERC20 DKA (L2)</td><td>0x6d9c6F7445C4D3a8fa3fC269f020e12eBF3ACD66</td></tr></tbody></table>

### 2.2 코어 컨트랙트

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>rollup</td><td>0xe66eBA4f90911fd14B2183b1EF0e9636Bbda1e0e</td></tr><tr><td>inbox</td><td>0xaecdEd066B62712Ad6Ce7F65E00571F8991b8642</td></tr><tr><td>outbox</td><td>0x149607bCbcCDBB39f76e19943a6d150E2a2f66a2</td></tr><tr><td>adminProxy</td><td>0x66adb680E5a4f1a83796044490342D9832b43228</td></tr><tr><td>sequencerInbox</td><td>0xCEcDd6E3ff446BB9D0bef548B1F481bc09202885</td></tr><tr><td>bridge</td><td>0xC7e932238A2d9fccFa33FF5e8Deed966F0460Ea7</td></tr><tr><td>utils</td><td>0x7C100c97a54e2D309a194752Df2f66922A802be3</td></tr><tr><td>validatorWalletCreator</td><td>0xFAd2C6Cb969Ab7B18d78BD63e512b650bb70B570</td></tr></tbody></table>

### 2.3 L2 컨트랙트

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0x203F2BA85b3ea74d68642D89143174D3dFbEf971</td></tr><tr><td>multicall</td><td>0xce1CAd780c529e66e3aa6D952a1ED9A6447791c1</td></tr><tr><td>proxyAdmin</td><td>0x66adb680E5a4f1a83796044490342D9832b43228</td></tr><tr><td>router</td><td>0x97EA4122cf29525bD0cF7B49700478fE4A914249</td></tr><tr><td>standardGateway</td><td>0x95F29bAE1Fb7e9FB5F85C22666550c541F426dA7</td></tr><tr><td>weth</td><td>0x980B62Da83eFf3D4576C647993b0c1D7faf17c73</td></tr></tbody></table>

### 2.4 L3 컨트랙트

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0x57afe9E80bb837D91C7E871bF68e68142A4Ea0d2</td></tr><tr><td>multicall</td><td>0xb5138E24231Bd50367Ef37a476630c42550e9E37</td></tr><tr><td>proxyAdmin</td><td>0xF78D7032E6e24B54159Ea3365e82B0abe399567d</td></tr><tr><td>router</td><td>0xaaa492673Ea2E1A881Edd34446de425ffe6B23BF</td></tr><tr><td>standardGateway</td><td>0xD9B2b6A1D61140A24b6E9a1dA9B836A8B73e4cE8</td></tr><tr><td>weth</td><td>0x0000000000000000000000000000000000000000</td></tr></tbody></table>


# Audit reports

## 디카르고의 보안 접근 방식

디카르고(dKargo)는 보안을 단발적인 점검이 아닌, 메인넷 운영 전반에 걸친 핵심 프로세스로 정의합니다. 우리는 다음과 같은 원칙을 바탕으로 네트워크의 무결성을 유지합니다.

* **선제적 위협 차단**: 설계 단계부터 잠재적 위협 모델링을 수행하여 취약점을 사전에 식별하고 대응합니다.
* **지속적 객관적 검증**: 자체 보안 점검에 그치지 않고, 글로벌 보안 전문 기관을 통해 정기적인 감사를 실시하여 기술적·운영적 투명성을 확보합니다.
* **실시간 모니터링 및 고도화**: 감사 결과를 바탕으로 권한 분산, 데이터 가용성 강화 등 보안 인프라를 상시 업데이트하며 진화하는 위협에 대응합니다.

아래 리스트는 디카르고가 수행한 정기 보안 감사 리포트이며, 생태계 확장에 맞춰 신규 감사 결과가 지속적으로 추가 및 업데이트될 예정입니다.

## Security Audit Reports

<table><thead><tr><th width="100.37890625">Auditor</th><th width="116.5">Audit date</th><th width="406.48046875">Audited code</th><th width="125.5234375">View report</th></tr></thead><tbody><tr><td>Theori</td><td>01/19/2026</td><td>Nitro Contract v3.1.0 &#x26; StandardArbERC20 DKA Contract</td><td><a href="https://github.com/dKargo/nitro-contracts/blob/f6430dfaa4d647f61857ef4c441abbce2ec53dfb/audit/%5BTheori%5D%20dKargo%20L3%20Deployment_Operation%20Security%20Assessment%20Final%20Report.pdf">view</a></td></tr></tbody></table>


# 버그 바운티 프로그램

<figure><img src="/files/76JTzLfHIduAoFVp0BFV" alt=""><figcaption></figcaption></figure>

디카르고 버그 바운티 프로그램은 테스트넷 및 메인넷의 보안성과 안정성 강화를 목적으로 운영됩니다. 본 문서에서는 프로그램의 목적, 평가 기준, 보상 체계 및 제출 프로세스를 설명합니다.

## 심각도 등급 및 리워드

{% hint style="info" %}
버그 심각도는 메인넷에 미치는 영향에 따라서 4단계로 분류됩니다.
{% endhint %}

<table><thead><tr><th width="131.73046875">등급</th><th width="470.1953125">설명 및 예시</th><th>리워드</th></tr></thead><tbody><tr><td>Critical (심각)</td><td><ul><li>시스템 핵심 기능 무력화 및 악용될 가능성이 있는 취약점</li><li>네트워크 중단, 자산 탈취, 주요 데이터 변조와 같은 심각한 보안 위협이 있는 취약점</li></ul></td><td>$2,000</td></tr><tr><td>High (높음)</td><td><ul><li>서비스 기능에 영향을 미칠 수 있는 취약점</li><li>데이터 조작, API 보안 결함, Faucet 시스템 악용 가능성</li><li>서비스 운영에 직접적인 영향을 줄 수 있는 버그</li></ul></td><td>$700</td></tr><tr><td>Medium (보통)</td><td><ul><li>특정 기능이 의도한 대로 동작하지 않거나 사용자 경험에 부정적인 영향을 줄 수 있는 취약점</li><li>노드 실행 오류, Docs 내 기술적 오류, 입/출금 오류</li></ul></td><td>$200</td></tr><tr><td>Low (낮음)</td><td><ul><li>시스템의 보안에는 영향을 미치지 않지만 UI/UX, 오탈자, 시각적 오류, 프로세스 흐름 결함 등 사소한 버그 포함</li><li>서비스 전반의 품질 개선이 필요한 내용</li></ul></td><td>$25</td></tr></tbody></table>

## 버그 적격 기준

{% hint style="success" %}
다음 조건을 충족하는 경우 버그 신고가 인정됩니다.
{% endhint %}

* L3 메인넷의 정상적인 운영 환경과 기본 설정에서 실제 사용자나 공격자가 악용할 수 있는 문제여야 합니다.
* 재현 가능하고 객관적으로 검증될 수 있어야 하며, 시스템의 보안이나 신뢰성을 위협하는 경우 적격한 버그로 인정됩니다.
* 단순히 시스템을 오작동하게 만들 수 있는 로직 상의 결함은 DDoS에 준하는 문제로, 악용 경로가 명확할 경우 적격 버그로 인정될 수 있습니다.
* 문제가 단일 노드, 클라이언트, 또는 전체 네트워크에서 발생하더라도, 그 영향이 명확해야 하며 실제 운영 환경에서 발생할 수 있는 위협 시나리오가 수반되어야 합니다.
* 최신 릴리스 혹은 문서에 존재하는 기술적 결함이나 구성상의 오류는 영향도에 따라 적격성 여부가 판단됩니다.

## 버그 부적격 기준

{% hint style="danger" %}
다음 항목은 버그 신고에서 제외됩니다.
{% endhint %}

* 물리적 접근이 필요한 공격, 대량의 트래픽으로 시스템 자원을 소진시키는 DDoS 공격 등은 일반적인 소프트웨어 취약점의 범주에서 벗어나므로 버그바운티 대상에서 제외됩니다.
* 단계별 지침, 재현 가능한 예시, 개념 증명 등 세부 정보가 충분하지 않은 경우 버그바운티 대상에서 제외됩니다.
* 오래되었거나 지원이 종료된 브라우저에서만 발생하는 문제, 이미 공개적으로 알려진 취약점, 또는 팀 내부적으로 인지하고 있는 문제가 중복 보고된 경우 역시 보상의 대상이 아닙니다.
* 사용자 개입이 과도하게 필요한 취약점(예: 복잡한 단계의 조작이 요구되는 버그), 단순한 보안 구성 제안 또는 우수 사례 수준의 제안, 개념 증명이 없는 이론적 보고 역시 부적격한 항목으로 간주됩니다.
* 장애는 버그 리포트에 포함되지 않습니다. 장애란 시스템 전체 또는 주요 기능이 일시적으로 작동하지 않는 상태로, 다수의 사용자가 동시에 영향을 받는 문제입니다. 이는 개발자나 보안 전문가가 제어할 수 없는 외부 요인(예: 서버 다운, 네트워크 문제)으로 인해 발생할 수 있습니다.

## 버그바운티 지원 방식

1. 지정된 양식은 존재하지 않지만, 다음의 내용은 반드시 포함되어야 합니다.

| 카테고리       | 내용                                                                           |
| ---------- | ---------------------------------------------------------------------------- |
| 버그 제목      | 버그를 간단히 설명하는 제목                                                              |
| 버그 설명      | 버그의 상세 설명. 어떤 문제가 발생했는지, 어떤 영향을 미치는지 가능한 구체적으로 작성                            |
| 단계 별 재현 방식 | <ul><li>버그를 재현할 수 있는 방식을 단계별로 설명</li><li>환경 설정 및 방식 등 가능한 구체적으로 작성</li></ul> |
| 영향         | 버그가 시스템에 미치는 영향                                                              |
| 심각도        | 심각도 등급 (Critical, High, Medium, Low)                                         |
| 자료         | 버그를 증명할 수 있는 자료 (스크릿샷, 비디오)                                                  |

2. 해당 내용을 포함한 문서를 <hello@dkargo.io> 로 전송해주세요.


# Welcome to dKargo

<figure><img src="/files/6MS4ucm1GnJeQQ5pbZOd" alt=""><figcaption></figcaption></figure>

## Welcom to dKargo: A Blockchain designed for Logistics

dKargo is an EVM-compatible Layer 3 blockchain built on Arbitrum (Layer 2). It enables all standard Ethereum functionalities, including contract deployment, web3.js integrations, and development with Hardhat, while offering lower transaction costs and faster execution.

This documentation provides comprehensive guidance on node setup, RPC endpoints, validators, bridging, staking, and contract deployment, as well as tools for testing and development within the dKargo ecosystem.

### ❖ Run dKargo Node

{% content-ref url="/pages/9GXWI5WmuAL4eiQ491Yl" %}
[Chain RPC](/docs2-eng/run-dkargo-node/chain-rpc)
{% endcontent-ref %}

{% content-ref url="/pages/etmHf2J2LebQFJ41iSeL" %}
[Installation Guide](/docs2-eng/run-dkargo-node/installation-guide)
{% endcontent-ref %}

{% content-ref url="/pages/Go8k3IBmQKn1nNLRnTvP" %}
[Types of Chain Node](/docs2-eng/run-dkargo-node/types-of-chain-node)
{% endcontent-ref %}

{% content-ref url="/pages/HWn0mVCYQnRBhv8BuoSz" %}
[Full Node](/docs2-eng/run-dkargo-node/full-node)
{% endcontent-ref %}

{% content-ref url="/pages/GtyE9NmajrJwimE4Xy8V" %}
[Archive Full Node](/docs2-eng/run-dkargo-node/archive-full-node)
{% endcontent-ref %}

{% content-ref url="/pages/qykWPOpEichkWkI6K4dW" %}
[Validator Node](/docs2-eng/run-dkargo-node/validator-node)
{% endcontent-ref %}

### Wallet Setup

{% content-ref url="/pages/mfhLJ3PvDzwsln2sSxVm" %}
[Connectiong to MetaMask](/docs2-eng/wallet-setup/connectiong-to-metamask)
{% endcontent-ref %}

{% content-ref url="/pages/xxqA2GvN13TOdty5dH8H" %}
[Creating a wallet](/docs2-eng/wallet-setup/creating-a-wallet)
{% endcontent-ref %}

{% content-ref url="/pages/5wSYBeraUKbPoI5tXybG" %}
[Importing a Wallet](/docs2-eng/wallet-setup/importing-a-wallet)
{% endcontent-ref %}

{% content-ref url="/pages/Yv66sKPlaARAFzXJMyLv" %}
[Adding the dKargo Network](/docs2-eng/wallet-setup/adding-the-dkargo-network)
{% endcontent-ref %}

### Validator Operations

{% content-ref url="/pages/bV6PowGSo4Gp6DjhIVVl" %}
[Validator Staking](/docs2-eng/validator-operations/validator-staking)
{% endcontent-ref %}

{% content-ref url="/pages/1oRN4gmRtyeLz3JVntcm" %}
[Staking](/docs2-eng/validator-operations/staking)
{% endcontent-ref %}

{% content-ref url="/pages/sozmWFyZD0DgsXS53tNI" %}
[Unstaking](/docs2-eng/validator-operations/unstaking)
{% endcontent-ref %}

{% content-ref url="/pages/lg2FVbG9xrrGQ6i01CoL" %}
[Claim](/docs2-eng/validator-operations/claim)
{% endcontent-ref %}

### Deploy Contract

{% content-ref url="/pages/YIyLFEjXyLH8AIfxvXAD" %}
[How to deploy a contract](/docs2-eng/deploy-contract/how-to-deploy-a-contract)
{% endcontent-ref %}

{% content-ref url="/pages/mEVgBAi6mFqrnMZji3xj" %}
[Deploying a Contract Using Remix-IDE](/docs2-eng/deploy-contract/deploying-a-contract-using-remix-ide)
{% endcontent-ref %}

{% content-ref url="/pages/e4UMWRHvWKv5qlq1PKvp" %}
[Deploying a Contract Using Hardhat](/docs2-eng/deploy-contract/deploying-a-contract-using-hardhat)
{% endcontent-ref %}

{% content-ref url="/pages/uso5vo83wo1Y3B0HPuDO" %}
[Deploying a Contract Using Foundry](/docs2-eng/deploy-contract/deploying-a-contract-using-foundry)
{% endcontent-ref %}

### ERC-20 Bridging

{% content-ref url="/pages/jm5i8Zbxu3XIZgqruTB5" %}
[ERC-20 BRIDGING](/docs2-eng/erc-20-bridging/erc-20-bridging)
{% endcontent-ref %}

{% content-ref url="/pages/ZbU6Zb54YH3vQWBIKaXa" %}
[Standard Gateway](/docs2-eng/erc-20-bridging/standard-gateway)
{% endcontent-ref %}

{% content-ref url="/pages/9quzKQ5FnOepKekSas9w" %}
[Generic-custom Gateway](/docs2-eng/erc-20-bridging/generic-custom-gateway)
{% endcontent-ref %}

{% content-ref url="/pages/uLmqiXZGg444aLql0u70" %}
[Custom Gateway](/docs2-eng/erc-20-bridging/custom-gateway)
{% endcontent-ref %}

### DKA Bridging

{% content-ref url="/pages/N04gAuNZlruUPiRwQP9y" %}
[DKA Bridging](/docs2-eng/dka-bridging/dka-bridging)
{% endcontent-ref %}

{% content-ref url="/pages/yM0oggGMarpMGMXKFPMt" %}
[DKA Bridging](/docs2-eng/dka-bridging/dka-bridging)
{% endcontent-ref %}

{% content-ref url="/pages/WX5bbHjs4wTJ9YEgjI3q" %}
[DKA Deposit](/docs2-eng/dka-bridging/dka-deposit)
{% endcontent-ref %}

{% content-ref url="/pages/2U8AInxwjoQm8F4cDa0B" %}
[DKA Withdraw](/docs2-eng/dka-bridging/dka-withdraw)
{% endcontent-ref %}

### Faucet

{% content-ref url="/pages/xU34lUJfga02GfmYm4Ig" %}
[Faucet for Testnet Tokens](/docs2-eng/faucet/faucet-for-testnet-tokens)
{% endcontent-ref %}

{% content-ref url="/pages/cefvVm4opxyIAfJwn50z" %}
[Claiming Testnet Tokens](/docs2-eng/faucet/claiming-testnet-tokens)
{% endcontent-ref %}

### Chain Snapshot

{% content-ref url="/pages/a1w6lvo2cVQOiMcxDAim" %}
[Download the Latest Chain Snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot)
{% endcontent-ref %}

### Contract Address

{% content-ref url="/pages/WR4hqqyw65oQ9k4a3WgJ" %}
[Contract Address List](/docs2-eng/contract-address/contract-address-list)
{% endcontent-ref %}


# Chain RPC

<figure><img src="/files/RcBp31dPWgZ1xmizOkks" alt=""><figcaption></figcaption></figure>

To interact with dKargo Chain or develop applications on it, access to a dKargo Chain Node is required.

The dKargo Chain RPC (Remote Procedure Call) provides a JSON-RPC interface, enabling developers and applications to programmatically interact with dKargo Chain Nodes.

Through this interface, various operations can be performed, including:

* Querying blockchain data (e.g., balances, blocks, transactions)
* Submitting write operations (e.g., deploying contracts, sending transactions)

As an EVM-compatible RPC, dKargo Chain supports Ethereum-like methods, allowing seamless integration with tools such as ethers.js and Web3.js.

<table><thead><tr><th width="110.875">Name</th><th width="368.30078125">RPC Urls(s)</th><th width="102.40625">Chain Id</th><th>Underlying chain</th></tr></thead><tbody><tr><td>Warehouse<br>(Testnet)</td><td>https://rpc.warehouse.dkargo.io</td><td>61022448</td><td>arbitrum sepolia</td></tr></tbody></table>


# Installation Guide

<figure><img src="/files/BBNCUsyWRUiM63xmhuUa" alt=""><figcaption></figcaption></figure>

## L2 Arbitrum RPC EndPoints

URL information for communicating with Arbitrum, the base layer of dKargo’s L3.

<table><thead><tr><th width="230.3203125">Name</th><th width="297.984375">RPC Url</th><th>Chain Id</th></tr></thead><tbody><tr><td>Arbitrum One</td><td><a href="https://arb1.arbitrum.io/rpc	">https://arb1.arbitrum.io/rpc	</a></td><td>42161</td></tr><tr><td>Arbitrum Sepolia (Testnet)</td><td><a href="https://sepolia-rollup.arbitrum.io/rpc	">https://sepolia-rollup.arbitrum.io/rpc	</a></td><td>421614</td></tr></tbody></table>

> To avoid rate limits, replace the Arbitrum One RPC URL with a private URL.

## Docker Images

Docker image paths for running a dKargo node.

<table><thead><tr><th width="120.19140625">Type</th><th width="305.9921875">Testnet</th><th>Mainnet</th></tr></thead><tbody><tr><td>Full node</td><td>repo.dkargo.io:59083/warehouse-fullnode:v0.0.1</td><td>repo.dkargo.io:59083/mainnet-fullnode:v0.0.1</td></tr><tr><td>Archive node </td><td>repo.dkargo.io:59083/warehouse-archive:v0.0.</td><td>repo.dkargo.io:59083/mainnet-archivenode:v0.0.1</td></tr><tr><td>Validator node</td><td>repo.dkargo.io:59083/warehouse-validator:v0.0.1</td><td>repo.dkargo.io:59083/mainnet-validator:v0.0.1</td></tr></tbody></table>

## L3 RPC EndPoints

Relayer node information for receiving data from dKargo Chain or sending transactions.

<table><thead><tr><th width="156.75">Type</th><th>Testnet</th><th>Mainnet</th></tr></thead><tbody><tr><td>https endpoint</td><td>https://rpc.warehouse.dkargo.io</td><td>https://mainnet-rpc.dkargo.io</td></tr><tr><td>wss endpoint</td><td>wss://ws.warehouse.dkargo.io</td><td>wss://mainnet-ws.dkargo.io</td></tr></tbody></table>

## **Node Port**

This is the port information used to communicate with the node. By default, the following settings are used.

* 8547: http
* 8548: websocket

## Environment

* `LOCAL_DATA_PATH`: The local PC path where the node data is stored.
* `SNAPSHOT_URL`: The URL where the [snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot) data is provided. For more details, please refer to Chain Snapshot.

{% hint style="danger" %}
When setting up a node, manual configuration is required based on the user’s environment.
{% endhint %}


# Types of Chain Node

<figure><img src="/files/nZC1DDYELNHKbsu4EnyD" alt=""><figcaption></figcaption></figure>

There are three types of dKargo chain nodes that can be run locally.

## Node Types

{% content-ref url="/pages/HWn0mVCYQnRBhv8BuoSz" %}
[Full Node](/docs2-eng/run-dkargo-node/full-node)
{% endcontent-ref %}

{% content-ref url="/pages/GtyE9NmajrJwimE4Xy8V" %}
[Archive Full Node](/docs2-eng/run-dkargo-node/archive-full-node)
{% endcontent-ref %}

{% content-ref url="/pages/qykWPOpEichkWkI6K4dW" %}
[Validator Node](/docs2-eng/run-dkargo-node/validator-node)
{% endcontent-ref %}


# Full Node

A Full Node manages the state of the chain and provides an API that allows other users to interact with the blockchain. It is particularly useful for users who intend to operate a node as a service provider.

## 1. Mainnet

### 1-1. Public L2 Arbitrum RPC EndPoints

URL information for communicating with Arbitrum, the base layer of dKargo’s L3.

* <https://arb1.arbitrum.io/rpc>

> To avoid rate limits, replace the Arbitrum One RPC URL with a private URL.

### 1-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/mainnet-fullnode:v0.0.1`</mark>

### 1-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://mainnet-rpc.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://mainnet-ws.dkargo.io`</mark>

### 1-4. Required Custom Options

* `--init.url`
  * Snapshot URL for downloading initialization data to enable fast synchronization.

* `--parent-chain.connection.url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

* `--node.data-availability.parent-chain-node-url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

### 1-5. Running a Node via Docker

{% code overflow="wrap" %}

```shell
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-fullnode:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: The local PC path where the node data is stored.

### 1-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-fullnode:v0.0.1 --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: The URL where the [snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot) data is provided. For more details, please refer to Chain Snapshot.

### 1-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-fullnode:v0.0.1 --init.url="https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}

## 2. Warehouse (Testnet)

Warehouse is a **testnet** that allows users to experiment before the official launch of the dKargo Mainnet.

### 2-1. Public L2 Arbitrum RPC EndPoints

URL information for communicating with Arbitrum, the base layer of dKargo’s L3.

* [https://sepolia-rollup.arbitrum.io/rpc](https://sepolia-rollup.arbitrum.io/rpc**)

> To avoid rate limits, replace the Arbitrum One RPC URL with a private URL.

### 2-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/warehouse-fullnode:v0.0.1`</mark>

### 2-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://rpc.warehouse.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://ws.warehouse.dkargo.io`</mark>

### 2-4. Required Custom Options

* `--init.url`
  * Snapshot URL for downloading initialization data to enable fast synchronization.

* `--parent-chain.connection.url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

* `--node.data-availability.parent-chain-node-url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

### 2-5. Running a Node via Docker

{% code overflow="wrap" %}

```shell
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-fullnode:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: The local PC path where the node data is stored.

### 2-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-fullnode:v0.0.1 --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: The URL where the [snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot) data is provided. For more details, please refer to Chain Snapshot.

### 2-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-fullnode:v0.0.1 --init.url="https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}


# Archive Full Node

An Archive Full Node retains all functionalities of a Full Node while preserving the entire historical record of the blockchain without deletion. Additionally, it supports debugging interface calls.

Due to these characteristics, an Archive Full Node is ideal for in-depth data analysis and debugging operations.

## 1. Mainnet

### 1-1. Public L2 Arbitrum RPC EndPoints

URL information for communicating with Arbitrum, the base layer of dKargo’s L3.

* <https://arb1.arbitrum.io/rpc>

> To avoid rate limits, replace the Arbitrum One RPC URL with a private URL.

### 1-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/mainnet-archivenode:v0.0.1`</mark>

### 1-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://mainnet-rpc.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://mainnet-ws.dkargo.io`</mark>

### 1-4. Required Custom Options

* `--init.url`
  * Snapshot URL for downloading initialization data to enable fast synchronization.

* `--parent-chain.connection.url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

* `--node.data-availability.parent-chain-node-url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

### 1-5. Running a Node via Docker

{% code overflow="wrap" %}

```shell
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-archivenode:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: The local PC path where the node data is stored.

### 1-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-archivenode:v0.0.1 --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: The URL where the [snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot) data is provided. For more details, please refer to Chain Snapshot.

### 1-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-archivenode:v0.0.1 --init.url="https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}

## 2. Warehouse (Testnet)

Warehouse is a testnet that allows users to experiment before the official launch of the dKargo Mainnet.

### 2-1. Public L2 Arbitrum RPC EndPoints

URL information for communicating with Arbitrum, the base layer of dKargo’s L3.

* [https://sepolia-rollup.arbitrum.io/rpc](https://sepolia-rollup.arbitrum.io/rpc**)

> To avoid rate limits, replace the Arbitrum One RPC URL with a private URL.

### 2-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/warehouse-archive:v0.0.1`</mark>

### 2-3. L3 RPC EndPoints

* https endpoint: <mark style="color:blue;">`https://rpc.warehouse.dkargo.io`</mark>
* wss endpoint: <mark style="color:blue;">`wss://ws.warehouse.dkargo.io`</mark>

### 2-4. Required Custom Options

* `--init.url`
  * Snapshot URL for downloading initialization data to enable fast synchronization.

* `--parent-chain.connection.url`
  * Base layer endpoint URL(default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

### 2-5. Running a Node via Docker

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-archive:v0.0.1
```

{% endcode %}

> `LOCAL_DATA_PATH`: The local PC path where the node data is stored.

### 2-6. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-archive:v0.0.1 --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: The URL where the [snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot) data is provided. For more details, please refer to Chain Snapshot.

### 2-7. Example(with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-archive:v0.0.1 --init.url="https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/archive/latest/archive.tar.gz"
```

{% endcode %}


# Validator Node

A Validator Node monitors the progress of the rollup protocol and actively participates to ensure the secure advancement of the chain state.

## 1. Prerequisites Before Deployment

Several requirements must be met before operating a **Validator Node**. These include:

* A Validator Account
* A Wallet Contract associated with the Validator Account
* An approval and authorization process for the Wallet Contract

### 1-1. Creating a Validator Wallet Contract

To deploy a Validator Node, users must create their own contract wallet using their account.

* The contract can only be controlled by the **deploying account**.
* At this stage, the validator has not yet been granted authorization and must go through an additional approval process.
* A Wallet Contract can be deployed multiple times using the same account. However, if an account owns multiple wallets, it cannot be used to run a node. Please take caution when managing multiple wallets.

For detailed instructions on wallet creation, refer to the following repository:

* <https://github.com/dKargo/dkargo-validator-utils>

### 1-2. Authorization Process

After creating the **Wallet Contract**, its **address must be submitted** to obtain authorization.

dKargo will **review the request** and grant **Validator permissions** upon approval.

For any questions or inquiries, please contact us through the [**dKargo support page**](https://dkargo.io/en/about/contact).

## 2. Mainnet

### 2-1. Public L2 Arbitrum RPC EndPoints

URL information for communicating with Arbitrum, the base layer of dKargo’s L3.

* <https://arb1.arbitrum.io/rpc>

> To avoid rate limits, replace the Arbitrum One RPC URL with a private URL.

### 2-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/mainnet-validator:v0.0.1`</mark>

### 2-3. Required Custom Options

* `--node.staker.parent-chain-wallet.private-key`
  * Private key of the wallet that operates the validator.

* `--init.url`
  * Snapshot URL for downloading initialization data to enable fast synchronization.

* `--parent-chain.connection.url`
  * Base layer endpoint URL (default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

### 2-4. Running a Node via Docker

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY>
```

{% endcode %}

> **`LOCAL_DATA_PATH`:** The local PC path where the validator node’s data is stored.\
> **`PRIVATE_KEY`:** The private key of the wallet used to operate the validator node.

### 2-5. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: The URL where the [snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot) data is provided. For more details, please refer to Chain Snapshot.

### 2-6. Example (with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/mainnet-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url="https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}

## 3. Warehouse (Testnet)

Warehouse is a testnet that allows users to experiment before the official launch of the dKargo Mainnet.

### 3-1. Public L2 Arbitrum RPC EndPoints

URL information for communicating with Arbitrum, the base layer of dKargo’s L3.

* [https://sepolia-rollup.arbitrum.io/rpc](https://sepolia-rollup.arbitrum.io/rpc**)

> To avoid rate limits, replace the Arbitrum One RPC URL with a private URL.

### 3-2. Docker Images

* Latest Docker Image: <mark style="color:blue;">`repo.dkargo.io:59083/warehouse-validator:v0.0.1`</mark>

### 3-3. Required Custom Options

* `--node.staker.parent-chain-wallet.private-key`
  * Private key of the wallet that operates the validator.

* `--init.url`
  * Snapshot URL for downloading initialization data to enable fast synchronization.

* `--parent-chain.connection.url`
  * Base layer endpoint URL (default: Arbitrum public RPC endpoints)

* `--node.data-availability.parent-chain-node-url`
  * Base layer endpoint URL (Default: Arbitrum public RPC endpoints).

### 3-4. Running a Node via Docker

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY>
```

{% endcode %}

> **`LOCAL_DATA_PATH`:** The local PC path where the validator node’s data is stored.\
> **`PRIVATE_KEY`:** The private key of the wallet used to operate the validator node.

### 3-5. Snapshot Sync

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url=<SNAPSHOT_URL>
```

{% endcode %}

> `SNAPSHOT_URL`: The URL where the [snapshot](/docs2-eng/chain-snapshot/download-the-latest-chain-snapshot) data is provided. For more details, please refer to Chain Snapshot.

### 3-6. Example (with snapshot)

{% code overflow="wrap" %}

```bash
docker run -d -p 8547:8547 -p 8548:8548 -v "<LOCAL_DATA_PATH>:/home/user/.arbitrum" repo.dkargo.io:59083/warehouse-validator:v0.0.1 --node.staker.parent-chain-wallet.private-key=<PRIVATE_KEY> --init.url="https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz"
```

{% endcode %}


# Connectiong to MetaMask

MetaMask is a blockchain wallet that supports connections to multiple chains.

MetaMask follows the [BIP-39](https://en.bitcoin.it/wiki/BIP_0039) standard, a Bitcoin-based wallet structure, which generates and manages wallets using a mnemonic (seed phrase).

dKargo also adheres to this address scheme, ensuring compatibility across multiple blockchain networks.


# Creating a wallet

The following instructions are based on the Chrome browser and outline the process of installing the MetaMask extension.

## STEP 1 - Installation

* Visit [MetaMask’s official website](https://metamask.io/) and install the MetaMask Chrome extension.

<figure><img src="/files/L2men9GEyvJgDCNu8xz6" alt="" width="563"><figcaption><p>Click "Add to Chrome" in the top-right corner to install the extension.</p></figcaption></figure>

## STEP 2 - Run

* Launch MetaMask and proceed with creating a new wallet.
* After accepting the terms and conditions, set up a password for the wallet.

<figure><img src="/files/enUc6oEqlHlF0jV02K64" alt=""><figcaption><p>Creating a new wallet and setting a password </p></figcaption></figure>

## STEP 3 - Check Mnemonic

* Review and securely store the mnemonic (seed phrase).
* Complete the verification process to finalize wallet creation.

{% hint style="warning" %}

* The **mnemonic is essential for wallet recovery** and should be stored securely.
* Sharing the mnemonic is equivalent to **sharing access to the wallet**.
* If lost, the **mnemonic cannot be recovered**, so ensure it is safely backed up.
  {% endhint %}

<figure><img src="/files/WVobf7qt7967vuD5UiC3" alt="" width="375"><figcaption></figcaption></figure>


# Importing a Wallet

MetaMask allows users to import wallets created externally using either a Private Key or a Keystore file.

## STEP 1 - Selecting Import Option

* Open MetaMask and navigate to the **wallet list** to view existing wallets.
* Select "Add Account" or "Add Hardware Wallet", then choose "Import Account" to proceed.
* Depending on the import method, enter a Private Key or upload a Keystore file.

<figure><img src="/files/iQDk8TyCptbiM3gDEFWw" alt=""><figcaption></figcaption></figure>

## STEP 2 - Importing via Private Key

* Choose "Private Key" as the import method.
* Enter the Private Key of the wallet you wish to import and complete the process.

<figure><img src="/files/DT2pEzNBFFPNHsOt7ymk" alt="" width="349"><figcaption></figcaption></figure>

## STEP 3 - Importing via Keystore File

* Choose "JSON File" as the import method.
* Upload the Keystore file and enter the password used when the file was created.
* Confirm to complete the wallet import process.

<figure><img src="/files/KkXg2WSL1bnjwc2N8Xwz" alt="" width="530"><figcaption></figcaption></figure>


# Adding the dKargo Network

By default, MetaMask supports Ethereum and a few other preconfigured networks. However, users can manually add custom networks by providing the necessary chain and service provider details.

The following guide explains how to add the dKargo network to MetaMask.

## Mainnet

{% hint style="info" %}
**Scheduled for public release in 2025.**
{% endhint %}

## Warehouse (Testnet)

Warehouse is a testnet that allows users to experiment before the official launch of the dKargo Mainnet.

| Index           | Details                           |
| --------------- | --------------------------------- |
| Network Name    | Warehouse                         |
| RPC URL         | <https://rpc.warehouse.dkargo.io> |
| Chain ID        | 61022448                          |
| Currency Symbol | DKA                               |
| Block Explorer  | <https://warehouse.dscanner.io/>  |

#### Layer2 DKA Token Details

This is the information of the DKA token deployed on Arbitrum Sepolia. You can check the current DKA balance held on Layer 2 through the contract information.

* Name: dKargo
* Symbol: DKA
* Dicimals: 18
* Address : [0x6d9c6F7445C4D3a8fa3fC269f020e12eBF3ACD66](https://sepolia.arbiscan.io/token/0x6d9c6F7445C4D3a8fa3fC269f020e12eBF3ACD66)

## Adding a Custom Network

MetaMask allows users to connect to multiple networks and manually add or remove custom chains beyond the default options. The steps below outline how to add dKargo’s network.

### STEP 1 - Checking the Network List

* Open MetaMask and navigate to the network list to view currently imported environments.
* If dKargo Warehouse is already added, simply switch networks to start using it.

<figure><img src="/files/8j0eCx91HWc7RsOuvxy7" alt="" width="355"><figcaption></figcaption></figure>

### STEP 2 - Adding a New Network

* Select **"Add a Custom Network"** to manually configure a new blockchain.
* Enter the [**network details**](https://app.gitbook.com/o/kVamf4pf2CkZ18iZjo87/s/im6M65ZKMXUcKE8zSRGC/~/changes/13/wallet-setup/adding-the-dkargo-network#mainnet) provided earlier and save the configuration to complete the setup.

<figure><img src="/files/s6vGUWgtCxRiOwXrqceN" alt="" width="563"><figcaption></figcaption></figure>


# Validator Staking

<figure><img src="/files/6ltn6x2KyYsIa39IvQDV" alt=""><figcaption></figcaption></figure>

Staking is an essential process for Validators to participate in the dKargo Chain and defend against malicious attacks.

In the event of any malicious activity affecting data on the dKargo Chain, the staked assets will be used to support the challenge process to maintain network integrity.


# Staking

Each Validator is responsible for verifying the batch submitted via rollup and must stake assets to participate and maintain validator status.

When a Validator operates correctly, it will automatically attempt to stake assets on the RBlock created in the current rollup contract.

{% hint style="info" %}
To initiate staking, users must manually execute the staking script provided in the Git Repository.

* ​<https://github.com/dKargo/dkargo-validator-utils>
  {% endhint %}

If the Validator does not have the required permissions, the following error may occur. If this error appears, please contact[ **dKargo Support**](https://dkargo.io/en/about/contact) through the inquiry page.

{% code overflow="wrap" %}

```jsx
WARN [12-12|02:46:42.480] validator address isn't whitelisted      address=0xf2A21a7EF1CD2e85b7Fb251d68ac98f523c5030b txSender=0xB31EbC1bAA9d165E5fbdF55DDfF60AC0a68f3362
```

{% endcode %}

If the Validator account does not have enough funds for staking, an error message related to insufficient tokens will be displayed. To resolve this, ensure the account used for Validator operation has sufficient ETH before running the node.

{% code overflow="wrap" %}

```jsx
WARN [12-12|02:46:42.480] insufficient funds for intrinsic transaction cost
```

{% endcode %}


# Unstaking

Unstaking is the process of retrieving the staked assets used for Validator operation.\
Once the process is initiated, the funds previously staked in the rollup contract will be transferred back to the Validator Wallet.

To proceed, certain conditions must be met, and the unstaking script must be executed.

{% hint style="info" %}
Unstaking requires manually running the provided script from the Git Repository.

* ​<https://github.com/dKargo/dkargo-validator-utils>
  {% endhint %}

## STEP 1 - Check Withdrawal Eligibility

To withdraw staked funds, the **curren**t staked RBlock must be finalized.

* The staked assets must remain locked for 6.4 days from the last staking event.
* Validator operation must be stopped until the withdrawal period is reached.

## STEP 2 - Unstake ETH

* Once the required period has elapsed, the **unstake function** can be called to retrieve the staked tokens.
* **Unstaked ETH is not immediately transferred** to the Validator Wallet.
* Instead, it remains **held within the rollup contract** and is managed under the `withdrawableFunds` variable.

```jsx
const res = await rollup.returnOldDeposit(stakerWalletAddress);

const receipt = await res.wait();

console.log(receipt.transactionHash)
```


# Claim

Claim is the process of withdrawing tokens from the Validator Wallet to the user's personal wallet.\
This step transfers ETH that was moved to the Wallet Contract during the unstaking process.

{% hint style="info" %}
Claim requires manually running the provided script from the Git Repository.

* ​<https://github.com/dKargo/dkargo-validator-utils>
  {% endhint %}

## STEP 1 - WithdrawStakerFunds

* This step withdraws ETH stored as `withdrawableFunds` in the rollup contract and transfers it to the Validator Wallet Contract.
* Once completed, the ETH will be held in the Validator Wallet Contract, ready for the final withdrawal.

```jsx
const res = await rollup.withdrawStakerFunds();

const receipt = await res.wait();

console.log(receipt.transactionHash)
```

## STEP 2 - Withdraw ETH

* The final step involves withdrawing the staked ETH from the Validator Wallet Contract.
* By calling the appropriate function, the ETH stored in the Validator Wallet will be transferred to the user’s personal wallet.

```jsx
const withdrawReq = await walletContract.withdrawEth(
	ethers.utils.parseEther(withdrawAmount), wallet.address
);

const reciept = await withdrawReq.wait();

console.log(receipt.transactionHash)
```


# How to deploy a contract

<figure><img src="/files/ay6eN9cgaFEKTNzNVw3s" alt=""><figcaption></figcaption></figure>

There are three ways to deploy contracts in dKargo Chain.

* **Deploying a Contract Using Remix-IDE**
* **Deploying a Contract Using Hardhat**
* **Deploying a Contract Using Foundry**


# Deploying a Contract Using Remix-IDE

Remix Project is a development tool that provides developers with the necessary technologies and features for the entire smart contract development process. It also serves as an educational platform for learning and experimenting with Ethereum.

{% hint style="info" %}
When deploying a contract on any blockchain, the native token of that chain must be used to pay for deployment gas fees. This applies to dKargo Chain as well. Before deploying on the dKargo testnet (Warehouse), developers can obtain $DKA testnet tokens via the faucet.\
\
Remix interacts with the blockchain using the wallet registered in [MetaMask](/docs2-eng/wallet-setup/creating-a-wallet).
{% endhint %}

## STEP 1 - Getting Started with Remix

1. Access [Remix](https://remix.ethereum.org/) and navigate to File Explorer, then click ≡ (menu icon). Select \[ + Create Blank ] to create a new Workspace.
2. Create a new file named <mark style="color:blue;">`counter.sol`</mark> and enter the contract code or use the provided sample code.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
contract Counter {
    uint256 public number;
 
    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }
 
    function increment() public {
        number++;
    }
}
```

3. Go to the **\[ Solidity Compile ]** sidebar option and select <mark style="color:blue;">`counter.sol`</mark> to compile the contract.

## STEP 2 - Deploying the Contract

1. Navigate to the **\[ Deploy & Run Transactions ]** sidebar option.
2. Change the ENVIRONMENT dropdown to "Injected Provider - MetaMask".
3. In MetaMask, click **\[ Connect ]** to grant Remix access.

<figure><img src="/files/2Mk6VQr1c6Ev8wuKUZHY" alt="" width="563"><figcaption></figcaption></figure>

4. Once MetaMask is connected, click the **\[ Deploy ]** button to deploy the contract.

<figure><img src="/files/RzlayEjM8r9E4cTrRSLe" alt="" width="563"><figcaption></figcaption></figure>

5. The deployed contract can be verified in[ the Verified Contracts section of dScanner](https://warehouse.dscanner.io/contracts-verified).&#x20;

## STEP 3 - Interacting with the Contract

1. After deploying the contract, interaction is available in the bottom-left tab of Remix.

<figure><img src="/files/o2BAoJeSFVzdr9Vm0o95" alt="" width="563"><figcaption></figcaption></figure>

2. Click **\[ number ]** to retrieve the stored number in the contract. The default value after deployment is **0**.

<figure><img src="/files/8pAriL2y1181a8h4hfk0" alt="" width="279"><figcaption></figcaption></figure>

3. Click **\[ increment ]** to send a transaction that increases the number by +1. Confirm the transaction in MetaMask, which will then be sent to the dKargo Chain.

<figure><img src="/files/Y5HM02EP8ZLyRGfHKvIm" alt="" width="563"><figcaption></figcaption></figure>

4. Click **\[ number ]** again to verify that the stored number has increased by **1**.

<figure><img src="/files/vw08jJrZQINkIVqhU861" alt="" width="280"><figcaption></figcaption></figure>


# Deploying a Contract Using Hardhat

Hardhat is a development framework for Ethereum software, providing various tools for smart contract development, compilation, debugging, and deployment. Since dKargo is compatible with Ethereum's EVM, smart contracts can be deployed on the dKargo network.

\
This guide will walk through the process of building a Wallet dApp using Hardhat and dKargo Warehouse.

{% hint style="info" %}
When deploying a contract on any blockchain, the native token of that chain is required to pay gas fees. This applies to dKargo Chain as well. Before deploying on the dKargo testnet (Warehouse), developers can obtain $DKA testnet tokens via the faucet.
{% endhint %}

## Development Environment

Prerequisites

* **Node.js & npm/yarn:**
  * Installed from [Node.js](https://nodejs.org/).
* **Hardhat**: Development environment for Ethereum.

  * Install with:

  ```bash
  npm install --save-dev hardhat
  ```

## Step 1 - Set Up a Hardhat Project

1. Initialize a new Hardhat project

```solidity
mkdir helloDka
cd helloDka
npx hardhat init
```

2. Install [dotenv](https://github.com/motdotla/dotenv) for managing environment variables.

```bash
npm i dotenv
touch .env
```

3. Below is an example of the **Hardhat project folder structure**:

```bash
/helloDka
├── README.md
├── contracts
|  └── Lock.sol
├── hardhat.config.js
├── ignition
|  └── modules
|     └── Lock.js
├── package-lock.json
├── package.json
└── test
   └── Lock.js
```

## Step 2 - Writing the `Counter.sol` Contract

* Create a new file named <mark style="color:blue;">`Counter.sol`</mark> inside the **`contracts`** folder.
* Copy and paste the following code into the file.

The following contract implements a simple function to set the **`number`** variable.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
contract Counter {
    uint256 public number;
 
    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }
 
    function increment() public {
        number++;
    }
}
```

## Step 3 - Updating the ignition file

* Create a new file named <mark style="color:blue;">`Counter.js`</mark> inside the <mark style="color:blue;">`./ignition/modules`</mark> folder.&#x20;
* Copy and paste the following code into the file.

This script follows the [**Hardhat Ignition standard**](https://hardhat.org/ignition/docs/getting-started#overview) and is responsible for deploying the contract.

```jsx
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
 
module.exports = buildModule("Counter", (m) => {
  const lock = m.contract("Counter");
 
  return { lock };
});
```

## Step 4 - Configuring Hardhat for dKargo Chain

1. Add the necessary plugins and configurations to <mark style="color:blue;">`hardhat.config.js`</mark> <mark style="color:blue;"></mark><mark style="color:blue;">.</mark>

* &#x20;<mark style="color:blue;">`require('dotenv').config() :`</mark> Loads environment variables from the <mark style="color:blue;">`.env`</mark> file.
* `accounts: [process.env.PRIVATE_KEY] :`  Passes the private key stored in the <mark style="color:blue;">`.env`</mark> file.

```jsx
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: "0.8.24",
  networks: {
    warehouse: {
      url: "<https://it-full.dknote.net>",
      accounts: [process.env.PRIVATE_KEY],
    },
  },
  ignition: {
    requiredConfirmations: 1
  }
};

```

2. Add the following code to the file, and ensure that <mark style="color:blue;">`.env`</mark> **is included in the** <mark style="color:blue;">`gitignore`</mark> **file** to prevent exposing sensitive information.

```bash
PRIVATE_KEY=YOUR KEY HERE WITH NO QUOTES
```

## Step 5 - Compile and Deploy

1. Compile <mark style="color:blue;">`counter.sol`</mark>.

```bash
npx hardhat compile
```

2. Deploy the <mark style="color:blue;">`counter.sol`</mark> contract to the Warehouse testnet:

```bash
npx hardhat ignition deploy ./ignition/modules/Counter.js --network warehouse
```

## Step 6 - Viewing the Deployment Transaction

1. Check the transaction details of the deployed contract.

```jsx
npx hardhat ignition transactions chain-2465001
```

2. Search for the transaction information on [dScanner](https://warehouse.dscanner.io/)

## Verify Contract

In Ethereum and other **EVM-based blockchains**, smart contracts are often verified using **Hardhat's `verify` function**.

However, **dKargo Chain** utilizes [dScanner](https://warehouse.dscanner.io/), a **block explorer optimized for logistics services**, which is **not compatible** with Hardhat’s `verify` function.

To ensure **transparency** of deployed contracts, the [Verify Contract](https://warehouse.dscanner.io/verify-contract) feature provided by [dScanner](https://warehouse.dscanner.io/) can be used instead.


# Deploying a Contract Using Foundry

Foundry is a Rust-based Ethereum development tool that enables developers to manage Solidity dependencies, compile and test smart contracts, deploy them, and interact with the blockchain through a command-line interface (CLI).

This guide outlines the process of building a Wallet dApp using Foundry and the dKargo Warehouse Testnet.

{% hint style="info" %}
When deploying a contract on any blockchain, the native token of that chain is required to pay gas fees. This applies to dKargo Chain as well. Before deploying on the dKargo testnet (Warehouse), developers can obtain $DKA testnet tokens via the faucet.
{% endhint %}

## STEP 1 - Get Started with Foundry

To install Foundry, run the following command:

Installing Foundry is simple.

* Linux or MacOS

```
curl -L <https://foundry.paradigm.xyz> | bash foundryup
```

* Windows

```
curl --proto '=https' --tlsv1.2 -sSf <https://sh.rustup.rs/> | sh cargo install --
git <https://github.com/foundry-rs/foundry> foundry-cli anvil --bins --locked
```

## STEP 2 - Set Up Foundry Project

After installing Foundry, the next step is to initialize a new project.

```bash
mkdir helloDka
cd helloDka
forge init .
```

Below is an example of the Foundry project folder structure.

```bash
/helloDka
├── README.md
├── foundry.toml
├── lib
|  └── forge-std
├── script
|  └── Counter.s.sol
├── src
|  └── Counter.sol
└── test
   └── Counter.t.sol
```

## Step 3 - Example Contract: `Counter.sol`

A newly created Foundry project includes a sample contract (<mark style="color:blue;">`counter.sol`</mark>) along with a sample test file by default. The <mark style="color:blue;">`counter.sol`</mark> contract implements a simple function to set the `number` variable.

```jsx
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}

```

## Step 4 - Compiling the Contract

If the contract compiles without errors, it is valid and ready for deployment on Warehouse.

```jsx
forge build
```

## Step 5 - Deploying the Contract on Warehouse

Deploying a contract can be easily completed with a single Forge CLI command.

However, this process requires:

* The Warehouse RPC endpoint
* A private key with sufficient $DKA for transaction fees

Use the provided RPC endpoint URL and ensure the private key is ready before proceeding.

* Warehouse RPC Endpoint : <https://it-full.dknote.net>

```jsx
forge create --rpc-url "<https://it-full.dknote.net>" --private-key YOUR_PRIVATE_KEY  src/Counter.sol:Counter
```

Upon successful execution, the following response will be displayed:

<figure><img src="/files/RMsLI4PmQAsgBBj4H1IZ" alt=""><figcaption></figcaption></figure>

The contract has been successfully deployed on dKargo’s Warehouse testnet.

Copy the contract address specified in "Deployed to", and use it to search for deployment details on [dScanner](https://warehouse.dscanner.io/).


# ERC-20 Bridging

<figure><img src="/files/HaCP3iXNV1x3JMzyZsdm" alt=""><figcaption></figcaption></figure>

The Arbitrum Chain (Layer 2, L2) hosts a variety of ERC-20 tokens, such as WETH and UNI.\
These assets can be permissionlessly bridged to dKargo Chain (Layer 3, L3) using the dKargo Token Bridge.

The process of bridging ERC-20 tokens from Arbitrum to dKargo Chain follows a different procedure than bridging DKA native tokens.

## Design Rationale: Token Bridge

The Token Bridge is a dApp deployed on both Arbitrum Chain and dKargo Chain, facilitating secure and trustless ERC-20 token bridging.\
It utilizes [Arbitrum's cross-chain messaging system](https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging) (retryable tickets) to ensure reliability.

The dKargo Token Bridge is built on [**Offchain Labs**](https://www.offchainlabs.com/)' [**Canonical Bridge**](https://github.com/OffchainLabs/token-bridge-contracts), a well-established solution that has already bridged hundreds of ERC-20 tokens on Arbitrum, ensuring high security and seamless user experience.

## Canonical token bridge implementation

The Token Bridge consists of three key contracts:

1. **ERC-20 Contract**

* Implements the ERC-20 standard for token operations.
* Deployed as paired contracts on both L2 and L3.

2. **Gateways Contract**

* Pairs ERC-20 contracts between L2 and L3, facilitating deposit and withdrawal messages between the two chains.
* L2 Gateway
  * Locks tokens during deposits.
  * Releases tokens during withdrawals.
* L3 Gateway
  * Mints tokens upon deposits.
  * Burns tokens upon withdrawals.

3. **Gateway Routers Contract**

* Handles all **deposit and withdrawal requests** from users.
* Routes transactions to the appropriate **Gateway contract** assigned to each token.

The following guide explains how to use the DKA Bridge using functions provided by [@dkargo/sdk.](https://github.com/dKargo/dkargo-sdk)

## Get Started with Token Bridging

To use the dKargo Token Bridge, three gateway options are available depending on the token type:

1. [Standard Gateway](/docs2-eng/erc-20-bridging/standard-gateway)
2. [Generic-Custom Gateway](/docs2-eng/erc-20-bridging/generic-custom-gateway)
3. [Custom Gateway](/docs2-eng/erc-20-bridging/custom-gateway)

Developers and project builders can choose one of these options based on their project requirements or ERC-20 token characteristics to provide liquidity from Arbitrum to dKargo Chain.

### Reference <a href="#reference" id="reference"></a>

#### Offchain Labs Token Bridge Contract <a href="#offchain-labs-token-bridge-contract" id="offchain-labs-token-bridge-contract"></a>

The bridge between **Arbitrum and dKargo** operates using a mechanism **similar to** [**Offchain Labs\`s**](https://www.offchainlabs.com/) **Token Bridge**.

For a deeper technical analysis, refer to Arbitrum’s documentation.

* <https://docs.arbitrum.io/build-decentralized-apps/token-bridging/overview>

#### **@dKargo/sdk tutorial** <a href="#dkargo-sdk-tutorial" id="dkargo-sdk-tutorial"></a>

By using the dKargo SDK, developers can integrate Token bridging functionality directly into their applications.

The `dkargo-tutorials` repository provides detailed instructions on how to use the dKargo SDK with `ethers.ts` and `hardhat.ts`.

* [**dkargo-tutorials**](https://github.com/dKargo/dkargo-tutorials)


# Standard Gateway

The movement of ERC-20 tokens between L2 and L3 implies that each chain must have a corresponding ERC-20 token contract.

This requires developers or project builders to deploy an additional ERC-20 token contract on L3 and register the necessary information in the GatewayRouter contract to ensure proper request routing.

However, when using the Standard Gateway, these additional steps are not required, allowing immediate use of the bridge without extra configurations.

If no paired ERC-20 contract exists on L3 at the time of the first deposit request, the contract is automatically deployed.

Transactions are routed through the Standard Gateway, and the entire process is handled automatically during the initial deposit request, ensuring a seamless and efficient onboarding experience.

{% hint style="info" %}
The dKargo Token Bridge is a dApp built using Arbitrum’s Retryable Ticket mechanism. A Retryable Ticket allows an L2 transaction to be generated and executed on L3. Through this mechanism, users can initiate L3 transactions directly from L2. The required transaction fees are paid in ERC-20 DKA on L2.
{% endhint %}

## ERC-20 Deposit

The deposit process refers to transferring ERC-20 tokens from Arbitrum Chain (L2) to dKargo Chain (L3).\
This process is executed in multiple steps through the collaboration of the Arbitrum-deployed token bridge contract and the dKargo sequencer.

### STEP 1 - Approving L2 ERC-20 Tokens & L2 ERC-20 DKA

Before transferring ERC-20 tokens from L2 to L3(dKargo), the Standard Gateway contract must be granted approval to access the user’s ERC-20 tokens on L2.

{% code overflow="wrap" %}

```tsx
const depositAmount = parseEther('1');
const res = await tokenBridge.approveToken({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
  amount:depositAmount
});

const receipt = await res.wait();      
console.log(`approve ERC20 token to L2 Standard Gateway Contract tx hash: ${receipt.transactionHash}`)

const allowance = await tokenBridge.allowanceTokenToGateway(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

{% endcode %}

Additionally, since transaction fees on L3 are paid in ERC-20 DKA (Gas Token) on L2, the Standard Gateway contract must also be separately approved to access ERC-20 DKA.

{% code overflow="wrap" %}

```tsx
const res = await tokenBridge.approveGasToken({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
});

const receipt = await res.wait();      
console.log(`approve DKA token to L2 Standard Gateway Contract tx hash: ${receipt.transactionHash}`)

const allowance = await tokenBridge.allowanceGasTokenToGateway(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

{% endcode %}

{% hint style="info" %}
&#x20;If <mark style="color:blue;">`null`</mark> is entered instead of <mark style="color:blue;">`depositAmount`</mark> as a parameter, the maximum available amount will be approved. Normally, an approval is required each time a deposit is made through the bridge. However, setting it this way allows for a one-time approval, eliminating the need for additional approvals in future transactions.
{% endhint %}

### STEP 2 - Deposit Token

Once approval is complete, the `outboundTransfer()` method of the L2 GatewayRouter contract is called to initiate the deposit.

* The L2 GatewayRouter routes the deposit request to the L2 Standard Gateway.
* The Standard Gateway transfers the ERC-20 tokens to its contract and sends the required L2 ERC-20 DKA to the bridge contract as a transaction fee.

```tsx
const res = await tokenBridge.deposit({
  amount: depositAmount,
  erc20ParentAddress: parentERC20.address,
  parentSigner,
  childProvider,
});

const receipt = await res.wait();      
console.log(`deposit erc20 token L2 tx hash: ${receipt.transactionHash}`)
```

The deposited ERC-20 tokens are then locked in the Standard Gateway contract deployed on Arbitrum, ensuring their security during the bridging process.

{% hint style="info" %}
If this is the first deposit attempt, an ERC-20 contract is automatically deployed on L3.\
The deployed ERC-20 contract is implemented based on [StandardArbERC20.sol.](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol).
{% endhint %}

### STEP 3 - Checking Deposit Status

Transferring ERC-20 tokens to the Standard Gateway does not result in an immediate deposit on L3.

The deposit status will initially be marked as "Pending", and after approximately 10 minutes, the tokens will be minted to the recipient’s account on dKargo Chain (L3).

{% code overflow="wrap" %}

```tsx
// The transaction remains in a pending state until the message is executed on the dKargo chain.
await receipt.waitForChildTransactionReceipt(childProvider);
```

{% endcode %}

The deposit status can be checked on [dScanner’s L2 ➔ L3 Transactions page](https://warehouse.dscanner.io/txs-deposits).

{% hint style="info" %}
The deposit request sent to the Standard Gateway on L2 includes a message that calls <mark style="color:blue;">`bridgeMint()`</mark> on the L3 ERC-20 contract. This message is verified by the sequencer and executed through the L3 Standard Gateway, triggering the minting of tokens, which are then deposited into the user’s account on L3.
{% endhint %}

## Withdrawal ERC-20

The withdrawal process refers to transferring ERC-20 tokens from dKargo Chain (L3) to Arbitrum Chain (L2). This process is executed in multiple steps through the collaboration of the Arbitrum-deployed token bridge contract and the dKargo sequencer.

### STEP 1 - Withdrawing **ERC-20 Tokens**

The ERC-20 withdrawal process is initiated by calling the <mark style="color:blue;">`outboundTransfer()`</mark> method of the L3 GatewayRouter.

The L3 GatewayRouter routes the withdrawal request to the L3 Standard Gateway.

The L3 Standard Gateway burns the specified amount of ERC-20 tokens on L3. Once this is completed, an equivalent amount of L2 ERC-20 tokens, previously locked in the L2 Standard Gateway, will be released to the user according to the procedure.&#x20;

```tsx
const res = await tokenBridge.withdraw({
  erc20ParentAddress: parentERC20.address,
  childSigner,
  amount,
  destinationAddress: parentSigner.address,
});

const receipt = await res.wait();
console.log(`withdraw erc20 token L3 tx hash: ${receipt.transactionHash}`)
```

{% hint style="info" %}
The withdrawal request sent to the L3 Standard Gateway includes a message that calls <mark style="color:blue;">`bridgeBurn()`</mark> on the L3 ERC-20 contract. This message burns the requested L3 tokens. Simultaneously, it grants permission to the L2 Standard Gateway to release the equivalent amount of L2 tokens that were previously locked.
{% endhint %}

### STEP 2: Checking Withdraw Status

After submitting a withdrawal request, ERC-20 tokens on L2 can only be claimed after a dispute period of approximately 6.4 days. During this period, the withdrawal status remains pending.

{% code overflow="wrap" %}

```tsx
const message = await receipt.getChildToParentMessages(parentProvider);

// The withdrawal remains in a pending state until the 6.4-day dispute period has passed.
await message[0].waitUntilReadyToExecute(childProvider);
```

{% endcode %}

The **withdrawal status** can be checked on [**dScanner’s \[L3 ➔ L2 Transactions\] page**](https://warehouse.dscanner.io/txs-exit).

### STEP 3: Claiming **ERC-20 Tokens**

Once the dispute period ends, the user becomes eligible to claim the withdrawn ERC-20 tokens from the L2 Standard Gateway. Users can finalize the withdrawal by claiming the ERC-20 tokens via the Outbox contract.

```jsx
const message = await receipt.getChildToParentMessages(parentSigner);
const res = await message[0].execute(childProvider);
const executeReceipt = await res.wait();
console.log(`claim erc20 token L2 tx hash: ${executeReceipt.transactionHash}`)
```


# Generic-custom Gateway

For token movement between Arbitrumn and dKargo, the Standard Gateway method is generally sufficient.

However, in the Standard Gateway model:

* When depositing tokens, an ERC-20 token contract on dKargo Chain is automatically deployed.
* This contract is enforced to use [StandardArbERC20.sol](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol), limiting customization.

For developers or project builders who want:

* To add custom functionalities to their ERC-20 contract
* To pair their own ERC-20 contract on dKargo Chain with a specific contract

Using the Generic-Custom Gateway provides greater flexibility in these cases.

{% hint style="info" %}
Smart contracts implemented in **Solidity** follow a naming convention where **"L1" and "L2" prefixes are specified**.

* This is because dKargo utilizes **Arbitrum’s** [token bridge contracts](https://github.com/OffchainLabs/token-bridge-contracts).
* In the **dKargo Token Bridge context**:
  * **L1 = Arbitrum (L2)**
  * **L2 = dKargo (L3)**

This distinction clarifies the **corresponding layers** when interacting with **Arbitrum’s bridge infrastructure** within dKargo’s ecosystem.
{% endhint %}

## Setting Up a Token with the Generic-Custom Gateway

To use the generic-custom gateway, the ERC-20 contract deployed on L2 must comply with the following rules:

### 1. Interface

The **ERC-20 token contract on L2** must implement the [ICustomToken](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/ICustomToken.sol) interface.&#x20;

The <mark style="color:blue;">`isArbitrumEnabled()`</mark> method is called during the token registration process. To utilize the **Generic-Custom Gateway**, this method **must return** <mark style="color:blue;">`uint8(0xb1)`</mark>.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ICustomToken} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/ICustomToken.sol";
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol";

import {L1OrbitGatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol";
import {L1OrbitCustomGateway} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol";
import {IL1GatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/IL1GatewayRouter.sol";
import { IERC20Bridge } from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/IERC20Bridge.sol";

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract L2TokenCustomGas is Ownable, ERC20, ICustomToken {
    using SafeERC20 for IERC20;

    address public gateway;
    address public router;
    bool internal shouldRegisterGateway;

    constructor(string memory name_, string memory symbol_,uint256 _initialSupply,address _gateway, address _router) ERC20(name_, symbol_) {
        gateway = _gateway;
        router = _router;
        _mint(msg.sender, _initialSupply * 10 ** decimals());
    }

    /// @dev See {ERC20-transferFrom}
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override(ICustomToken, ERC20) returns (bool) {
        return super.transferFrom(sender, recipient, amount);
    }

    /// @dev See {ERC20-balanceOf}
    function balanceOf(address account) public view override(ICustomToken, ERC20) returns (uint256) {
        return super.balanceOf(account);
    }

    /// @dev we only set shouldRegisterGateway to true when in `registerTokenOnL2`
    function isArbitrumEnabled() external view override returns (uint8) {
        require(shouldRegisterGateway, "NOT_EXPECTED_CALL");
        return uint8(0xb1);
    }

    function registerTokenOnL2(
        address l2CustomTokenAddress,
        uint256 maxSubmissionCostForCustomGateway,
        uint256 maxSubmissionCostForRouter,
        uint256 maxGasForCustomGateway,
        uint256 maxGasForRouter,
        uint256 gasPriceBid,
        uint256 valueForGateway,
        uint256 valueForRouter,
        address creditBackAddress
    ) public payable override onlyOwner {
        // we temporarily set `shouldRegisterGateway` to true for the callback in registerTokenToL2 to succeed
        bool prev = shouldRegisterGateway;
        shouldRegisterGateway = true;

        address inbox = IL1GatewayRouter(router).inbox();
        address bridge = address(IInbox(inbox).bridge());

        // transfer fees from user to here, and approve router to use it
        {
            address nativeToken = IERC20Bridge(bridge).nativeToken();

            IERC20(nativeToken).safeTransferFrom(
                msg.sender,
                address(this),
                valueForGateway + valueForRouter
            );
            IERC20(nativeToken).approve(router, valueForRouter);
            IERC20(nativeToken).approve(gateway, valueForGateway);
        }

        L1OrbitCustomGateway(gateway).registerTokenToL2(
            l2CustomTokenAddress,
            maxGasForCustomGateway,
            gasPriceBid,
            maxSubmissionCostForCustomGateway,
            creditBackAddress,
            valueForGateway
        );

        L1OrbitGatewayRouter(router).setGateway(
            gateway,
            maxGasForRouter,
            gasPriceBid,
            maxSubmissionCostForRouter,
            creditBackAddress,
            valueForRouter
        );

        // reset allowance back to 0 in case not all approved native tokens are spent
        {
            address nativeToken = IERC20Bridge(bridge).nativeToken();

            IERC20(nativeToken).approve(router, 0);
            IERC20(nativeToken).approve(gateway, 0);
        }

        shouldRegisterGateway = prev;
    }
}

```

{% endcode %}

The ERC-20 token contract on L3 must implement the [IArbToken](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) interface.

* The contract must include:
  * <mark style="color:blue;">`bridgeMint`</mark>  and <mark style="color:blue;">`bridgeBurn`</mark> functions, which can only be called by the Generic-Custom Gateway contract (onlyL2Gateway).
  * The `l1Address` variable to store the corresponding L2 ERC-20 token contract address.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

contract L3Token is ERC20, ERC20Permit, IArbToken {
    address public l2Gateway;
    address public override l1Address;

    modifier onlyL2Gateway() {
        require(msg.sender == l2Gateway, "NOT_GATEWAY");
        _;
    }

    constructor(string memory name_, string memory symbol_,address _l2Gateway, address _l1TokenAddress) ERC20(name_, symbol_) ERC20Permit(name_) {
        l2Gateway = _l2Gateway;
        l1Address = _l1TokenAddress;
    }

    /**
     * @notice should increase token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeMint(address account, uint256 amount) external override onlyL2Gateway {
        _mint(account, amount);
    }

    /**
     * @notice should decrease token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeBurn(address account, uint256 amount) external override onlyL2Gateway {
        _burn(account, amount);
    }
}

```

{% endcode %}

### 2. Approve

The next step is **registering and pairing the L2 and L3 ERC-20 tokens** through the **Generic-Custom Gateway**.

Since the two tokens are deployed on **independent chains**, the pairing process is conducted using **retryable tickets**, and the associated fees are paid in **L2 ERC-20 DKA**.

The **registration process** begins by calling a method on the **L2 ERC-20 token contract**. Before initiating this step, the **L2 ERC-20 token contract must be approved to use L2 ERC-20 DKA** for transaction fees.

{% hint style="info" %}
The dKargo Token Bridge is a dApp built using Arbitrum’s Retryable Ticket mechanism. A Retryable Ticket allows an L2 transaction to be generated and executed on L3.

This mechanism enables users to perform L3 operations directly from L2.The transaction fees for this process are paid in L2 ERC-20 DKA.
{% endhint %}

{% code overflow="wrap" %}

```tsx
const res = await customTokenBridge.approveGasTokenForCustomTokenRegistration({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
});

const receipt = await res.wait();
console.log(`approve gas token to L2 ERC20 Contract tx hash: ${receipt.transactionHash}`

const allowance = await customTokenBridge.allowanceGasTokenToParentERC20(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

{% endcode %}

### 3. Register

The **L2 and L3 ERC-20 tokens** must be registered with the **Generic-Custom Gateway** to complete the pairing process.

By referring to **\[L2 ERC-20 Example]**, it can be observed that the <mark style="color:blue;">`registerTokenOnL2`</mark> method calls both:

* <mark style="color:blue;">`customGateway.registerTokenToL2`</mark>
* <mark style="color:blue;">`customGateway.setGateway`</mark>

These methods are executed on the **L2 ERC-20 contract**.

* <mark style="color:blue;">`customGateway.registerTokenToL2`</mark>
  * Executes the **pairing process** within the **L2 Generic-Custom Gateway**.
  * Sends a **cross-chain message to dKargo Chain**, ensuring that the same pairing operation is executed on **L3 Generic-Custom Gateway**.
* <mark style="color:blue;">`customGateway.setGateway`</mark>
  * Registers **Generic-Custom Gateway** as the **designated gateway** for the token in the **L2 GatewayRouter**.
  * Sends a **cross-chain message to dKargo Chain**, ensuring that the **L3 GatewayRouter** applies the same configuration.

```tsx
const res = await customTokenBridge.registerCustomToken(
  parentERC20.address,
  childERC20.address,
  parentSigner,
  childProvider
);

const receipt = await res.wait();
console.log(`register tx hash: ${receipt.transactionHash}`
```

Since the two tokens are registered on separate chains, pairing them requires some processing time.

* The registration request status will initially be marked as "Pending".
* After approximately 10 minutes, the process is finalized, and the token pairing is completed on dKargo Chain (L3).

```tsx
const l2ToL3Msgs = await receipt.getParentToChildMessages(childProvider);

// The process waits until the message is executed on the dKargo chain.
await l2ToL3Msgs[0].waitForStatus();
await l2ToL3Msgs[1].waitForStatus();
```

The status of the registration request can be checked on [dScanner’s \[L2 ➔ L3 Transactions\] page](https://warehouse.dscanner.io/txs-deposits).

## Depositing & Withdrawing Tokens

Now that the Generic-Custom Gateway is set up, deposits and withdrawals between Arbitrum Chain and dKargo Chain can now be performed. The deposit and withdrawal process functions exactly the same as the [Standard Gateway](https://www.notion.so/160f208f1da980478de7d5372ebc5919?pvs=21) method, so refer to its process for execution.

It is important to note that when using the Standard Gateway, the first deposit automatically deploys an ERC-20 token contract on dKargo Chain, enforcing the use of [StandardArbERC20.sol](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol).

However, when using the Generic-Custom Gateway, it is mandatory to complete the registration process before making the first deposit.


# Custom Gateway

* The **Standard Gateway** enables deposits and withdrawals **without additional configurations or approvals**, offering a **simple and efficient onboarding process**.
* The **Generic-Custom Gateway** allows developers to add **custom functionalities** to an **ERC-20 contract** or pair it with a **specific ERC-20 contract on dKargo Chain**.

Both **Gateways** are **smart contracts provided by dKargo’s Token Bridge**. If a **developer or project builder** requires a **Gateway with additional features or custom functionality**, they can opt for the **Custom Gateway** instead of using a pre-deployed option.

{% hint style="warning" %}
**Do you really need a Custom Gateway?**

Before implementing and deploying a Custom Gateway, it is strongly recommended to analyze the functionalities of the Standard Gateway and Generic-Custom Gateway offered by dKargo’s Token Bridge.

To better understand Custom Gateway requirements, first, review Arbitrum’s [token bridge docs.](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-custom-gateway). This will make it significantly easier to grasp the underlying code and implementation details.
{% endhint %}

## STEP 1 - Use case

As previously explained in the Token Bridge overview, the L2 Gateway:

* Locks tokens during deposits.
* Releases locked tokens during withdrawals.

Meanwhile, the L3 Gateway:

* Mints tokens upon deposits.
* Burns tokens upon withdrawals.

If additional functionalities need to be implemented within this Gateway framework, the Custom Gateway is the appropriate choice.

Below are examples of features that can be added through a Custom Gateway:

* Whitelist verification to ensure that only authorized users can deposit or withdraw tokens.
* Fee mechanism to charge users for utilizing the bridge.
* Balance tracking to record the amount of tokens bridged in and out.

By leveraging a Custom Gateway, it is possible to implement tailored functionalities that meet specific requirements.

## STEP 2 - Setting up a Custom Gatewey

{% hint style="info" %}
This guide explains how to implement and use a Custom Gateway with an added functionality that allows an administrator (owner) to enable or disable deposits and withdrawals.
{% endhint %}

A **Gateway** must be deployed on **both L2 and L3**.

* **Deposit requests** are processed through the **L2 Custom Gateway**, so the **deposit enable/disable functionality** must be implemented in the **L2 Custom Gateway**.
* **Withdrawal requests** are processed through the **L3 Custom Gateway**, so the **withdrawal enable/disable functionality** must be implemented in the **L3 Custom Gateway**.

### L2 Custom Gateway example

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./interfaces/ICustomGateway.sol";
import "./CrosschainMessenger.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import {L1ArbitrumMessenger} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/L1ArbitrumMessenger.sol";
import {GatewayMessageHandler} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/gateway/GatewayMessageHandler.sol";
import {IERC20Inbox} from "@arbitrum/nitro-contracts/src/bridge/IERC20Inbox.sol";
import {IERC20Bridge} from "@arbitrum/nitro-contracts/src/bridge/IERC20Bridge.sol";

/**
 * @title Example implementation of a custom gateway to be deployed on L2
 * @dev Inheritance of Ownable is optional. In this case we use it to call the function setTokenBridgeInformation
 * and simplify the test
 */
contract ParentChainCustomGateway is L1ArbitrumMessenger, IL2CustomGateway, L2CrosschainMessenger, Ownable {
    using Address for address;
    using SafeERC20 for IERC20;

    // Token bridge state variables
    address public l2CustomToken;
    address public l3CustomToken;
    address public l3Gateway;
    address public router;

    // Custom functionality
    bool public allowsDeposits;

    /**
     * Contract constructor, sets the L2 router to be used in the contract's functions and calls L1CrosschainMessenger's constructor
     * @param router_ l3GatewayRouter address
     * @param inbox_ Inbox address
     */
    constructor(
        address router_,
        address inbox_
    ) L2CrosschainMessenger(inbox_) {
        router = router_;
        allowsDeposits = false;
    }

    /**
     * Sets the information needed to use the gateway. To simplify the process of testing, this function can be called once
     * by the owner of the contract to set these addresses.
     * @param l2CustomToken_ address of the custom token on L2
     * @param l3CustomToken_ address of the custom token on L3
     * @param l3Gateway_ address of the counterpart gateway (on L3)
     */
    function setTokenBridgeInformation(
        address l2CustomToken_,
        address l3CustomToken_,
        address l3Gateway_
    ) public onlyOwner {
        require(l2CustomToken == address(0), "Token bridge information already set");
        l2CustomToken = l2CustomToken_;
        l3CustomToken = l3CustomToken_;
        l3Gateway = l3Gateway_;

        // Allows deposits after the information has been set
        allowsDeposits = true;
    }

    /// @dev See {ICustomGateway-outboundTransfer}
    function outboundTransfer(
        address l2Token,
        address to,
        uint256 amount,
        uint256 maxGas,
        uint256 gasPriceBid,
        bytes calldata data
    ) public payable override returns (bytes memory) {
        return outboundTransferCustomRefund(l2Token, to, to, amount, maxGas, gasPriceBid, data);
    }

    function outboundTransferCustomRefund(
        address _l2Token,
        address _refundTo,
        address _to,
        uint256 _amount,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        bytes calldata _data
    ) public payable virtual override returns (bytes memory res) {
        // Only execute if deposits are allowed
        require(allowsDeposits == true, "Deposits are currently disabled");

        // Only allow calls from the router
        require(msg.sender == router, "Call not received from router");

        // Only allow the custom token to be bridged through this gateway
        require(_l2Token == l2CustomToken, "Token is not allowed through this gateway");

        // This function is set as public and virtual so that subclasses can override
        // it and add custom validation for callers (ie only whitelisted users)
        address _from;
        uint256 seqNum;
        bytes memory extraData;
        {
            // unpack user encoded data
            uint256 maxSubmissionCost;
            uint256 tokenTotalFeeAmount;
            if (isRouter(msg.sender)) {
                // router encoded
                (_from, extraData) = GatewayMessageHandler.parseFromRouterToGateway(_data);
            } else {
                _from = msg.sender;
                extraData = _data;
            }
            (maxSubmissionCost, extraData, tokenTotalFeeAmount) = _parseUserEncodedData(extraData);

            // the inboundEscrowAndCall functionality has been disabled, so no data is allowed
            require(extraData.length == 0, "EXTRA_DATA_DISABLED");

            require(_l2Token.isContract(), "L1_NOT_CONTRACT");
            address l2Token = calculateL2TokenAddress(_l2Token);
            require(l2Token != address(0), "NO_L2_TOKEN_SET");

            _amount = outboundEscrowTransfer(_l2Token, _from, _amount);

            // we override the res field to save on the stack
            res = getOutboundCalldata(_l2Token, _from, _to, _amount, extraData);

            seqNum = _initiateDeposit(
                _refundTo,
                _from,
                _amount,
                _maxGas,
                _gasPriceBid,
                maxSubmissionCost,
                tokenTotalFeeAmount,
                res
            );
        }
        emit DepositInitiated(_l2Token, _from, _to, seqNum, _amount);
        return abi.encode(seqNum);
    }

    /// @dev See {ICustomGateway-finalizeInboundTransfer}
    function finalizeInboundTransfer(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes calldata data
    ) public payable override onlyCounterpartGateway(l3Gateway) {
        // Only allow the custom token to be bridged through this gateway
        require(l2Token == l2CustomToken, "Token is not allowed through this gateway");

        // Decoding exitNum
        (uint256 exitNum, ) = abi.decode(data, (uint256, bytes));

        // Releasing the tokens in the gateway
        IERC20(l2Token).transfer(to, amount);

        emit WithdrawalFinalized(l2Token, from, to, exitNum, amount);
    }

    /// @dev See {ICustomGateway-getOutboundCalldata}
    function getOutboundCalldata(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) public pure override returns (bytes memory outboundCalldata) {
        bytes memory emptyBytes = "";

        outboundCalldata = abi.encodeWithSelector(
            ICustomGateway.finalizeInboundTransfer.selector,
            l2Token,
            from,
            to,
            amount,
            abi.encode(emptyBytes, data)
        );

        return outboundCalldata;
    }

    /// @dev See {ICustomGateway-calculateL2TokenAddress}
    function calculateL2TokenAddress(address l2Token) public view override returns (address) {
        if (l2Token == l2CustomToken) {
            return l3CustomToken;
        }

        return address(0);
    }

    /// @dev See {ICustomGateway-counterpartGateway}
    function counterpartGateway() public view override returns (address) {
        return l3Gateway;
    }

    function _parseUserEncodedData(bytes memory data)
        internal
        pure
        returns (
            uint256 maxSubmissionCost,
            bytes memory callHookData,
            uint256 tokenTotalFeeAmount
        )
    {
        (maxSubmissionCost, callHookData, tokenTotalFeeAmount) = abi.decode(
            data,
            (uint256, bytes, uint256)
        );
    }

    function outboundEscrowTransfer(
        address _l2Token,
        address _from,
        uint256 _amount
    ) internal virtual returns (uint256 amountReceived) {
        // this method is virtual since different subclasses can handle escrow differently
        // user funds are escrowed on the gateway using this function
        uint256 prevBalance = IERC20(_l2Token).balanceOf(address(this));
        IERC20(_l2Token).safeTransferFrom(_from, address(this), _amount);
        uint256 postBalance = IERC20(_l2Token).balanceOf(address(this));
        return postBalance - prevBalance;
    }
    function _initiateDeposit(
        address _refundTo,
        address _from,
        uint256, // _amount, this info is already contained in _data
        uint256 _maxGas,
        uint256 _gasPriceBid,
        uint256 _maxSubmissionCost,
        uint256 tokenTotalFeeAmount,
        bytes memory _data
    ) internal returns (uint256) {
        return
        sendTxToL2CustomRefund(
                address(inbox),
                counterpartGateway(),
                _refundTo,
                _from,
                tokenTotalFeeAmount,
                0,
                L2GasParams({
                    _maxSubmissionCost: _maxSubmissionCost,
                    _maxGas: _maxGas,
                    _gasPriceBid: _gasPriceBid
                }),
                _data
            );
    }

    function _createRetryable(
        address _inbox,
        address _to,
        address _refundTo,
        address _user,
        uint256 _totalFeeAmount,
        uint256 _l2CallValue,
        uint256 _maxSubmissionCost,
        uint256 _maxGas,
        uint256 _gasPriceBid,
        bytes memory _data
    ) internal override returns (uint256) {
        {
            // Transfer native token amount needed to pay for retryable fees to the inbox.
            // Fee tokens will be transferred from user who initiated the action - that's `_user` account in
            // case call was routed by router, or msg.sender in case gateway's entrypoint was called directly.
            address nativeFeeToken = IERC20Bridge(address(getBridge(_inbox))).nativeToken();
            uint256 inboxNativeTokenBalance = IERC20(nativeFeeToken).balanceOf(_inbox);
            if (inboxNativeTokenBalance < _totalFeeAmount) {
                address transferFrom = isRouter(msg.sender) ? _user : msg.sender;
                IERC20(nativeFeeToken).safeTransferFrom(
                    transferFrom,
                    _inbox,
                    _totalFeeAmount - inboxNativeTokenBalance
                );
            }
        }

        return
            IERC20Inbox(_inbox).createRetryableTicket(
                _to,
                _l2CallValue,
                _maxSubmissionCost,
                _refundTo,
                _user,
                _maxGas,
                _gasPriceBid,
                _totalFeeAmount,
                _data
            );
    }

    function isRouter(address _target) internal view returns (bool isTargetRouter) {
        return _target == router;
    }

    // --------------------
    // Custom methods
    // --------------------
    /**
     * Disables the ability to deposit funds
     */
    function disableDeposits() external onlyOwner {
        allowsDeposits = false;
    }

    /**
     * Enables the ability to deposit funds
     */
    function enableDeposits() external onlyOwner {
        require(l2CustomToken != address(0), "Token bridge information has not been set yet");
        allowsDeposits = true;
    }
}
```

{% endcode %}

### L3 Custom Gateway example

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./interfaces/ICustomGateway.sol";
import "./CrosschainMessenger.sol";
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title Example implementation of a custom gateway to be deployed on L3
 * @dev Inheritance of Ownable is optional. In this case we use it to call the function setTokenBridgeInformation
 * and simplify the test
 */
contract ChildChainCustomGateway is IL3CustomGateway, L3CrosschainMessenger, Ownable {
    // Exit number (used for tradeable exits)
    uint256 public exitNum;

    // Token bridge state variables
    address public l2CustomToken;
    address public l3CustomToken;
    address public l2Gateway;
    address public router;

    // Custom functionality
    bool public allowsWithdrawals;

    /**
     * Contract constructor, sets the L3 router to be used in the contract's functions
     * @param router_ L3GatewayRouter address
     */
    constructor(address router_) {
        router = router_;
        allowsWithdrawals = false;
    }

    /**
     * Sets the information needed to use the gateway. To simplify the process of testing, this function can be called once
     * by the owner of the contract to set these addresses.
     * @param l2CustomToken_ address of the custom token on L2
     * @param l3CustomToken_ address of the custom token on L3
     * @param l2Gateway_ address of the counterpart gateway (on L2)
     */
    function setTokenBridgeInformation(
        address l2CustomToken_,
        address l3CustomToken_,
        address l2Gateway_
    ) public onlyOwner {
        require(l2CustomToken == address(0), "Token bridge information already set");
        l2CustomToken = l2CustomToken_;
        l3CustomToken = l3CustomToken_;
        l2Gateway = l2Gateway_;

        // Allows withdrawals after the information has been set
        allowsWithdrawals = true;
    }

    /// @dev See {ICustomGateway-outboundTransfer}
    function outboundTransfer(
        address l2Token,
        address to,
        uint256 amount,
        bytes calldata data
    ) public payable returns (bytes memory) {
        return outboundTransfer(l2Token, to, amount, 0, 0, data);
    }

    /// @dev See {ICustomGateway-outboundTransfer}
    function outboundTransfer(
        address l2Token,
        address to,
        uint256 amount,
        uint256, /* _maxGas */
        uint256, /* _gasPriceBid */
        bytes calldata data
    ) public payable override returns (bytes memory res) {
        // Only execute if deposits are allowed
        require(allowsWithdrawals == true, "Withdrawals are currently disabled");

        // The function is marked as payable to conform to the inheritance setup
        // This particular code path shouldn't have a msg.value > 0
        require(msg.value == 0, "NO_VALUE");
        
        // Only allow the custom token to be bridged through this gateway
        require(l2Token == l2CustomToken, "Token is not allowed through this gateway");

        (address from, bytes memory extraData) = _parseOutboundData(data);

        // The inboundEscrowAndCall functionality has been disabled, so no data is allowed
        require(extraData.length == 0, "EXTRA_DATA_DISABLED");

        // Burns L2 tokens in order to release escrowed L1 tokens
        IArbToken(l3CustomToken).bridgeBurn(from, amount);

        // Current exit number for this operation
        uint256 currExitNum = exitNum++;

        // We override the res field to save on the stack
        res = getOutboundCalldata(l2Token, from, to, amount, extraData);

        // Trigger the crosschain message
        uint256 id = _sendTxToL2(
            from,
            l2Gateway,
            res
        );

        emit WithdrawalInitiated(l2Token, from, to, id, currExitNum, amount);
        return abi.encode(id);
    }

    /// @dev See {ICustomGateway-finalizeInboundTransfer}
    function finalizeInboundTransfer(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes calldata data
    ) public payable override onlyCounterpartGateway(l2Gateway) {
        // Only allow the custom token to be bridged through this gateway
        require(l2Token == l2CustomToken, "Token is not allowed through this gateway");

        // Abi decode may revert, but the encoding is done by L2 gateway, so we trust it
        (, bytes memory callHookData) = abi.decode(data, (bytes, bytes));
        if (callHookData.length != 0) {
            // callHookData should always be 0 since inboundEscrowAndCall is disabled
            callHookData = bytes("");
        }

        // Mints L3 tokens
        IArbToken(l3CustomToken).bridgeMint(to, amount);

        emit DepositFinalized(l2Token, from, to, amount);
    }

    /// @dev See {ICustomGateway-getOutboundCalldata}
    function getOutboundCalldata(
        address l2Token,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) public view override returns (bytes memory outboundCalldata) {
        outboundCalldata = abi.encodeWithSelector(
            ICustomGateway.finalizeInboundTransfer.selector,
            l2Token,
            from,
            to,
            amount,
            abi.encode(exitNum, data)
        );

        return outboundCalldata;
    }

    /// @dev See {ICustomGateway-calculateL2TokenAddress}
    function calculateL2TokenAddress(address l2Token) public view override returns (address) {
        if (l2Token == l2CustomToken) {
            return l3CustomToken;
        }
        
        return address(0);
    }

    /// @dev See {ICustomGateway-counterpartGateway}
    function counterpartGateway() public view override returns (address) {
        return l2Gateway;
    }

    /**
     * Parse data received in outboundTransfer
     * @param data encoded data received
     * @return from account that initiated the deposit,
     *         extraData decoded data
     */
    function _parseOutboundData(bytes memory data)
    internal
    view
    returns (
        address from,
        bytes memory extraData
    )
    {
        if (msg.sender == router) {
            // Router encoded
            (from, extraData) = abi.decode(data, (address, bytes));
        } else {
            from = msg.sender;
            extraData = data;
        }
    }

    // --------------------
    // Custom methods
    // --------------------
    /**
     * Disables the ability to deposit funds
     */
    function disableWithdrawals() external onlyOwner {
        allowsWithdrawals = false;
    }

    /**
     * Enables the ability to deposit funds
     */
    function enableWithdrawals() external onlyOwner {
        require(l2CustomToken != address(0), "Token bridge information has not been set yet");
        allowsWithdrawals = true;
    }
}
```

{% endcode %}

Additionally, the interface and methods compatible with the Token Bridge can be referenced in the provided code below.

### CrosschainMessenger.sol

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
import "@arbitrum/nitro-contracts/src/libraries/AddressAliasHelper.sol";
import {IERC20Inbox} from "@arbitrum/nitro-contracts/src/bridge/IERC20Inbox.sol";
import {IBridge} from "@arbitrum/nitro-contracts/src/bridge/IBridge.sol";

/**
 * @title Interface needed to call function `l2ToL1Sender` of the Outbox
 */
interface IOutbox {
    function l2ToL1Sender() external view returns (address);
}

/**
 * @title Minimum expected implementation of a crosschain messenger contract to be deployed on L1
 */
abstract contract L2CrosschainMessenger {
    IERC20Inbox public immutable inbox;

    constructor(address inbox_) {
        inbox = IERC20Inbox(inbox_);
    }

    modifier onlyCounterpartGateway(address l3Counterpart) {
        // A message coming from the counterpart gateway was executed by the bridge
        IBridge bridge = inbox.bridge();
        require(msg.sender == address(bridge), "NOT_FROM_BRIDGE");

        // And the outbox reports that the L2 address of the sender is the counterpart gateway
        address l2ToL1Sender = IOutbox(bridge.activeOutbox()).l2ToL1Sender();
        require(l2ToL1Sender == l3Counterpart, "ONLY_COUNTERPART_GATEWAY");

        _;
    }
}

/**
 * @title Minimum expected implementation of a crosschain messenger contract to be deployed on L3
 */
abstract contract L3CrosschainMessenger {
    address internal constant ARB_SYS_ADDRESS = address(100);

    /**
     * Emitted when calling sendTxToL1
     * @param from account that submits the L3-to-L2 message
     * @param to account recipient of the L3-to-L2 message
     * @param id id for the L3-to-L2 message
     * @param data data of the L3-to-L2 message
     */
    event TxToL1(
        address indexed from,
        address indexed to,
        uint256 indexed id,
        bytes data
    );

    modifier onlyCounterpartGateway(address l2Counterpart) {
        require(
            msg.sender == AddressAliasHelper.applyL1ToL2Alias(l2Counterpart),
            "ONLY_COUNTERPART_GATEWAY"
        );

        _;
    }

    /**
     * Creates an L3-to-L2 message to send over to L2 through ArbSys
     * @param from account that is sending funds from L3
     * @param to account to be credited with the tokens in the destination layer
     * @param data encoded data for the L3-to-L2 message
     * @return id id for the L3-to-L2 message
     */
    function _sendTxToL2(
        address from,
        address to,
        bytes memory data
    ) internal returns (uint256) {
        uint256 id = ArbSys(ARB_SYS_ADDRESS).sendTxToL1(to, data);

        emit TxToL1(from, to, id, data);
        return id;
    }
}
```

{% endcode %}

### ICustomGateway.sol

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/**
 * @title Minimum expected interface for a custom gateway
 */
interface ICustomGateway {
    function outboundTransfer(
        address l1Token,
        address to,
        uint256 amount,
        uint256 maxGas,
        uint256 gasPriceBid,
        bytes calldata data
    ) external payable returns (bytes memory);

    function finalizeInboundTransfer(
        address l1Token,
        address from,
        address to,
        uint256 amount,
        bytes calldata data
    ) external payable;

    function getOutboundCalldata(
        address l1Token,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) external view returns (bytes memory);

    function calculateL2TokenAddress(address l1Token) external view returns (address);

    function counterpartGateway() external view returns (address);
}

/**
 * @title Minimum expected interface for a custom gateway to be deployed on L1
 */
interface IL2CustomGateway is ICustomGateway {
    event DepositInitiated(
        address l1Token,
        address indexed from,
        address indexed to,
        uint256 indexed sequenceNumber,
        uint256 amount
    );

    event WithdrawalFinalized(
        address l1Token,
        address indexed from,
        address indexed to,
        uint256 indexed exitNum,
        uint256 amount
    );

    function outboundTransferCustomRefund(
        address l1Token,
        address refundTo,
        address to,
        uint256 amount,
        uint256 maxGas,
        uint256 gasPriceBid,
        bytes calldata data
    ) external payable returns (bytes memory);
}

/**
 * @title Minimum expected interface for a custom gateway to be deployed on L2
 */
interface IL3CustomGateway is ICustomGateway {
    event WithdrawalInitiated(
        address l1Token,
        address indexed from,
        address indexed to,
        uint256 indexed l2ToL1Id,
        uint256 exitNum,
        uint256 amount
    );

    event DepositFinalized(
        address indexed l1Token,
        address indexed from,
        address indexed to,
        uint256 amount
    );
}
```

{% endcode %}

## STEP 3 - Setting up the token

The ERC-20 token contract required for the Custom Gateway is very similar to the one used in the [Generic-Custom Gateway](/docs2-eng/erc-20-bridging/generic-custom-gateway). However, the token registration process within the Gateway is omitted. This is because users must manually deploy the Custom Gateway and register the ERC-20 token themselves.

### L2 ERC20 Token example

In the provided example code, the <mark style="color:blue;">`registerTokenOnL2()`</mark> method does not include a call to <mark style="color:blue;">`L1OrbitCustomGateway(gateway).registerTokenToL2()`</mark> . Instead only <mark style="color:blue;">`L1OrbitGatewayRouter(router).setGateway()`</mark> is called.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ICustomToken} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/ICustomToken.sol";
import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol";

import {L1OrbitGatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol";
import {L1OrbitCustomGateway} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol";
import {IL1GatewayRouter} from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/ethereum/gateway/IL1GatewayRouter.sol";
import { IERC20Bridge } from "@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/IERC20Bridge.sol";

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract L2TokenCustomGasToken is Ownable, ERC20, ICustomToken {
    using SafeERC20 for IERC20;

    address public gateway;
    address public router;
    bool internal shouldRegisterGateway;

    constructor(string memory name_, string memory symbol_,uint256 _initialSupply,address _gateway, address _router) ERC20(name_, symbol_) {
        gateway = _gateway;
        router = _router;
        _mint(msg.sender, _initialSupply * 10 ** decimals());
    }

    function mint() external {
        _mint(msg.sender, 50000000);
    }

    /// @dev See {ERC20-transferFrom}
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override(ICustomToken, ERC20) returns (bool) {
        return super.transferFrom(sender, recipient, amount);
    }

    /// @dev See {ERC20-balanceOf}
    function balanceOf(address account) public view override(ICustomToken, ERC20) returns (uint256) {
        return super.balanceOf(account);
    }

    /// @dev we only set shouldRegisterGateway to true when in `registerTokenOnL2`
    function isArbitrumEnabled() external view override returns (uint8) {
        require(shouldRegisterGateway, "NOT_EXPECTED_CALL");
        return uint8(0xb1);
    }

    /**
     * @dev See {ICustomToken-registerTokenOnL2}
     * In this case, we don't need to call IL1CustomGateway.registerTokenToL2, because our
     * custom gateway works for a single token it already knows.
     */
    function registerTokenOnL2(
        address, /* l2CustomTokenAddress */
        uint256, /* maxSubmissionCostForCustomGateway */
        uint256 maxSubmissionCostForRouter,
        uint256, /*  maxGasForCustomGateway */
        uint256 maxGasForRouter,
        uint256 gasPriceBid,
        uint256, /* valueForGateway */
        uint256 valueForRouter,
        address creditBackAddress
    ) public override payable onlyOwner {
        // we temporarily set `shouldRegisterGateway` to true for the callback in registerTokenToL2 to succeed
        bool prev = shouldRegisterGateway;
        shouldRegisterGateway = true;

        address inbox = IL1GatewayRouter(router).inbox();
        address bridge = address(IInbox(inbox).bridge());

        // transfer fees from user to here, and approve router to use it
        {
            address nativeToken = IERC20Bridge(bridge).nativeToken();

            IERC20(nativeToken).safeTransferFrom(
                msg.sender,
                address(this),
                valueForRouter
            );
            IERC20(nativeToken).approve(router, valueForRouter);
        }

        L1OrbitGatewayRouter(router).setGateway(
            gateway,
            maxGasForRouter,
            gasPriceBid,
            maxSubmissionCostForRouter,
            creditBackAddress,
            valueForRouter
        );

        shouldRegisterGateway = prev;
    }
}

```

{% endcode %}

The ERC-20 token deployed on L3 is implemented exactly the same as in the Generic-Custom Gateway.

### L3 ERC-20 Token example

The key point is that the <mark style="color:blue;">`bridgeMint()`</mark> and <mark style="color:blue;">`bridgeBurn()`</mark> methods must be implemented to allow the Gateway to mint or burn L3 ERC-20 tokens during deposits and withdrawals.

{% code overflow="wrap" %}

```solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@arbitrum/token-bridge-contracts/contracts/tokenbridge/arbitrum/IArbToken.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract L3Token is ERC20, IArbToken {
    address public l3Gateway;
    address public override l1Address; /** override by arbitrum */

    modifier onlyL3Gateway() {
        require(msg.sender == l3Gateway, "NOT_GATEWAY");
        _;
    }

    constructor(string memory name_, string memory symbol_,address _l3Gateway, address _l2TokenAddress) ERC20(name_, symbol_) {
        l3Gateway = _l3Gateway;
        l1Address = _l2TokenAddress;
    }

    /**
     * @notice should increase token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeMint(address account, uint256 amount) external override onlyL3Gateway {
        _mint(account, amount);
    }

    /**
     * @notice should decrease token supply by amount, and should only be callable by the L2Gateway.
     */
    function bridgeBurn(address account, uint256 amount) external override onlyL3Gateway {
        _burn(account, amount);
    }
}

```

{% endcode %}

## STEP 4 - Register the Token and Custom Gateway

Once the Custom Gateway and ERC-20 token contracts have been deployed on L2 and L3 (a total of four contracts), the next step is to register them with the Gateway and GatewayRouter.

### Storing Contract Addresses

In the custom gateway, use the <mark style="color:blue;">`setTokenBridgeInformation()`</mark> method to store the addresses of the paired gateways and the ERC-20 toekn contract addresses.

```tsx
let res = await parentCustomGateway.setTokenBridgeInformation(
  parentERC20.address,
  childERC20.address,
  childCustomGateway.address
);
let receipt = await res.wait();
console.log(`setTokenBridgeInformation L2 tx hash: ${receipt.transactionHash}`

res = await childCustomGateway.setTokenBridgeInformation(
  parentERC20.address,
  childERC20.address,
  parentCustomGateway.address
);
receipt = await res.wait();
console.log(`setTokenBridgeInformation L3 tx hash: ${receipt.transactionHash}`
```

### Pairing Configuration

To enable the Custom Gateway, it must be registered and paired with the GatewayRouter on each chain.

* Since the GatewayRouter is deployed on both L2 and L3, the pairing process is conducted using [retryable ticket](https://docs.arbitrum.io/build-decentralized-apps/cross-chain-messaging).
* During this process, L2 ERC-20 DKA is required to pay transaction fees.
* The registration process starts by calling a method on the L2 ERC-20 token contract.

Before starting the pairing process, ensure that the L2 ERC-20 token contract is approved to use L2 ERC-20 DKA for transaction fees.

{% hint style="info" %}
The dKargo Token Bridge is a dApp built using Arbitrum’s Retryable Ticket mechanism.&#x20;

* A Retryable Ticket allows an L2 transaction to be created and executed on L3.
* This mechanism enables L3 operations to be initiated directly from L2.
* The transaction fees for this process are paid in L2 ERC-20 DKA.
  {% endhint %}

```tsx
const res = await customTokenBridge.approveGasTokenForCustomTokenRegistration({
  erc20ParentAddress: parentERC20.address,
  parentSigner,
});

const receipt = await res.wait();
console.log(`approve gas token to L2 ERC20 Contract tx hash: ${receipt.transactionHash}`

const allowance = await customTokenBridge.allowanceGasTokenToParentERC20(
  parentERC20.address,
  parentSigner.address,
  parentProvider
);
console.log(`allowance amount: ${allowance}`
```

### Gateway Registration

The <mark style="color:blue;">`registerTokenOnL2`</mark> method in the L2 ERC-20 contract is used to call <mark style="color:blue;">`router.setGateway`</mark>, registering the L2 Custom Gateway as the designated Gateway for the L2 ERC-20 token within the L2 GatewayRouter.

This process also sends a message to dKargo Chain, where the same operation is executed to register the L3 Custom Gateway in the L3 GatewayRouter.

```tsx
const res = await customTokenBridge.registerCustomToken(
  parentERC20.address,
  childERC20.address,
  parentSigner,
  childProvider
);

const receipt = await res.wait();
console.log(`register tx hash: ${receipt.transactionHash}`
```

Since the two Custom Gateways are registered on separate chains, it takes some time for the pairing process to be fully completed.

* The registration request status will initially be marked as "Pending".
* After approximately 10 minutes, the request is finalized, and the pairing is completed on dKargo Chain (L3).

{% code overflow="wrap" %}

```tsx
const l2ToL3Msgs = await receipt.getParentToChildMessages(childProvider);

// The message remains in a waiting state until it is executed on the dKargo chain.
await l2ToL3Msgs[0].waitForStatus();
```

{% endcode %}

The status of the registration request can be checked on [dScanner’s \[L2 ➔ L3 Transactions\] (link) page](https://warehouse.dscanner.io/txs-deposits).

## Deposit & Withdraw Token

With the Custom Gateway now set up, deposits and withdrawals between Arbitrum Chain and dKargo Chain can be performed.

From this point forward, the deposit and withdrawal processes function identically to those of the Standard Gateway. Refer to the [Standard Gateway](/docs2-eng/erc-20-bridging/standard-gateway)’s deposit and withdrawal process for execution details.


# DKA Bridging

<figure><img src="/files/ORU5NfYr9hX8KnyjZhkR" alt=""><figcaption></figcaption></figure>

DKA is the native token of dKargo Chain, used to pay transaction fees.

To enable transactions on dKargo Chain (Layer 3, L3), ERC-20 DKA tokens held on Arbitrum Chain (Layer 2, L2) must first be converted into L3 DKA native tokens.

This conversion process is facilitated by the DKA Bridge.

The following guide explains how to use the DKA Bridge using functions provided by [@dkargo/sdk.](https://github.com/dKargo/dkargo-sdk)

## Reference

### **Arbitrum Native Bridge**

The bridge between **Arbitrum and dKargo** operates using a mechanism **similar to Arbitrum's ETH Bridge**.

For a deeper technical analysis, refer to Arbitrum’s documentation.

* [**ETH bridgeing**](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/token-bridge-ether)

### **@dKargo/sdk tutorial**

By using the dKargo SDK, developers can integrate DKA bridging functionality directly into their applications.

The `dkargo-tutorials` repository provides detailed instructions on how to use the dKargo SDK with <mark style="color:blue;">`ethers.ts`</mark> and <mark style="color:blue;">`hardhat.ts`</mark>.

* [**dkargo-tutorials**](https://github.com/dKargo/dkargo-tutorials)


# DKA Deposit

The **d**eposit process refers to transferring ERC-20 DKA from Arbitrum Chain (L2) to dKargo Chain (L3).

This process is executed in multiple steps through the collaboration of the [bridge contract](https://sepolia.arbiscan.io/address/0xC7e932238A2d9fccFa33FF5e8Deed966F0460Ea7) deployed on Arbitrum and the dKargo sequencer.

## STEP 1 - Approve L2 ERC-20 DKA

Before transferring ERC-20 DKA from L2 to L3, the Inbox Contract must be granted approval to access the user's DKA tokens.

```jsx
const depositAmount = parseEther('1');
const res = await dkaBridge.approveGasToken({
  parentSigner,
  amount:depositAmount
});
const receipt = await res.wait();
console.log(`approve DKA token to Inbox Contract tx hash: ${receipt.transactionHash}`)

const allowance = await dkaBridge.allowanceGasTokenToInbox(
  parentSigner.address,
  parentProvider
);

console.log(`allowance amount: ${allowance}`
```

{% hint style="info" %}
If <mark style="color:blue;">`null`</mark>is entered instead of <mark style="color:blue;">`depositAmount`</mark>, the maximum available amount will be approved.

Normally, an approval is required each time a deposit is made through the bridge. However, by setting it this way, a one-time approval is sufficient, eliminating the need for additional approvals in future transactions.
{% endhint %}

## STEP 2 - Depositing DKA

Once approval is complete, the <mark style="color:blue;">`depositEth()`</mark> method of the Inbox Contract is called to initiate the deposit.

The Inbox Contract transfers the L2 DKA to the bridge contract..

```jsx
const res = await dkaBridge.deposit({
  parentSigner,
  amount: depositAmount,
});
const receipt = await res.wait();
console.log(`deposit DKA L2 tx hash: ${receipt.transactionHash}`)
```

The deposited DKA is then locked in the bridge contract deployed on Arbitrum.

## STEP 3 - Checking Deposit Status

Transferring **L2 DKA** to the bridge contract does not result in an **immediate deposit** on **L3**.

The **deposit status** will initially be marked as **"Pending"**. After approximately **10 minutes**, the **DKA balance** will be **finalized and transferred** to the user's account on **dKargo Chain (L3)**.

```jsx
// Wait until the message is executed on the dKargo chain.
await receipt.waitForChildTransactionReceipt(childProvider);
```


# DKA Withdraw

The withdrawal refers to transferring DKA from dKargo Chain (L3) to Arbitrum Chain (L2).

This process is executed in multiple steps through the collaboration of the [bridge contract](https://sepolia.arbiscan.io/address/0xC7e932238A2d9fccFa33FF5e8Deed966F0460Ea7) deployed on Arbitrum and the dKargo sequencer.

## STEP 1 - Withdrawing DKA

The DKA withdrawal process is executed using the <mark style="color:blue;">`withdrawEth()`</mark> method of the ArbSys precompile contract.

The DKA withdrawn from dKargo Chain (L3) is burned, and the corresponding L2 DKA in the bridge contract is processed through the next steps before being sent to the user.

```jsx
const res = await dkaBridge.withdraw({
  childSigner,
  amount,
  destinationAddress: parentSigner.address,
  from: childSigner.address,
});

const receipt = await res.wait();
console.log(`withdraw DKA L3 tx hash: ${receipt.transactionHash}`)
```

## STEP 2: Checking Withdrawal Status

After submitting a withdrawal request, the DKA on L2 can only be claimed after a dispute period of approximately 1 hour. During this period, the withdrawal status remains pending.

```jsx
const timeToWaitMs = 1000 * 60
const message = await receipt.getChildToParentMessages(parentProvider);

// Waiting until the 6.4-day dispute period passes.
await message[0].waitUntilReadyToExecute(childProvider, timeToWaitMs);
```

The **withdrawal status** can be checked on [dScanner's L3 ➔ L2 Transactions page.](https://warehouse.dscanner.io/txs-exit).

## STEP 3: Claiming DKA

Once the dispute period ends, the user becomes eligible to claim the withdrawn DKA from the bridge contract.

The user can then finalize the withdrawal by claiming the requested DKA through the Outbox contract.

```jsx
const message = await receipt.getChildToParentMessages(parentSigner);
const res = await message[0].execute(childProvider);
const executeReceipt = await res.wait();
console.log(`claim DKA L2 tx hash: ${executeReceipt.transactionHash}`)
```

*Last updated on **May 6, 2026***


# Faucet for Testnet Tokens

<figure><img src="/files/66IkhQ82HStJ9nAgozEu" alt=""><figcaption></figcaption></figure>

A Faucet is an application that distributes testnet tokens, named after a water faucet that provides a controlled flow of resources.

The dKargo Faucet supplies testnet tokens for testing smart contracts on dKargo's Warehouse testnet.

Before deploying a dApp on the mainnet, developers can use these testnet tokens to test contract deployment, transaction execution, and other blockchain interactions.

Testnet tokens have no real-world value and cannot be used on the mainnet.

* [Go to dKargo Faucet](https://dkargo.io/developers/faucet)


# Claiming Testnet Tokens

To receive **testnet tokens from the Faucet**, a **valid address** compatible with **dKargo’s Warehouse testnet** is required.\
Addresses used in **EVM-compatible chains** can be used in the same way.

* Each address can claim 1 **$DKA (Arbitrum Sepolia) once every 24 hours**.

Visit the [Faucet ](https://dkargo.io/developers/faucet)Page and request $DKA.

An existing address can be used, or a new one can be generated separately.

Wallet creation is not supported on this page.

<figure><img src="/files/H60VTGbao2Tky5EifRHT" alt="" width="563"><figcaption></figcaption></figure>

If an invalid address is entered, the following error message will appear.

Check whether the address is typed correctly and compatible with the Warehouse testnet.

<figure><img src="/files/sIhGAz7p5ceQZccho3NO" alt="" width="563"><figcaption></figcaption></figure>

Once the claim is successful, a transaction marked "Success" will be displayed.

This indicates that $DKA has been successfully sent to the address.

The claim history stores up to five transactions, with the oldest entry being removed as new claims are made.

<figure><img src="/files/j5FnnQbc3IGcN0Wsr7e8" alt="" width="563"><figcaption></figcaption></figure>

If there is a network issue or communication failure, the request may fail, resulting in:

* A "Fail" transaction status.
* No transaction being generated.

If this occurs, try requesting $DKA again.

<figure><img src="/files/DQOKG4JRJIilYxcKMiWJ" alt="" width="563"><figcaption></figcaption></figure>

<figure><img src="/files/FZtb2bxwoLd0LNwgE9XN" alt="" width="563"><figcaption></figcaption></figure>

$DKA can be checked by [adding the dKargo test network](/docs2-eng/wallet-setup/adding-the-dkargo-network) to [dScanner](https://warehouse.dscanner.io/) or a personal wallet.


# Download the Latest Chain Snapshot

When operating a Full Node, the initial startup process involves synchronizing block information from dKargo Chain.

The more recorded block data there is, the longer the synchronization process will take.

To optimize this, **dKargo provides a snapshot of the latest stored state** at a specific point in time.

The latest snapshot can be downloaded from the following address:

## Mainnet Full Node

* S3 Snapshot Link
  * <https://dkargo-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz>

## Warehouse Full Node

* S3 Snapshot Link
  * <https://warehouse-snapshot.s3.ap-northeast-2.amazonaws.com/prune/latest/pruned.tar.gz>


# Contract Address List

<figure><img src="/files/wjub3PLp8a1yGD5kur5K" alt=""><figcaption></figcaption></figure>

This section provides information on Arbitrum L2 contracts and dKargo L3 contracts used for operations.

## 1. Mainnet

### 1.1 Chain Info

<table><thead><tr><th width="284.66015625">Name</th><th width="460.390625">Address</th></tr></thead><tbody><tr><td>ChainId</td><td>61022894</td></tr><tr><td>ERC20 DKA (L1)</td><td>0x5dc60C4D5e75D22588FA17fFEB90A63E535efCE0</td></tr><tr><td>Arbitrum ERC20 DKA (L2)</td><td>0x1E2C41d3fF045488D0921591e6B5532583e54F1C</td></tr></tbody></table>

​

### 1.2 Core Contract

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>rollup</td><td>0x11e3D0e9604a0AD8a8B32068B95e83d7C63b3af7</td></tr><tr><td>inbox</td><td>0xB17a5495FA25FBcA887083b7048Bc60A796A201B</td></tr><tr><td>outbox</td><td>0x947fe294C167A6e9b7bc5c328AeF0aCe9ac83584</td></tr><tr><td>adminProxy</td><td>0x2582E771BC50f6273b17349780d3f8eB98A2b143</td></tr><tr><td>sequencerInbox</td><td>0x48781bAec9B5f9eBCf6fd96134f24231c6987Aa0</td></tr><tr><td>bridge</td><td>0x42C4b496edA79215872De91f71D77F434098e162</td></tr><tr><td>utils</td><td>0x7C100c97a54e2D309a194752Df2f66922A802be3</td></tr><tr><td>validatorWalletCreator</td><td>0x69A6dE0B9BeC9edb33805167327a6ABEf0C69Fd2</td></tr></tbody></table>

​

### 1.3 L2 Contract

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0x03141af6d5ca65E8D3Ee5CbC17f9ff26D81D47E3</td></tr><tr><td>multicall</td><td>0x90B02D9F861017844F30dFbdF725b6aa84E63822</td></tr><tr><td>proxyAdmin</td><td>0x2582E771BC50f6273b17349780d3f8eB98A2b143</td></tr><tr><td>router</td><td>0xcF6298ca74B278e5CB02B75f100D766BDfAC11A2</td></tr><tr><td>standardGateway</td><td>0x306485BFA7c6c0b533A9Bd3C3B363bE848c7A289</td></tr><tr><td>weth</td><td>0x0000000000000000000000000000000000000000</td></tr></tbody></table>

### 1.4 L3 Contract

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0xd8a9B86bBcd534C69CE1DcdeC7B463BfE0378526</td></tr><tr><td>multicall</td><td>0xE5183093695E95368902DD7c018F4ae910d79AAD</td></tr><tr><td>proxyAdmin</td><td>0x5CF9C1795bA901aC40aBF6Ca1BfBD40a0E7E6c5A</td></tr><tr><td>router</td><td>0x8e7f3c9a7743a673C2395eD58bcb5b57168291e4</td></tr><tr><td>standardGateway</td><td>0xE400CAeDEf6F46E6C9aF10324F2C308363A1e246</td></tr><tr><td>weth</td><td>0x0000000000000000000000000000000000000000</td></tr></tbody></table>

## 2. Warehouse (Testnet)

### 2.1 Chain Info

<table><thead><tr><th width="284.66015625">Name</th><th width="460.390625">Address</th></tr></thead><tbody><tr><td>parentChainId</td><td>61022448</td></tr><tr><td>Sepolia ERC20 DKA (L1)</td><td>0xB55F1261a635919fEEba99e4142608D589d72842</td></tr><tr><td>Arbitrum Sepolia ERC20 DKA (L2)</td><td>0x6d9c6F7445C4D3a8fa3fC269f020e12eBF3ACD66</td></tr></tbody></table>

### 2.2 Core Contract

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>rollup</td><td>0xe66eBA4f90911fd14B2183b1EF0e9636Bbda1e0e</td></tr><tr><td>inbox</td><td>0xaecdEd066B62712Ad6Ce7F65E00571F8991b8642</td></tr><tr><td>outbox</td><td>0x149607bCbcCDBB39f76e19943a6d150E2a2f66a2</td></tr><tr><td>adminProxy</td><td>0x66adb680E5a4f1a83796044490342D9832b43228</td></tr><tr><td>sequencerInbox</td><td>0xCEcDd6E3ff446BB9D0bef548B1F481bc09202885</td></tr><tr><td>bridge</td><td>0xC7e932238A2d9fccFa33FF5e8Deed966F0460Ea7</td></tr><tr><td>utils</td><td>0x7C100c97a54e2D309a194752Df2f66922A802be3</td></tr><tr><td>validatorWalletCreator</td><td>0xFAd2C6Cb969Ab7B18d78BD63e512b650bb70B570</td></tr></tbody></table>

### 2.3 L2 Contract

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0x203F2BA85b3ea74d68642D89143174D3dFbEf971</td></tr><tr><td>multicall</td><td>0xce1CAd780c529e66e3aa6D952a1ED9A6447791c1</td></tr><tr><td>proxyAdmin</td><td>0x66adb680E5a4f1a83796044490342D9832b43228</td></tr><tr><td>router</td><td>0x97EA4122cf29525bD0cF7B49700478fE4A914249</td></tr><tr><td>standardGateway</td><td>0x95F29bAE1Fb7e9FB5F85C22666550c541F426dA7</td></tr><tr><td>weth</td><td>0x980B62Da83eFf3D4576C647993b0c1D7faf17c73</td></tr></tbody></table>

### 2.4 L3 Contract

<table><thead><tr><th width="225">Name</th><th>Address</th></tr></thead><tbody><tr><td>customGateway</td><td>0x57afe9E80bb837D91C7E871bF68e68142A4Ea0d2</td></tr><tr><td>multicall</td><td>0xb5138E24231Bd50367Ef37a476630c42550e9E37</td></tr><tr><td>proxyAdmin</td><td>0xF78D7032E6e24B54159Ea3365e82B0abe399567d</td></tr><tr><td>router</td><td>0xaaa492673Ea2E1A881Edd34446de425ffe6B23BF</td></tr><tr><td>standardGateway</td><td>0xD9B2b6A1D61140A24b6E9a1dA9B836A8B73e4cE8</td></tr><tr><td>weth</td><td>0x0000000000000000000000000000000000000000</td></tr></tbody></table>


# Audit reports

## Our Approach to Security

At dKargo, we define security not as a one-time task but as a core ongoing process integrated into every aspect of our mainnet operations. We uphold the integrity of our network based on the following principles:

* Proactive Threat Mitigation: We perform rigorous threat modeling starting from the design phase to identify and address vulnerabilities before they arise.
* Continuous Third-Party Verification: Beyond internal checks, we conduct regular audits with world-class security firms to ensure technical and operational transparency.
* Real-time Monitoring & Evolution: Based on audit findings, we continuously enhance our security infrastructure—including decentralized governance and data availability—to adapt to evolving threats.

The following list comprises official security audit reports conducted by dKargo. New reports will be added and updated regularly as we continue to expand and fortify our ecosystem.

## Security Audit Reports

<table><thead><tr><th width="100.37890625">Auditor</th><th width="116.5">Audit date</th><th width="406.48046875">Audited code</th><th width="125.5234375">View report</th></tr></thead><tbody><tr><td>Theori</td><td>01/19/2026</td><td>Nitro Contract v3.1.0 &#x26; StandardArbERC20 DKA Contract</td><td><a href="https://github.com/dKargo/nitro-contracts/blob/f6430dfaa4d647f61857ef4c441abbce2ec53dfb/audit/%5BTheori%5D%20dKargo%20L3%20Deployment_Operation%20Security%20Assessment%20Final%20Report.pdf">view</a></td></tr></tbody></table>


# Bug Bounty Program

<figure><img src="/files/tcOkr2leUH13J8C7EnJo" alt=""><figcaption></figcaption></figure>

## Bug Bounty Program Overview

The dKargo Bug Bounty Program is designed to enhance the security and stability of both our Testnet and Mainnet. This document outlines the program's purpose, evaluation criteria, reward structure, and submission procedures.

## Severity Levels and Rewards

{% hint style="info" %}
Bug severity is classified into four levels based on its potential impact on the mainnet.
{% endhint %}

| Severity | Description and Examples                                                                                                                                                                                                                          | Rewards |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Critical | <ul><li>Vulnerabilities that can disable core system functions or be exploited</li><li>Severe security threats such as network downtime, asset theft, or significant data tampering</li></ul>                                                     | $2,000  |
| High     | <ul><li>Vulnerabilities that can impact service functionality</li><li>Issues like data manipulation, API security flaws, or potential exploitation of the Faucet system</li><li>Bugs that can directly affect service operations</li></ul>        | $700    |
| Medium   | <ul><li>Vulnerabilities that cause specific functions to behave unexpectedly or negatively impact user experience</li><li>Examples include node execution errors, technical inaccuracies in documentation, or deposit/withdrawal issues</li></ul> | $200    |
| Low      | <ul><li>Issues that do not affect system security but involve minor bugs such as UI/UX issues, typos, visual errors, or process flow defects</li><li>Suggestions for overall quality improvement of the service</li></ul>                         | $25     |

## Eligible Bug Criteria

{% hint style="success" %}
A bug report will be considered eligible if it meets the following conditions.
{% endhint %}

* The issue must be exploitable by a real user or attacker in the normal operating environment and default settings of the L3 mainnet.
* The bug must be reproducible and objectively verifiable, posing a threat to the system’s security or reliability to be deemed eligible.
* Logical flaws that can cause system malfunctions, if they can be clearly exploited (e.g., akin to a DDoS attack), may be considered eligible.
* Whether the issue occurs on a single node, client, or the entire network, its impact must be clear and accompanied by a realistic threat scenario in an operational environment.
* Technical flaws or configuration errors present in the latest release or documentation will be evaluated for eligibility based on their impact.

## Ineligible Bug Criteria

{% hint style="danger" %}
The following items are excluded from the Bug Bounty Program.
{% endhint %}

* Attacks requiring physical access or those that exhaust system resources through excessive traffic (e.g., DDoS attacks) fall outside the scope of typical software vulnerabilities and are excluded from the Bug Bounty Program.
* Reports lacking sufficient details—such as step-by-step instructions, reproducible examples, or proof of concept—will be excluded.
* Issues that only occur in outdated or unsupported browsers, vulnerabilities already publicly known, or problems already identified internally by the team will not qualify for rewards if reported as duplicates.
* Vulnerabilities requiring excessive user intervention (e.g., bugs that need complex manipulation to trigger), simple security configuration suggestions, best practice recommendations, or theoretical reports without proof of concept are considered ineligible.
* Incidents are not included in bug reports. An incident refers to a situation where the entire system or a major function temporarily stops working, affecting multiple users simultaneously. Such issues may arise from external factors beyond the control of developers or security experts (e.g., server downtime or network issues).

## How to Submit a Bug Bounty Report

1. While there is no specific template, your submission must include the following details

| Category                  | Content                                                                                                                                         |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Bug Title                 | A concise title describing the bug.                                                                                                             |
| Bug Description           | <ul><li>A detailed explanation of the bug, including what the issue is and its potential impact.</li><li>Be as specific as possible.</li></ul>  |
| Step-by-Step Reproduction | <ul><li>A detailed explanation of the bug, including what the issue is and its potential impact. </li><li>Be as specific as possible.</li></ul> |
| Impact                    | The impact of the bug on the system.                                                                                                            |
| **Severity**              | The severity level based on the defined severity categories.                                                                                    |
| Supporting Materials      | Evidence to substantiate the bug (e.g., screenshots, videos).                                                                                   |

2. Please compile the above information into a document and send it to <hello@dkargo.io>.


