From 9513fab2836039de39b9515f31d0d8b0e02fd5f4 Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:37:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=90=88=E4=BD=9C=E4=BC=99?= =?UTF-8?q?=E4=BC=B4nft=20holder=20claim=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/partner_nft_list.json | 4 +- src/common/Constants.ts | 2 +- src/controllers/nft.controller.ts | 44 ++++++++++- src/controllers/sign.controller.ts | 2 +- src/controllers/tasks.controller.ts | 5 ++ src/services/chain.svr.ts | 109 +++++++++++++++++++++++----- src/services/oauth.svr.ts | 44 ++++++++++- src/services/rank.svr.ts | 5 +- 8 files changed, 188 insertions(+), 27 deletions(-) diff --git a/configs/partner_nft_list.json b/configs/partner_nft_list.json index 78e11e7..a2965ce 100644 --- a/configs/partner_nft_list.json +++ b/configs/partner_nft_list.json @@ -5,7 +5,9 @@ "contract": "0x20577896ea6113ed8c94b2f08f3893bdc08eba22", "collection": "l3e7 worlds", "remarks": "600 collection", - "chain": 1 + "chain": 1, + "guild": "1222509817411665920", + "role": "1229658972793999391" }, { "projectName": "Ultiverse", diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 6506d3a..6cdd6a9 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -22,7 +22,7 @@ export const STEP_SCORE_MIN = 40 // 每一步能获得的最大分数 export const STEP_SCORE_MAX = 50 // 每一步能获得的宝箱的概率 -export const STEP_CHEST_RATE = 0.3 +export const STEP_CHEST_RATE = 1 // 邀请用户得到额外分数 export const INVITE_REBATE = 0.1 diff --git a/src/controllers/nft.controller.ts b/src/controllers/nft.controller.ts index 8973fdd..97ef8df 100644 --- a/src/controllers/nft.controller.ts +++ b/src/controllers/nft.controller.ts @@ -2,6 +2,7 @@ import { ChestStatusEnum } from 'models/ActivityChest' import { NFTHolderRecord } from 'models/NFTHodlerRecord' import { queryNftBalance } from 'services/chain.svr' import { generateChestLevel, generateNewChest } from 'services/game.svr' +import { checkDiscordRole } from 'services/oauth.svr' import { SyncLocker, BaseController, router, role, ROLE_ANON, ZError } from 'zutils' const nftList = require('../../configs/partner_nft_list.json') @@ -33,9 +34,9 @@ class NftController extends BaseController { return list } /** - * 领取合作伙伴nft holder奖励 + * 领取合作伙伴nft holder奖励, 检查NFT */ - @router('post /api/partner/claim') + // @router('post /api/partner/claim') async claimNftHolderReward(req) { new SyncLocker().checkLock(req) const user = req.user @@ -73,4 +74,43 @@ class NftController extends BaseController { await recordNew.save() return { chests: [chest.toJson()] } } + + @router('post /api/partner/claim') + async claimNftHolderRewardDC(req) { + new SyncLocker().checkLock(req) + const user = req.user + let { contract } = req.params + if (!contract) { + throw new ZError(11, 'contract not found') + } + contract = contract.toLowerCase() + if (!nftMap.has(contract)) { + throw new ZError(12, 'contract not found') + } + let record = await NFTHolderRecord.findOne({ user: user.id, contract }) + if (record) { + throw new ZError(13, 'already claimed') + } + const cfg = nftMap.get(contract) + let rpcRes = await checkDiscordRole(user.address.toLowerCase(), cfg.guild, cfg.role) + console.log('check result:', rpcRes) + if (rpcRes.errcode) { + throw new ZError(20, `check error: ${rpcRes.errmsg}`) + } + if (!rpcRes.data.result) { + throw new ZError(14, 'not match claim condition') + } + let randomLevel = generateChestLevel() + let chest = generateNewChest(user.id, user.activity, randomLevel, ChestStatusEnum.NORMAL) + await chest.save() + let recordNew = new NFTHolderRecord({ + user: user.id, + contract, + chain: nftMap.get(contract).chain, + holderNum: 1, + rewards: [chest.id], + }) + await recordNew.save() + return { chests: [chest.toJson()] } + } } diff --git a/src/controllers/sign.controller.ts b/src/controllers/sign.controller.ts index 0566321..ca32a61 100644 --- a/src/controllers/sign.controller.ts +++ b/src/controllers/sign.controller.ts @@ -146,7 +146,7 @@ class SignController extends BaseController { discordName: user.discordName, scoreToday: formatNumShow(todayScore ? parseInt(todayScore + '') / RANK_SCORE_SCALE : 0), scoreTotal: formatNumShow(totalScore ? parseInt(totalScore + '') / RANK_SCORE_SCALE : 0), - rankTotal: totalRank ? totalRank : '-', + rankTotal: totalRank != undefined ? totalRank : '-', invite, inviteCount: records.length, inviteScore, diff --git a/src/controllers/tasks.controller.ts b/src/controllers/tasks.controller.ts index 37149b0..309d65c 100644 --- a/src/controllers/tasks.controller.ts +++ b/src/controllers/tasks.controller.ts @@ -8,6 +8,7 @@ import { BASE_TASK_TICKET } from 'common/Constants' import { BASE_TASK_REWARD, SIGN_SEQ, TicketRecord } from 'models/TicketRecord' import { ActivityGame } from 'models/ActivityGame' import { formatNumShow } from 'common/Utils' +import { fetchClaimStatus } from 'services/chain.svr' const fs = require('fs') const prod = process.env.NODE_ENV === 'production' @@ -205,6 +206,10 @@ export default class TasksController extends BaseController { if (!chainRecord) { throw new ZError(14, 'waiting for chain confirm') } + // let result = await fetchClaimStatus(user.address.toLowerCase(), task) + // if (!result) { + // throw new ZError(15, 'waiting for chain confirm') + // } } const Task = require('../tasks/' + currentTask.task) diff --git a/src/services/chain.svr.ts b/src/services/chain.svr.ts index 344ccd7..4967da0 100644 --- a/src/services/chain.svr.ts +++ b/src/services/chain.svr.ts @@ -2,6 +2,10 @@ import { CheckIn } from 'models/chain/CheckIn' import { NftHolder } from 'models/chain/NftHolder' import { NftStake } from 'models/chain/NftStake' import { getMonthBegin, getNDayAgo } from 'utils/utcdate.util' +import { timeoutFetch } from 'zutils/utils/net.util' +import { numberToBN } from 'zutils/utils/number.util' +const DEFAULT_TIMEOUT = 30000 +const ACTIVITY_RPC_URL = process.env.ACTIVITY_RPC_URL export const queryCheckInList = async (address: string, days: string | number | string[], limit: number = 0) => { let query: any = { from: address.toLowerCase() } @@ -69,23 +73,94 @@ export const queryStakeList = async (userAddress: string) => { return records } +const requestChain = async (rpc: string, method: string, params: any) => { + const data = { + id: Date.now(), + jsonrpc: '2.0', + method, + params, + } + const options: any = { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: JSON.stringify(data), + } + return timeoutFetch(rpc, options, DEFAULT_TIMEOUT).then(res => res.json()) +} + export const queryNftBalance = async (contract: string, address: string) => { const rpc = 'https://mainnet.infura.io/v3/b6bf7d3508c941499b10025c0776eaf8' - const data = { - id: (Date.now() / 1000) | 0, - jsonrpc: '2.0', - method: 'eth_call', - params: [ - { - data: `0x70a08231000000000000000000000000${address.replace('0x', '')}`, - from: address, - to: contract, - }, - 'latest', - ], - } - return fetch(rpc, { - body: JSON.stringify(data), - method: 'POST', - }).then(res => res.json()) + const params = [ + { + data: `0x70a08231000000000000000000000000${address.replace('0x', '')}`, + from: address, + to: contract, + }, + 'latest', + ] + return requestChain(rpc, 'eth_call', params) +} + +export const fetchChainStatus = async (address: string, data: string) => { + console.log(data) + const params = [ + { + data: data, + from: address, + to: process.env.ACTIVITY_CONTRACT, + }, + 'latest', + ] + return requestChain(ACTIVITY_RPC_URL, 'eth_call', params) +} + +export const fetchCheckInStatus = async (address: string) => { + const days = ((Date.now() / 1000 / 60 / 60 / 24) | 0) - 1 + const valStr = days.toString(16).padStart(64, '0') + const addressStr = address.replace('0x', '').padStart(64, '0') + const method = '86cd4926' + return fetchChainStatus(address, `0x${method}${addressStr}${valStr}`) +} + +export const fetchExploreStatus = async (address: string, exploreId: string) => { + const valStr = exploreId.toString().padStart(64, '0') + const addressStr = address.replace('0x', '').padStart(64, '0') + const method = '36028275' + return fetchChainStatus(address, `0x${method}${addressStr}${valStr}`) +} + +export const fetchOpenBoxtatus = async (address: string, boxId: string) => { + const valStr = boxId.padStart(64, '0') + const addressStr = address.replace('0x', '').padStart(64, '0') + const method = 'd869bb29' + return fetchChainStatus(address, `0x${method}${addressStr}${valStr}`) +} + +export const fetchEnhanceStatus = async (address: string, shareCode: string) => { + const shareCodeHex = shareCode + .split('') + .map(c => c.charCodeAt(0).toString(16).padStart(2, '0')) + .join('') + const valStr = shareCodeHex.padStart(64, '0') + const addressStr = address.replace('0x', '').padStart(64, '0') + const method = '9b68ea4c' + let res = await fetchChainStatus(address, `0x${method}${addressStr}${valStr}`) + if (res.error) { + throw new Error(res.error.message) + } + let result = parseInt(res.result) + return !!result +} + +export const fetchClaimStatus = async (address: string, taskId: string) => { + const taskIdHex = taskId + .split('') + .map(c => c.charCodeAt(0).toString(16).padStart(2, '0')) + .join('') + const valStr = taskIdHex.padStart(64, '0') + const addressStr = address.replace('0x', '').padStart(64, '0') + const method = '4902f7e0' + return fetchChainStatus(address, `0x${method}${addressStr}${valStr}`) } diff --git a/src/services/oauth.svr.ts b/src/services/oauth.svr.ts index 6139d50..e5f5063 100644 --- a/src/services/oauth.svr.ts +++ b/src/services/oauth.svr.ts @@ -1,7 +1,8 @@ import { hmacSha256 } from 'zutils/utils/security.util' -import { handleFetch } from 'zutils/utils/net.util' +import { handleFetch, timeoutFetch } from 'zutils/utils/net.util' const SECRET_KEY = process.env.HASH_SALT +const DEFAULT_TIMEOUT = 30000 function createSign(address: string) { // address = address.toLowerCase(); @@ -9,14 +10,51 @@ function createSign(address: string) { return signCheck } -export function checkTwitter(address: string) { +export async function checkTwitter(address: string) { let sign = createSign(address) const url = `${process.env.OAUTH_SVR_URL}/activity/twitter/${address}?sign=${sign}` return handleFetch(url) } -export function checkDiscord(address: string) { +export async function checkDiscord(address: string) { let sign = createSign(address) const url = `${process.env.OAUTH_SVR_URL}/activity/discord/${address}?sign=${sign}` return handleFetch(url) } + +export async function checkDiscordGuid(address: string, gid: string) { + let sign = createSign(address) + const data = { + gid, + address, + sign, + } + const url = `${process.env.OAUTH_SVR_URL}/activity/discord/guild` + const options: any = { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: JSON.stringify(data), + } + return timeoutFetch(url, options, DEFAULT_TIMEOUT).then(res => res.json()) +} + +export async function checkDiscordRole(address: string, gid: string, rid: string) { + let sign = createSign(address) + const data = { + gid, + address, + rid, + sign, + } + const url = `${process.env.OAUTH_SVR_URL}/activity/discord/role` + const options: any = { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + body: JSON.stringify(data), + } + return timeoutFetch(url, options, DEFAULT_TIMEOUT).then(res => res.json()) +} diff --git a/src/services/rank.svr.ts b/src/services/rank.svr.ts index 29422f4..f8c144f 100644 --- a/src/services/rank.svr.ts +++ b/src/services/rank.svr.ts @@ -91,7 +91,8 @@ export const rankKey = (activity: string, date?: Date) => { return `${activity}:score:${dateTag}` } -export const rankLevel = (rank: number) => { +export const rankLevel = (rank: number | string) => { + rank = parseInt(rank + '') const data = rankLevels.find(o => rank >= o.rankMin && rank <= o.rankMax) - return data.level + return data ? data.level : '-' }