클레이스왑 → 클레임스왑 순서의 아비트라지를 수행하는 함수(swapKlaySwapToClaimSwap) 와 클레임스왑 → 클레이스왑 순서의 아비트라지를 수행하는 함수(swapClaimSwapToKlaySwap) 가 존재합니다. 각각 상황에 맞추어 호출하면 (B), (C) 컨트랙트를 호출하는 플래시론 동작을 수행합니다.
위 코드는 ArbitragerClaimToKlaySwap 컨트랙트의 일부분입니다. 플래시론을 통해 대출이 실행되었을때 수행할 코드를 작성하기위해 IFlashLoanReceiver 를 상속 받습니다. ArbitragerClaimToKlaySwap 는 클레임스왑 → 클레이스왑의 아비트라지를 수행하는 컨트랙트입니다. 따라서 function executeOperation(...)에서 실질적인 아비트라지를 진행합니다. 빌린 자산인 asset[0] 가 tokenA, 파라미터로 넘어온 데이터인 params가 tokenB가 되게됩니다. 함수의 동작은 다음과같은 순서로 진행됩니다.
클레임 스왑의 getAmountsOut 과 클레이스왑의 estimate를 이용하여 거래를했을때 수익이 나는지 확인합니다.(require(estimateOut2 > amounts[0]);)
_swapClaimSwap 를 통해 클레임스왑에서 A→B 토큰으로 교환을 하고
_swapKlaySwap 를 통해 클레이스왑에서 B→A 토큰으로 교환을 합니다.
마지막으로 클레이뱅크에 빌린 자산을 돌려주기위해 approve 를 확인해주는 checkApprove 함수를 실행합니다.
이로써 A→B→A 토큰 교환을 마치고 대출금액 및 플래시론 수수료를 상환한 뒤 남은 차액을 얻게됩니다.
swapKlaySwapToClaimSwap 를 사용하는 ArbitragerKlaySwapToClaim 컨트랙트역시 로직은 동일하며 순서만 다릅니다.
매 블록마다 기회를 포착하기위해 소켓을 이용하며, 각 컨트랙트들을 ABI와 주소를 이용해 초기화해줍니다.
constkeyring=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를 이용하여 키링을 설정해줍니다.
부분에서 newBlockHeaders 를 subscribe하여 매 블록마다 이벤트를 받을 수 있도록 구성합니다. 이 subscribtion은 블록이 생성될때마다 async (error, result) => {...} 함수 콜백을 실행시켜줍니다.
따라서 콜백함수 내부에서 매 블록마다 클레이스왑 → 클레임스왑 에서 기회가 있는지, 클레임스왑 → 클레이스왑 에서 기회가 있는지 체크하게 됩니다.
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 =newBigNumber(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 함수를 통해 플래시론 및 아비트라지를 수행하게 됩니다.
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 =newBigNumber(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를 이용하는 것 외에 이전 설명과 동일합니다.
마치며
아비트라지 기회는 위에서 언급한 클레이스왑↔클레임스왑 사이에만 존재하는것은 아닙니다. 또한 KUSDT↔KETH 페어 사이에서만 발생하는 것 역시 아닙니다. 다양한 Dex(클레이스왑, 클레임스왑, 팔라, UFO, …) 과 다양한 페어(KUSDT↔KETH, KLAY↔KUSDT, CLA↔KUSDT, …)를 이용하여 여러 기회를 포착할 수 있습니다.
따라서 봇 코드를 고도화 다양한 경로를 감시 및 아비트라지 할 수 있으며, 더욱 고도화 한다면 고정된 시도금액 ( 예시에서 10,000 KUSDT ) 이 아닌 더 큰 금액 또는 작은 금액으로 가변적으로 조절할 수 있습니다.
플래시론은 이러한 아비트라지 기회를 포착하고 실행할때, 무담보 대출을 실행하여 자본이 없거나 부족하더라도 아비트라지를 안정적으로 성공하도록 도와줍니다.
위 예시 코드에서는, Arbitrager contract에 쌓인 토큰을 꺼내는 함수 및 보안을 위한 사항들이 고려되어있지 않으므로 사용시에는 적절한 수정을 통해 사용하는 것을 권장합니다.