Technology, Tutorials, for Developer, Tool support

심리스한 토큰 스왑 (1): 스마트 컨트랙트에 KLAYswap V2 통합하기

개요

탈중앙화 애플리케이션에 KLAYswap을 프로그래밍 방식으로 통합하는 것은 사용자가 디앱을 떠나지 않고도 토큰을 원활하게 사고 팔 수 있도록 하는 등, 다양한 이유로 유용할 수 있습니다. 또한, 애플리케이션이 ERC20 및 KIP7 토큰을 중심으로 서비스를 제공하는 경우에도 이러한 통합이 유용할 수 있습니다. 예컨대, 디앱을 통해 프로그래밍 방식으로 sKAI를 구매하면 사용자가 사이트에서 해당 sKAI를 빌려줄 수 있습니다.

이 가이드에서는 프로그래밍 방식으로 KLAYswap을 자체 스마트 컨트랙트에 통합하고, Klaytn Cypress(메인넷)에서 KLAY에서 sKAI로 토큰 스왑을 실행합니다. 본 튜토리얼은 2부 시리즈로, 다음 2부에서는 thirdweb과 Next.js를 사용하여 컨트랙트와 상호작용하는 최소한의 프론트엔드를 구축하는 방법을 배워보겠습니다. 

전제 조건

  • 스마트 컨트랙트에 대한 기본적인 이해
  • 스마트 컨트랙트 개발 환경에 대한 숙련도 (이 튜토리얼에서는 thirdweb 사용)

KLAYswap이란 무엇인가요?

클레이스왑은 클레이튼 네트워크를 위한 자동화된 마켓 메이커 (automated market maker,AMM) 프로토콜입니다. 간단히 말해, 기존의 오더북 구조가 아닌 유동성 공급자가 온체인에 생성한 유동성 풀을 활용하여 토큰 간 즉각적인 스왑을 지원하는 탈중앙화 거래소(decentralized exchange, DEX)입니다.

온체인 스왑 서비스로서 KLAY 또는 KCT-type 암호화폐를 보유한 사람이라면 누구나 다음 중 한 가지 방법으로 주요 참여자가 될 수 있습니다:

  • 유동성 공급자: KLAY-KCT 쌍을 KLAYswap의 Pool 메뉴에서 생성한 토큰 컨트랙트와 매칭하여 유동성을 제공할 수 있습니다. 일반적으로 유동성 공급자(Liquidity Provider, LP) 토큰을 공급 증명서로 받고, 해당 풀에서 수수료의 보상 몫을 받게 됩니다.
  • 트레이더: 프로토콜에 나열된 토큰은 해당 풀이 존재하는 한 KLAY 또는 KCT로 거래할 수 있습니다.

