DApp에 클립(Klip)을 연동해보자!

승톨·2022년 7월 18일
0

배경

  • 토이 프로젝트로 클레이튼 환경 디파이를 활용한 디파이 프로젝트를 만들어보고 있습니다.
  • 현재 프로젝트에 카이카스 지갑만 연동이 되는 상황인데, 카이카스 외의 지갑들이 몇 개 있기 때문에 다른 지갑을 붙여보는 경험을 해보고싶었습니다.
  • Klip은 API로 지갑을 제공해주는 서비스라서, Injected Provider 형태의 지갑과 연동이 다릅니다. API 형태의 지갑을 연동해야 할 일이 있을 때 레퍼런스가 되면 좋을 것 같아 글을 적었습니다.

다룰 내용

  • Klip의 Prepare, Request, Result API 사용법
  • QR 코드 만드는법
  • 개발 시 주의 할 점

Klip이란

  • Klip은 Klaytn 기반의 디지털 지갑 서비스입니다. 현재는 카카오톡 하위 기능으로 존재합니다.
  • 카카오의 중앙화된 서버에서 지갑을 관리하고 있으며, 개인키를 Klip 측에서 암호화하여 보관하고 있습니다.( 이용자가 개인키 확인이 불가능하다.)
  • Klip은 사용자의 모바일 환경에서만 사용이 가능하며, Klaytn 기반의 FT, NFT를 전송, 보관할 수 있습니다.

Klip API

  • API를 통해 Klip에 있는 KLAY, FT 조회, 전송 및 NFT 발행, 조회, 전송 또는 삭제하는 기능을 제공합니다.
  • Klip API는 크게 Card Minting API와 App2App API로 구성되어 있습니다.
  • Card Minting API는 NFT(Card)를 발행,조회, 전송, 삭제하는 API 입니다.(Klip Partners 가입 심사가 필요)
  • App2App API는 Klip 사용자 인증, 트랜잭션을 전송할 수 있는 API입니다. PC 환경에서는 QR코드로, 모바일 환경에서는 Deep Link를 통해 Klip에 서명을 요청하는 방식으로 동작합니다.(가입 심사 필요 X)
  • 공식 문서 : https://docs.klipwallet.com/

주로 App2App을 많이 사용하다보니, 이번 글에서는 PC 환경 App2APP API 개발에 대해서만 다루겠습니다.

App2App 개발은 Prepare → Request → Result 단계로 구성되어있습니다.

Prepare는 특정한 트랜잭션을 Klip에 요청하는 단계, Request는 트랜잭션을 Klip 지갑에서 내가 서명하고 체인에 전송하는 단계, Result는 트랜잭션 전송 결과에 대해 확인하는 단계라고 보시면 됩니다.

Prepare

  • Klip에 요청 데이터를 전달하고 Request Key를 발급받는 과정입니다. Request Key는 QR코드나 Deep Link 호출 및 결과 확인 과정에서 필요합니다.
  • 요청의 종류는 auth, Klay 전송, FT 전송, NFT 전송, 스마트 컨트랙트 실행으로 나뉩니다.
  • 실제로 제가 Klip을 연동하면서 진행했던 예시(Auth → 잔고 보여주기 → FT Approve 트랜잭션 전송)를 보여드리겠습니다.
  • Prepare 단계에서 가장 중요한 요청은 Auth 입니다. Klip 사용자의 EOA 주소를 알아야 서명도 진행할 수 있기 때문에 Auth API 요청부터 시작해야 합니다.
  • 공식 스펙 참고 : https://docs.klipwallet.com/rest-api/rest-api-a2a#api-detail

예제 코드

curl -X POST "https://a2a-api.klipwallet.com/v2/a2a/prepare" \
-d '{"bapp": { "name" : "My BApp" }, "callback": { "success": "mybapp:\/\/klipwallet\/success", "fail": "mybapp:\/\/klipwallet\/fail" }, "type": "auth" }' \
-H "Content-Type: application/json"

