주로 App2App을 많이 사용하다보니, 이번 글에서는 PC 환경 App2APP API 개발에 대해서만 다루겠습니다.
App2App 개발은 Prepare → Request → Result 단계로 구성되어있습니다.
Prepare는 특정한 트랜잭션을 Klip에 요청하는 단계, Request는 트랜잭션을 Klip 지갑에서 내가 서명하고 체인에 전송하는 단계, Result는 트랜잭션 전송 결과에 대해 확인하는 단계라고 보시면 됩니다.
auth, Klay 전송, FT 전송, NFT 전송, 스마트 컨트랙트 실행
으로 나뉩니다.예제 코드
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는 스펙 문서를 참고하시면 됩니다.
리액트를 활용해 개발한 예시는 아래와 같습니다.
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는 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에서 승인을 진행합니다.
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를 체크합니다.
status
는 prepared
, requested
, completed
, canceled
, error
상태중 하나를 가집니다.
completed
가 되면 인증이 완료되었으므로 이후 로직을 전개합니다.
response에는 klaytn_address라는 EOA 주소가 포함됩니다.
EOA 주소를 알았으니 계정의 잔고를 조회할 수 있습니다.
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 라이브러리를 이용해서 확인이 가능합니다.
컨트랙트 함수 호출 같은 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 연동할 때 도움이 되었으면 좋겠습니다.