# 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.