참고로 예제 코드에는 callback 객체에 sccess, fail 필드가 있지만, Deep Link를 사용하지 않을거면 생략해도 상관이 없습니다.

스펙에 있는 엔드포인트로 POST 요청을 날리면 되고, Payload object는 스펙 문서를 참고하시면 됩니다.

리액트를 활용해 개발한 예시는 아래와 같습니다.

  • Auth 요청 시에는 Klip JS SDK를 사용했습니다.
const connectKlip = async () => {
    // Auth 요청해서 Request key 얻기
    try{
      const bappName = 'Test BAPP'
      const successLink = ''
      const failLink = ''
        const result = await prepare.auth({bappName, successLink, failLink});
        if (result.err) {
        } else if (result.request_key) {
          // 얻은 request key를 url로 하는 QR 코드 생성하는 QRvalue SET STATE
          setqrValue(`https://klipwallet.com/?target=/a2a?request_key=${result.request_key}`);
          setmodalOpen(true);
          
          // Auth 요청 인증 결과를 받는 Result call polling
          let timer = setInterval(() => 
          {
            axios.get(`https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${result.request_key}`)
              .then( async (response)=> {
                  if(response.data.status === "completed"){
                    const selectedAddress = response.data.result.klaytn_address;
                    setCurrentAccount(response.data.result.klaytn_address);              
                    setmodalOpen(false);
                    clearInterval(timer);
                  }
                });
          }, 1000);
        }
        return true;
    } 
    catch (error) {
      console.log(error);
      return false;
    }
  }

prepare.auth를 통해 bappName이 담긴 객체를 인자로 넘깁니다.

const result = await prepare.auth({bappName, successLink, failLink});
{
  "request_key": "0b0ee0ad-62b3-4146-980b-531b3201265d", // random string
  "status": "prepared", // 정상적으로 처리된 경우. 만약 문제가 있다면 "error" 상태가 넘어옴.
  "expiration_time": 1600011054 //unix timestamp
}

Request

Request는 BApp에서 Klip에 App2App 처리를 요청하기 위한 QR코드 혹은 Deep Link를 실행하는 과정입니다. Klip이 실행된 경우, 사용자에게 확인 창이 뜨게됩니다

위에서 받은 equest_key는 QR 코드를 만드는데 사용됩니다. docs에 나온 url에 request key를 넣어서 QR 코드를 만듭니다.

https://klipwallet.com/?target=/a2a?request_key={requestKey}

QR코드를 만드는 방법은 여러가지가 있지만, 저는 qr.react 라이브러리를 활용했습니다.

import QRCodeCanvas from "qrcode.react"
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';

const QRcodeModal = ({value, open, onClose}) => {

    const handleClose = () => {
        onClose(false);
      };

    return (
            <Dialog onClose={handleClose} open={open} maxWidth={'md'}
                >
                <DialogTitle>Klip QR Code</DialogTitle>
                <QRCodeCanvas value={value} size={150} style={{ margin: "auto" }} />
            </Dialog>
    )
}
export default QRcodeModal;

<QRCodeCanvas /> 를 랜더링하면 request_key를 url에 담은 QR코드를 화면에 나타낼 수 있습니다.
props로 있는 value에 QR코드 url을 넣으면 됩니다. 좀 더 자세한 사용법은 라이브러리 깃헙 사이트를 참고하시기 바랍니다.

QR코드를 촬영하면 Klip 링크로 이동하고, Klip에서 승인을 진행합니다.

Result

API 요청의 최종 상태는 Result API를 polling하여 얻을 수 있습니다.
이때 Prepare 과정에서 얻은 request_key값을 쿼리 스트링으로 설정합니다.

승인 결과는 klip api를 호출하여 확인합니다.

let timer = setInterval(() => 
          {
            axios.get(`https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${result.request_key}`)
              .then( async (response)=> {
                  if(response.data.status === "completed"){
                    const selectedAddress = response.data.result.klaytn_address;
                    setCurrentAccount(response.data.result.klaytn_address);              
                    setmodalOpen(false);
                    clearInterval(timer);
                  }
                });
          }, 1000);