KLAYswap 디앱 외에도 온체인 통합을 개발하는 데 필요한 스마트 컨트랙트도 있는 경우 제공했습니다. 이 튜토리얼에서는 KLAYswap V2 버전을 다룹니다. (최신 버전은 V3

KLAYswap V2 통합하기 

이 섹션에서는 솔리디티를 사용하여 스마트 컨트랙트에 KLAYswap을 프로그래밍 방식으로 통합하는 방법을 배우게 됩니다. 이 통합이 끝나면 사용자는 KLAY에서 sKAI로 또는 그 반대로 토큰을 스왑할 수 있습니다.

*Note: KLAYswap Universal Router의 테스트넷 주소는 테스트 목적으로 제공되지 않았기 때문에 클레이튼 메인넷(Cypress)에서 시도할 예정입니다. 

이제, 스왑 통합에 필요한 구성 요소와 스왑 방법을 살펴보며 스왑 통합에 대해 살펴보겠습니다.

KLAYswap UniversalRouter

KLAYswap 라우터는 다양한 자산을 안전하게 스왑할 수 있는 다양한 방법을 제공합니다. 이 라우터는 V2 및 V3 풀에서 거래를 최적으로 집계하여 사용자에게 매우 유연하고 개인화된 트랜잭션에 대한 액세스를 제공합니다. 스왑 방법을 성공적으로 호출하려면 먼저 트레이더/사용자가 라우터를 통해 스왑하고자 하는 토큰 수만큼의 토큰을 승인해야 한다는 점에 유의하시기 바랍니다. 

토큰 스왑 방법

Swapping tokens on KLAYswap consists of using one of several swap methods in the Router smart contract. The methods used most often are swapExactETHForTokens, swapExactTokensForTokens, and swapExactTokensForETH. Each of these methods has different use cases.

A. swapExactETHForTokens: 이 함수는 경로에 따라 결정된 경로를 따라 가능한 한 많은 출력 토큰으로 정확한 양의 KLAY를 교환하고자 할 때 사용됩니다. 이 함수는 KLAY를 WKLAY로 래핑한다는 점에 유의하세요. 

struct SwapParams {
        address to;
        address[] path;
        address[] pool;
        uint deadline;
}
function swapExactETHForTokens(
        uint256 amountOutMin, 
        SwapParams calldata p
) external payable returns (uint256[] memory amounts);

B. swapExactTokensForTokens: 이 함수는 경로에 의해 결정된 경로를 따라 정확한 양의 입력 토큰을 최대한 많은 출력 토큰으로 바꾸고 싶을 때 사용합니다. 첫 번째 경로 요소는 입력 토큰이고 마지막 요소는 출력 토큰입니다.

struct SwapParams {
        address to;
        address[] path;
        address[] pool;
        uint deadline;
}
   function swapExactTokensForTokens(
        uint256 amountIn, 
        uint256 amountOutMin, 
        SwapParams calldata params
    ) external returns (uint256[] memory amounts);

C. swapExactTokensForETH: 이 함수는 경로에 의해 결정된 경로를 따라 정확한 양의 토큰을 가능한 한 많은 이더로 교환하고자 할 때 사용됩니다. 첫 번째 경로 요소는 입력 토큰이고, 마지막 요소는 WKLAY여야 합니다.  

struct SwapParams {
        address to;
        address[] path;
        address[] pool;
        uint deadline;
}
    function swapExactTokensForETH(
        uint256 amountIn, 
        uint256 amountOutMin, 
        SwapParams calldata p
    ) external returns (uint256[] memory amounts);

이 함수의 각 파라미터에 대한 자세한 설명은 KLAYswap UniversalRouter Parameter Structs를 확인하세요. 

Full 예제

다음은 스왑 작업에 직접 사용할 수 있는 완전한 작동 예시입니다. 이를 통해 KLAY를 sKAI로, sKAI를 KLAY로 교환할 수 있으며 토큰을 토큰으로 교환할 수도 있습니다. 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 interface IKlaySwapRouterV2  {
    struct SwapParams {
        address to;
        address[] path;
        address[] pool;
        uint deadline;
    }
    function swapExactTokensForTokens(
        uint256 amountIn, 
        uint256 amountOutMin, 
        SwapParams calldata params
    ) external returns (uint256[] memory amounts);
    function swapExactETHForTokens(
        uint256 amountOutMin, 
        SwapParams calldata p
    ) external payable returns (uint256[] memory amounts);
    function swapExactTokensForETH(
        uint256 amountIn, 
        uint256 amountOutMin, 
        SwapParams calldata p
    ) external returns (uint256[] memory amounts);
}
interface IERC20 {
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
}
contract MiniKLAYSwap {
    address public constant routerAddress = 0xe0fbB27D0E7F3a397A67a9d4864D4f4DD7cF8cB9;
    address public constant MBX_TOKEN_ADDRESS = 0xD068c52d81f4409B9502dA926aCE3301cc41f623;
    address public constant sKAI_TOKEN_ADDRESS = 0x37d46C6813B121d6A27eD263AeF782081ae95434;
    address public constant WKLAY_ADDRESS = 0x19Aac5f612f524B754CA7e7c41cbFa2E981A4432;
    IKlaySwapRouterV2 public immutable swapRouter = IKlaySwapRouterV2(routerAddress);
    IERC20 public immutable MBX_TOKEN = IERC20(MBX_TOKEN_ADDRESS);
    IERC20 public immutable SKAI_TOKEN = IERC20(sKAI_TOKEN_ADDRESS);    
    address[] private pool =  [address(0)];
    address[] private path =  [MBX_TOKEN_ADDRESS, WKLAY_ADDRESS];
    address[] private path1 =  [WKLAY_ADDRESS, sKAI_TOKEN_ADDRESS];
    address[] private path2 =  [sKAI_TOKEN_ADDRESS, WKLAY_ADDRESS];
    // must give contract ownership of input token
    // approve this contract address(this) of amount in; 
    // this approval enables the transfer of input token to this contract: address(this)
    function swapExactTokensForTokens(uint256 amountIn)
        external
        returns (uint256[] memory amountOut)
    {
        MBX_TOKEN.transferFrom(msg.sender, address(this), amountIn);
        MBX_TOKEN.approve(address(swapRouter), amountIn);
        IKlaySwapRouterV2.SwapParams memory params = IKlaySwapRouterV2
            .SwapParams({
                to: msg.sender,
                path: path,
                pool: pool,
                deadline: block.timestamp        
            });
        amountOut = swapRouter.swapExactTokensForTokens(amountIn, 1, params);
    }
    function swapExactKLAYForTokens()
                external
                payable 
                returns (uint256[] memory amountOut)
            {
            IKlaySwapRouterV2.SwapParams memory params = IKlaySwapRouterV2
                .SwapParams({
                    to: msg.sender,
                    path: path1,
                    pool: pool,
                    deadline: block.timestamp        
                });
            amountOut = swapRouter.swapExactETHForTokens{value: msg.value}(1, params);
    }
    // must give contract ownership of input token
    // approve this contract address(this) of amount in; 
    // this approval enables the transfer of input token to address(this)
    function swapExactTokensForKLAY(uint256 amountIn)
                external 
                returns (uint256[] memory amountOut)
            {
            
            SKAI_TOKEN.transferFrom(msg.sender, address(this), amountIn);
            SKAI_TOKEN.approve(address(swapRouter), amountIn);
            IKlaySwapRouterV2.SwapParams memory params = IKlaySwapRouterV2
                .SwapParams({
                    to: msg.sender,
                    path: path2,
                    pool: pool,
                    deadline: block.timestamp        
                });
            amountOut = swapRouter.swapExactTokensForETH(amountIn, 1, params);
    }
}

위의 소스 코드 전문은 Github에서 확인할 수 있습니다. 

thirdweb CLI를 사용하여 MiniKLAYswap.sol 배포하기

thirdweb CLI를 사용하여 MiniKLAYswap.sol을 배포하려면 이 guide를 참조하세요. 배포 가이드의 단계를 따른 후 다음 사항을 확인하세요.

i. 프로젝트 이름을 지정합니다.
ii. 선호하는 프레임워크를 선택합니다: Hardhat 또는 Foundry
iii. 스마트 컨트랙트 이름을 지정합니다. (예: MiniKLAYswap.sol).
iv. 기본 컨트랙트 유형을 선택합니다: Empty, ERC20, ERC721, 또는 ERC1155. 원하는 확장을 추가합니다. 이 튜토리얼에서는 비어있는 옵션을 선택합니다.
v. 새로 생성한 파일에 위의 코드를 붙여넣습니다:MiniKLAYswap.sol
vi. ‘npx thirdweb deploy` 명령을 사용하여 컨트랙트를 배포합니다. 

스왑 작업 수행하기

이 섹션에서는 세 번째 웹 대시보드를 사용하여 swapExactKLAYForTokensswapExactTokensForKLAY 메서드를 호출하여 스왑 작업을 수행하겠습니다. 

*Note: 스왑하려는 토큰에 유동성이 없는 경우, 유동성이 있는 쌍을 생성해야만 스왑할 수 있습니다. 유동성 풀이 존재하므로 KLAY/sKAI 스왑을 수행하는 것이 안전합니다. 

이제, 스왑 실행에 대해 하나씩 살펴보겠습니다:

1. swapExactKLAYForTokens. 

정확한 KLAY를 토큰으로 교환하려면 아래 단계를 따르세요.

i. Thirdweb 대시보드에서 배포된 MiniKLAYswap 컨트랙트의 탐색기 탭으로 이동합니다. 
ii. 컨트랙트 write 탭에서 swapExactKLAYforTokens 메서드를 클릭합니다.
iii. 제공된 입력 필드에 스왑할 KLAY의 양을 입력합니다. 
iv. Execute 버튼을 클릭해 스왑 작업을 수행합니다.

스왑이 완료되면 아래 출력 필드에 트랜잭션 해시가 표시됩니다.

스왑 작업을 확인하려면 아래와 같이 Klaytnscope의 검색창에 스왑 트랜잭션 해시를 붙여넣으세요: 

2. swapExactTokensForKLAY. 

이제 토큰을 정확한 KLAY로 다시 교환해 보겠습니다. 아래의 단계를 따르세요.

A.  스왑하고자 하는 토큰의 수량만큼 MiniKLAYswap 컨트랙트를 승인합니다. 

이는 MiniKLAYswap 컨트랙트가 토큰(이 예시에서는 sKAI)을 제어하기를 원하기 때문입니다. 이를 위해서는 아래 단계를 따르세요.

i. Klaytnscope에서 sKAI 컨트랙트 탭으로 이동합니다. 

ii. 승인 방법을 실행하기 위해 메타마스크 지갑을 연결합니다.
iii. 승인 방법을 클릭합니다.
iv. MiniKLAYswap 컨트랙트 주소와 스왑할 토큰의 양을 각각 spenderamount 입력 필드에 붙여넣습니다. 
v. Write 버튼을 클릭하여 승인 방법을 실행합니다. 

*Note: sKAI 토큰 컨트랙트는 검증된 컨트랙트이기 때문에 Klaytnscope에서 직접 상호작용했습니다. 

B. 스왑 작업 실행

이제 MiniKLAYswap이 sKAI 토큰을 제어할 수 있도록 승인되었으므로, 스왑 작업을 수행할 수 있습니다. 이를 수행하려면 아래 단계를 따르세요.

i. Thirdweb 대시보드에서 배포된 MiniKLAYswap 컨트랙트의 탐색기 탭으로 이동합니다. 
ii. 컨트랙트 쓰기 탭에서 swapExactTokensForKLAY 메서드를 클릭합니다.
iii. 제공된 write 필드에 스왑할 토큰의 양을 입력합니다.
iv. Execute 버튼을 클릭하여 스왑 작업을 수행합니다.

스왑이 완료되면 아래와 같이 출력 필드에 트랜잭션 해시를 확인할 수 있습니다.

스왑 작업을 확인하려면 아래와 같이 Klaytnscope 의 검색창에 스왑 트랜잭션 해시를 붙여넣으세요: 

결론

여러분은 이제 KLAYswap과 이를 스마트 컨트랙트에 프로그래밍 방식으로 통합하는 방법을 더 잘 이해하셨을 것입니다. 지금까지 이 튜토리얼에서는 KLAYswap Universal Router를 사용하여 스왑 방법을 구현하는 방법을 알아보고, 그 결과 KLAY를 sKAI로 또는 그 반대로 스왑할 수 있었습니다. 

2부에서는 thirdweb과 Next.js를 사용하여 MiniKLAYswap 컨트랙트와 상호작용하는 최소한의 프론트엔드를 구축하는 방법에 대해 알아보겠습니다. 더 많은 정보를 원하신다면 Klaytn DocsKLAYswap Docs를 참고하세요. 궁금한 점이 있으시다면 Klaytn Forum을 방문해주세요.