# 플래시론을 활용한 아비트라지(2)

## Overview <a href="#id-1be4" id="id-1be4"></a>

다음은 실제코드와 함께 플래시론을 이용하여 아비트라지를 하는 예제를 설명합니다.

본 예제에서는 클레이스왑과 클레임스왑에 존재하는 KUSDT↔KETH 페어간 아비트라지 기회를 포착하고 실행하는 방법 및 코드를 설명합니다.

아비트라지를 하기 위해서는,

1. 플래시론을 활용하여 스왑하는 컨트랙트
2. 기회를 포착하여 트랜잭션을 전송하는 봇

두가지가 필요합니다.

컨트랙트는 세개의 컨트랙트로 구성되어있으며 각각,

* 기회를 포착한 봇이 트랜잭션을 날려 시작점이 되는 컨트랙트 (A)
* 플래시론을 이용해 클레이스왑 → 클레임스왑 순서의 차액거래를 하는 컨트랙트 (B)
* 플래시론을 이용해 클레임스왑 → 클레이스왑 순서의 차액거래를 하는 컨트랙트 (C)

로 구성됩니다. 아래의 설명에서는 각 컨트랙트의 주소를 (A),(B),(C)로 표기하여 설명합니다.

## Contract <a href="#id-8bfe" id="id-8bfe"></a>

Arbitrager Contract(A)는 아비트라지 기회를 포착후 Tx를 받는 허브역할을 하게됩니다.

```solidity
contract Arbitrager {
    
    ILendingPool LENDING_POOL;
    IKlaySwapProtocol KLAYSWAP;
    IClaimSwapRouter CLAIMSWAP;
    ...
    function swapKlaySwapToClaimSwap(address tokenA, address tokenB, uint256 amount) public {
    ...
    }
    function swapClaimSwapToKlaySwap(address tokenA, address tokenB, uint256 amount) public {
    ...
    }
    ...   
}
```

클레이스왑 → 클레임스왑 순서의 아비트라지를 수행하는 함수(`swapKlaySwapToClaimSwap`) 와 클레임스왑 → 클레이스왑 순서의 아비트라지를 수행하는 함수(`swapClaimSwapToKlaySwap`) 가 존재합니다. 각각 상황에 맞추어 호출하면 (B), (C) 컨트랙트를 호출하는 플래시론 동작을 수행합니다.

```solidity
function swapClaimSwapToKlaySwap(address tokenA, address tokenB, uint256 amount) public {
    address[] memory assets = new address[](1);
    assets[0] = tokenA;
    uint256[] memory amounts = new uint256[](1);
    amounts[0] = amount;
    uint256[] memory modes = new uint256[](1);
    modes[0] = 0;
    bytes memory params = addressToBytes(tokenB);
    LENDING_POOL.flashLoan(
        ArbitragerClaimToKlaySwap,
        assets,
        amounts,
        modes,
        address(this),
        params,
        0
    );
}
```

두가지의 함수중 `swapClaimSwapToKlaySwap` 를 기준으로 설명합니다.

`swapClaimSwapToKlaySwap` 는

1. KUSDT → KETH ( 클레임 스왑)
2. KETH → KUSDT ( 클레이 스왑)

과정의 아비트라지를 수행해주는 함수입니다. 따라서 플래시론을 통해 빌릴 토큰 파라미터인 `assets` 에 tokenA 주소를, `amounts`에 파라미터로 받은 amount를 넣고 플래시론을 실행해줍니다.

