스캐너 API 개발 가이드
NFT 홀더 인증 이후 추가적인 기능 구현을 위해 준비된 개발 가이드
소개
엔드 포인트 URL 설정 방법

흐름

스펙
Request
key
type
required
description
Response

key
type
description
API 서버 예제 (Node.js)
Last updated
{
"walletAddress": "0xd464B499639A267Da03721b2DBa7469896732947",
"contractAddress": "0x8F5Aa6b6DCD2D952A22920E8fE3f798471D05901", // 토큰 홀더 인증을 설정한 경우
"tokenId": "1", // 토큰 홀더 인증을 설정한 경우
"expireTime": "2023-12-06T02:44:57.000Z",
"signature": "0x5b2fe8961ea45988d9eb6f1f927bd06fbd5b495de240b88f4d76cee293cc3a1b56c616c0046bcd73874c24555c081d4f49c58ff25bcb6233ec50018d8efc5ce21b"
}{
"success": true,
"title": "Success! 😀",
"reason": "Authentication succeeded."
}{
"success": false,
"title": "Fail! 😥",
"reason": "Authentication failed."
}mkdir api_endpoint_example
cd api_endpoint_examplenpm install express ethersconst ethers = require('ethers');
// QR코드 검증 함수
const qrAuth = (walletAddress, contractAddress, expireTime, signature) => {
// 값 확인
if (!walletAddress || !expireTime || !signature) {
throw new Error('Parameter error.');
}
// walletAddress, contractAddress 값 검증
if (!ethers.isAddress(walletAddress)) {
throw new Error('Invalid wallet address.');
}
if (contractAddress && !ethers.isAddress(contractAddress)) {
throw new Error('Invalid contract address.');
}
// QR 만료 시간 확인
if (new Date(expireTime) < new Date()) {
throw new Error('QR Expired.');
}
const loweredCasedWalletAddress = walletAddress.toLowerCase();
// recover할 메세지
const message = `i-love-fingerlabs_${loweredCasedWalletAddress}_${expireTime}`;
// 메세지에 대해 Byte Array 타입으로 변경
const msgHash = ethers.hashMessage(message);
const msgHashBytes = ethers.getBytes(msgHash);
const recoverAddress = ethers.recoverAddress(msgHashBytes, signature);
// recover한 주소와 walletAddress 비교
if (recoverAddress.toLowerCase() !== loweredCasedWalletAddress) {
throw new Error('Validation Failed.');
}
};
module.exports = qrAuth;
const express = require('express');
const ethers = require('ethers');
const app = express();
app.use(express.json());
// prefixChain: Klaytn Or Ethereum (Klaytn Chain 외 다른 체인은 전부 Ethereum Prefix)
const hashMessage = ({ prefixChain, message }) => {
const messagePrefix = `\x19${prefixChain} Signed Message:\n`;
if (typeof(message) === 'string') {
message = ethers.utils.toUtf8Bytes(message);
}
return ethers.utils.keccak256(ethers.utils.concat([
ethers.utils.toUtf8Bytes(messagePrefix),
ethers.utils.toUtf8Bytes(String(message.length)),
message
]));
}
// signature 인증
const qrVerify = ({ prefixChain, walletAddress, signature, expireTime }) => {
// recover 할 메시지 원문
const message = `i-love-fingerlabs_${walletAddress}_${expireTime}`;
// 원문을 hash
const hashedMessage = hashMessage({ prefixChain, message });
const recoverAddress = ethers.utils.recoverAddress(
hashedMessage,
signature
);
// recover한 주소와 walletAddress 비교, QR 만료 시간 확인
if (recoverAddress !== walletAddress || new Date(expireTime) < new Date()) {
throw new Error('Validation Failed.');
}
};
// 요청받을 API 엔드포인트
app.post('/', (req, res) => {
const { prefixChain, walletAddress, signature, expireTime } = req.body;
qrVerify({
prefixChain,
walletAddress,
signature,
expireTime
});
/**
* 작업할 코드
* ex) NFT mint, Token airdrop, Holder pass...
*/
// 성공
res.send({
success: true,
title: "Success! 😀",
reason: "Authentication succeeded."
});
});
// 에러 핸들러
app.use((err, req, res, next) => {
// 실패
res.send({
success: false,
title: "Fail! 😥",
reason: "Authentication failed."
});
});
// 3000번 포트로 listen
app.listen(3000, () => {
console.log('running => http://localhost:3000');
});node index.jscurl --location --request POST 'http://localhost:3000' \
--header 'Content-Type: application/json' \
--data-raw '{
"walletAddress": "0x94a8acc5c8b33f02ef0b7054ed51b9475ab33742",
"expireTime": "2023-12-06T03:08:45.000Z",
"signature": "0xaca28ee707213f1dae11c37c96b3fdf811cca2e5c09623298bbd4483095db0dc3e70c63e10999761cdc433b8f6f82ca6d65dd7a9a84d89cfdca058aecee942981b"
}'{
"success":true,
"title":"Success! 😀",
"reason":"Authentication succeeded."
}