스캐너 API 개발 가이드
NFT 홀더 인증 이후 추가적인 기능 구현을 위해 준비된 개발 가이드
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."
}