플래시론 인터페이스 및 파라미터 에 대한 설명은 [여기](https://docs.klaybank.org/protocol/functions/flashloan#flashloan) 를 참조해주세요.

`LENDING_POOL.flashLoan(...)` 을 통해 플래시론을 실행해주고, 플래시론을 통해 로직을 수행할 컨트랙트인

`ArbitragerClaimToKlaySwap` 에 (C) 주소를 입력합니다.

```solidity
contract ArbitragerClaimToKlaySwap is IFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool){
        uint estimateOut;
        uint estimateOut2;
        address[] memory path = new address[](2);
        // tokenA
        path[0] = assets[0];
        // tokenB
        path[1] = bytesToAddress(params);
        estimateOut = CLAIMSWAP.getAmountsOut(amounts[0], path)[1];
        IKlaySwapExchange klaySwapPool = IKlaySwapExchange(KLAYSWAP.tokenToPool(path[0], path[1]));
        estimateOut2 = klaySwapPool.estimatePos(path[1], estimateOut);
        require(estimateOut2 > amounts[0]);
        _swapClaimSwap(path[0], path[1], amounts[0]);
        _swapKlaySwap(path[1], path[0], estimateOut);
        for (uint i = 0; i < assets.length; i++) {
            checkApprove(assets[i], address(LENDING_POOL));
        }
        IKIP7(assets[i]).transfer(msg.sender,estimateOut2-amounts[0]-premiums[0]);
        return true;
    }
    
    function _swapKlaySwap(address tokenA, address tokenB, uint256 amount) private {
        checkApprove(tokenA, address(KLAYSWAP));
        KLAYSWAP.exchangeKctPos(tokenA, amount, tokenB, 10, new address[](0));
    }
    
    function _swapClaimSwap(address tokenA, address tokenB, uint256 amount) private {
        checkApprove(tokenA, address(CLAIMSWAP));
        address[] memory path = new address[](2);
        path[0] = tokenA;
        path[1] = tokenB;
        CLAIMSWAP.swapExactTokensForTokens(amount, 10, path, address(this), 4230424911);
    }
    
    function checkApprove(address token, address spender) public {
        if (token == address(0x0000000000000000000000000000000000000000)) {
            return;
        }
        if (!approvedTokens[spender][token]) {
            IKIP7(token).approve(spender, uint(2 ** 256 - 1));
            approvedTokens[spender][token] = true;
        }
    }
    ...
}
```

위 코드는 `ArbitragerClaimToKlaySwap` 컨트랙트의 일부분입니다. 플래시론을 통해 대출이 실행되었을때 수행할 코드를 작성하기위해 `IFlashLoanReceiver` 를 상속 받습니다. `ArbitragerClaimToKlaySwap` 는 클레임스왑 → 클레이스왑의 아비트라지를 수행하는 컨트랙트입니다. 따라서 `function executeOperation(...)`에서 실질적인 아비트라지를 진행합니다. 빌린 자산인 `asset[0]` 가 tokenA, 파라미터로 넘어온 데이터인 params가 tokenB가 되게됩니다. 함수의 동작은 다음과같은 순서로 진행됩니다.

1. 클레임 스왑의 getAmountsOut 과 클레이스왑의 estimate를 이용하여 거래를했을때 수익이 나는지 확인합니다.(`require(estimateOut2 > amounts[0]);`)
2. `_swapClaimSwap` 를 통해 클레임스왑에서 A→B 토큰으로 교환을 하고
3. `_swapKlaySwap` 를 통해 클레이스왑에서 B→A 토큰으로 교환을 합니다.
4. 마지막으로 클레이뱅크에 빌린 자산을 돌려주기위해 approve 를 확인해주는 `checkApprove` 함수를 실행합니다.

이로써 A→B→A 토큰 교환을 마치고 대출금액 및 플래시론 수수료를 상환한 뒤 남은 차액을 얻게됩니다.

`swapKlaySwapToClaimSwap` 를 사용하는 `ArbitragerKlaySwapToClaim` 컨트랙트역시 로직은 동일하며 순서만 다릅니다.

## Arbitrage Bot <a href="#b6f3" id="b6f3"></a>

Arbitrage Bot의 전체 소스코드는 [http://github.com/klaybank/arbitrager ](https://github.com/klaybank/arbitrager)에 업로드 되어있습니다.

```javascript
const Caver = require('caver-js')
const BigNumber = require("bignumber.js");
const IKIP7ABI = require('./build/contracts/IKIP7.json');
const IKlayswapProtocolABI = require('./build/contracts/IKlaySwapProtocol.json');
const IKlayswapExchangeABI = require('./build/contracts/IKlaySwapExchange.json');
const IClaimSwapRouterABI = require('./build/contracts/IClaimSwapRouter.json');
const ArbitragerABI = require('./build/contracts/Arbitrager.json')
```

먼저 봇 실행에 필요한 기본적인 Caver, BigNumber 및 컨트랙트의 ABI들을 추가해줍니다.

```javascript
const KETHAddress = "0x34d21b1e550d73cee41151c77f3c73359527a396"
const KUSDTAddress = "0xcee8faf64bb97a73bb51e115aa89c17ffa8dd167"
const KlayswapFactoryAddress = "0xc6a2ad8cc6e4a7e08fc37cc5954be07d499e7654"
const ClaimSwapRouterAddress = "0xEf71750C100f7918d6Ded239Ff1CF09E81dEA92D"
const Klayswap_KUSDT_KETH_ExchangeAddress = "0x029e2a1b2bb91b66bd25027e1c211e5628dbcb93"
const ArbitragerAddress = "Deployed Contract Address"
const tryKUSDTAmount = new BigNumber("10000" + "000000") // 10000 KUSDT
```

위에는 각 컨트랙트들의 주소를 나타내며, `ArbitragerAddress`의 경우 직접 배포한 컨트랙트의 주소가 위치하게 됩니다.

`tryKUSDTAmount` 는 한번 아비트라지를 시도할때 사용할 금액을 나타내며, 이 예시에서는 10,000 KUSDT를 기준으로 설명합니다.

```javascript
const caver = new Caver(new Caver.providers.WebsocketProvider("wss://public-node-api.klaytnapi.com/v1/cypress/ws"));
const KUSDTContract = new caver.klay.Contract(IKIP7ABI, KUSDTAddress)
const KETHContract = new caver.klay.Contract(IKIP7ABI, KETHAddress)
const klayswapFactoryContract = new caver.klay.Contract(IKlayswapProtocolABI, KlayswapFactoryAddress)
const klayswap_USDT_ETH_ExchangeContract = new caver.klay.Contract(IKlayswapExchangeABI, Klayswap_KUSDT_KETH_ExchangeAddress)
const claimSwapRouterContract = new caver.klay.Contract(IClaimSwapRouterABI, ClaimSwapRouterAddress)
const arbitragerContract = new caver.klay.Contract(ArbitragerABI, ArbitragerAddress)
```

매 블록마다 기회를 포착하기위해 소켓을 이용하며, 각 컨트랙트들을 ABI와 주소를 이용해 초기화해줍니다.

```javascript
const keyring = caver.wallet.keyring.createFromPrivateKey('Input your private key here')
caver.wallet.add(keyring)
caver.klay.accounts.wallet.add(
    caver.klay.accounts.createWithAccountKey(keyring.address, keyring.key.privateKey))
```

또한 아비트라지 트랜잭션을 보낼 EOA의 private key를 이용하여 키링을 설정해줍니다.

```javascript
const subscription = caver.rpc.klay.subscribe(
    'newBlockHeaders',
    async (error, result) => {
        if (error) {
            console.error(error);
            return;
        }
        let estimateUsdtToETHKlayswap = await klayswap_USDT_ETH_ExchangeContract.methods.estimatePos(KUSDTAddress, tryKUSDTAmount).call();
        let estimateETHToUsdtClaimSwap = (await claimSwapRouterContract.methods.getAmountsOut(estimateUsdtToETHKlayswap, [KETHAddress, KUSDTAddress]).call())[1]
        let KlayswapToClaimRes = new BigNumber(estimateETHToUsdtClaimSwap)
        console.log(`diff1 : ${KlayswapToClaimRes.toString()}`)
        if (KlayswapToClaimRes.isGreaterThan(tryKUSDTAmount)) {
            let txRes = await arbitragerContract.methods.swapKlaySwapToClaimSwap(KUSDTAddress, KETHAddress, tryKUSDTAmount).send(
                {
                    from: keyring.address,
                    gas: 5000000
                }
            )
            console.log(`try arbitrage klayswap -> claimSwap tx : ` + txRes.hash)
        }
        let estimateUsdtToETHClaimSwap = (await claimSwapRouterContract.methods.getAmountsOut(tryKUSDTAmount, [KUSDTAddress, KETHAddress]).call())[1];
        let estimateETHToUsdtKlayswap = await klayswap_USDT_ETH_ExchangeContract.methods.estimatePos(KETHAddress, estimateUsdtToETHClaimSwap).call()
        let ClaimSwapToKlayswapRes = new BigNumber(estimateETHToUsdtKlayswap)
        console.log(`diff2 : ${ClaimSwapToKlayswapRes.toString()}`)
        if (ClaimSwapToKlayswapRes.isGreaterThan(tryKUSDTAmount)) {
            let txRes = await arbitragerContract.methods.swapClaimSwapToKlaySwap(KUSDTAddress, KETHAddress, tryKUSDTAmount).send(
                {
                    from: keyring.address,
                    gas: 5000000
                }
            )
            console.log(`try arbitrage claimSwap -> klayswap tx : ` + txRes.hash)
        }
})
    .on('connected', console.log)
    .on('error', console.error);
```

블록마다 아비트라지기회를 포착하고 실행하는 코드부분입니다.

```javascript
const subscription = caver.rpc.klay.subscribe(
    'newBlockHeaders',
    async (error, result) => {
        if (error) {
            console.error(error);
            return;
        }
    ...
    ...
```

부분에서 `newBlockHeaders` 를 subscribe하여 매 블록마다 이벤트를 받을 수 있도록 구성합니다. 이 subscribtion은 블록이 생성될때마다 `async (error, result) => {...}` 함수 콜백을 실행시켜줍니다.

따라서 콜백함수 내부에서 매 블록마다 클레이스왑 → 클레임스왑 에서 기회가 있는지, 클레임스왑 → 클레이스왑 에서 기회가 있는지 체크하게 됩니다.

```javascript
let estimateUsdtToETHKlayswap = await klayswap_USDT_ETH_ExchangeContract.methods.estimatePos(KUSDTAddress, tryKUSDTAmount).call();
let estimateETHToUsdtClaimSwap = (await claimSwapRouterContract.methods.getAmountsOut(estimateUsdtToETHKlayswap, [KETHAddress, KUSDTAddress]).call())[1]
let KlayswapToClaimRes = new BigNumber(estimateETHToUsdtClaimSwap)
console.log(`diff1 : ${KlayswapToClaimRes.toString()}`)
if (KlayswapToClaimRes.isGreaterThan(tryKUSDTAmount)) {
    let txRes = await arbitragerContract.methods.swapKlaySwapToClaimSwap(KUSDTAddress, KETHAddress, tryKUSDTAmount).send(
        {
            from: keyring.address,
            gas: 5000000
        }
    )
    console.log(`try arbitrage klayswap -> claimSwap tx : ` + txRes.hash)
}
```

콜백함수 내 위 코드는 클레이스왑 → 클레임스왑 에서의 KUSDT→KETH→KUSDT 경로의 아비트라지 기회가 있는지 포착합니다.

클레이 스왑에서 10,000 KUSDT를 KETH로 바꾼 예측값(`estimateUsdtToETHKlayswap`)를 구합니다. 이 예측값을 다시 이용하여 클레임 스왑에서 KETH를 KUSDT로 바꾼 예측값(`estimateETHToUsdtClaimSwap`) 를 구합니다.

이때 `estimateETHToUsdtClaimSwap` 의 값이 `tryKUSDTAmount` 보다 크다면 아비트라지 기회가 생긴 것 이므로, Contract 섹션에서 설명했던 arbitragerContract의 `swapKlaySwapToClaimSwap` 함수를 통해 플래시론 및 아비트라지를 수행하게 됩니다.

```javascript
let estimateUsdtToETHClaimSwap = (await claimSwapRouterContract.methods.getAmountsOut(tryKUSDTAmount, [KUSDTAddress, KETHAddress]).call())[1];
let estimateETHToUsdtKlayswap = await klayswap_USDT_ETH_ExchangeContract.methods.estimatePos(KETHAddress, estimateUsdtToETHClaimSwap).call()
let ClaimSwapToKlayswapRes = new BigNumber(estimateETHToUsdtKlayswap)
console.log(`diff2 : ${ClaimSwapToKlayswapRes.toString()}`)
if (ClaimSwapToKlayswapRes.isGreaterThan(tryKUSDTAmount)) {
    let txRes = await arbitragerContract.methods.swapClaimSwapToKlaySwap(KUSDTAddress, KETHAddress, tryKUSDTAmount).send(
        {
            from: keyring.address,
            gas: 5000000
        }
    )
console.log(`try arbitrage claimSwap -> klayswap tx : ` + txRes.hash)
}
```

클레임스왑 → 클레이스왑 의 경우 역시 마찬가지로 클레임 스왑에서 10,000 KUSDT를 KETH로 바꾼 예측값(`estimateUsdtToETHClaimSwap`) 를 구합니다. 이 예측값을 다시 이용하여 클레이 스왑에서 KETH를 KUSDT로 바꾼 예측값(`estimateETHToUsdtKlayswap`) 를 구합니다. 그 후 과정은 arbitragerContract의 `swapClaimSwapToKlaySwap`를 이용하는 것 외에 이전 설명과 동일합니다.

## 마치며 <a href="#id-83f0" id="id-83f0"></a>

아비트라지 기회는 위에서 언급한 클레이스왑↔클레임스왑 사이에만 존재하는것은 아닙니다. 또한 KUSDT↔KETH 페어 사이에서만 발생하는 것 역시 아닙니다. 다양한 Dex(클레이스왑, 클레임스왑, 팔라, UFO, …) 과 다양한 페어(KUSDT↔KETH, KLAY↔KUSDT, CLA↔KUSDT, …)를 이용하여 여러 기회를 포착할 수 있습니다.

따라서 봇 코드를 고도화 다양한 경로를 감시 및 아비트라지 할 수 있으며, 더욱 고도화 한다면 고정된 시도금액 ( 예시에서 10,000 KUSDT ) 이 아닌 더 큰 금액 또는 작은 금액으로 가변적으로 조절할 수 있습니다.

플래시론은 이러한 아비트라지 기회를 포착하고 실행할때, 무담보 대출을 실행하여 자본이 없거나 부족하더라도 아비트라지를 안정적으로 성공하도록 도와줍니다.

위 예시 코드에서는, Arbitrager contract에 쌓인 토큰을 꺼내는 함수 및 보안을 위한 사항들이 고려되어있지 않으므로 사용시에는 적절한 수정을 통해 사용하는 것을 권장합니다.


---

# Agent Instructions: Querying This Documentation

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

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

```
GET https://docs.klaybank.org/protocol/functions/flashloan/2.md?ask=<question>
```

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

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