setInterval로 klip api 결과를 주기적으로 확인해 response의 status를 체크합니다.

statusprepared, requested, completed, canceled, error 상태중 하나를 가집니다.

completed 가 되면 인증이 완료되었으므로 이후 로직을 전개합니다.

response에는 klaytn_address라는 EOA 주소가 포함됩니다.

EOA 주소를 알았으니 계정의 잔고를 조회할 수 있습니다.

Method Call

const TokenContract = new caver.klay.Contract(ERC20.abi, DEPLOYED_ADDRESS)
const getTokenAmount = async (selectedAddress) => {
    const amount = await TokenContract.methods.balanceOf(selectedAddress).call();
    setAmount(amount);
  }

잔고 조회 같은 Read 메소드는 Klip API 호출 없이 caver 라이브러리를 이용해서 확인이 가능합니다.

Execute Contract

컨트랙트 함수 호출 같은 Write 메소드는 Klip API를 사용해 호출합니다.

docs : https://docs.klipwallet.com/tutorial/tutorial-a2a-rest-api#case-5-execute-contract

export const executeContract = async (txTo, txValue, txAbi, txParams, setqrValue, callback) => {
    axios.post("https://a2a-api.klipwallet.com/v2/a2a/prepare",
        {
            bapp: { name: 'BAPP'},
            transaction: {
                to: txTo,
                abi: txAbi,
                value: txValue,
                params: txParams,
              },
            type: "execute_contract",
        }).then((response) => {
            const {request_key} = response.data;
            setqrValue(`https://klipwallet.com/?target=/a2a?request_key=${request_key}`);

            let timer = setInterval(() => 
            {
                axios.get(`https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${request_key}`)
                .then( async (response)=> {
                    if(response.data.status === "completed"){
                        const status = response.data.result.status;
                        if (status === "success"){
                            callback();
                            clearInterval(timer);
                        }
                    }})
            }, 1000);
        }).catch((error) => {
            console.log(error);
            });
}

execute_contract는 스마트컨트랙트 함수를 호출할 수 있는 API 입니다. 모든 컨트랙트 함수는 이 API로 요청할 수 있습니다. transaction 필드에 4개의 인자가 필수적으로 필요합니다.

to는 실행할 스마트 컨트랙트 주소, value는 해당 컨트랙트에 전송할 KLAY(peb 단위) ,abi는 실행할 컨트랙트 함수 인터페이스, params는 해당 함수에 넘겨줄 인자값입니다.

이 과정에서 나는 대부분의 오류는 abi 혹은 params의 값을 넣다가 발생합니다.

아래의 예시 abi(approve 메소드)와 params가 있으니 참고하시면 됩니다.

const abi = '{ "constant": false, "inputs": [ { "internalType": "address", "name": "spender", "type": "address"},{ "internalType": "uint256", "name": "amount", "type": "uint256" }], "name": "approve", "outputs": [{ "name": "", "type": "bool"}], "payable":false, "stateMutability": "nonpayable","type": "function" }';
params = `[\"${Contract._address}\",\"1000000000000000000000000"]`,

참고로 uint256 타입으로 넣어야 하는 값은 숫자를 온전하게 적어야 호출이 됩니다.(1e+25 같은 string 형태로 표시하는 숫자 값은 오류가 남.)

HTTP post로 execute_contract 를 호출한 뒤, QR코드를 띄운 다음, 트랜잭션 결과를 setInterval로 확인하는 로직은 위의 auth 호출과 동일합니다.

트랜잭션이 성공한 경우 아래와 같은 값이 리턴됩니다.

{
  "request_key": "random key",
  "expiration_time": unix timestamp,
  "status": "completed",
  "result": {
    "tx_hash": string,
    "status": "success"
  }
}

마치며

아직 클레이튼 환경의 Klip 개발 레퍼런스 문서가 많이 부족한데, 이 글이 독자분들이 Klip 연동할 때 도움이 되었으면 좋겠습니다.

profile
소프트웨어 엔지니어링을 연마하고자 합니다.

0개의 댓글