根据最新需求修改cec claim相关接口

This commit is contained in:
CounterFire2023 2024-08-22 15:02:01 +08:00
parent fa64a84b15
commit 49fa01ce7e
7 changed files with 234 additions and 124 deletions

View File

@ -11,27 +11,14 @@
```js
{
"stage": 0, // 当前所处阶段, 0: 预充值阶段, 1, 2: 对应的阶段
"total": "200000000000000000000", // 总量
"available": "100000000000000000000", // 当前可获取的数量
"claimed": "0", // 已领取的数量
"outerAccount": 0, // 是否已绑定交易所账号
"stages": [ // 阶段信息
{
"stage": 1,
"amount": "100000000000000000000", // 当前阶段可获取的数量
"status": 0, // 领取状态, 0: 未领取, 1: 领取中, 9: 已领取
"unlocked": true, // 是否已解锁
"claimTime": 1720685893000, // 领取时间
"unlockTime": 1720685893000 // 解锁时间
},
{
"stage": 2,
"amount": "100000000000000000000",
"status": 0,
"unlocked": false,
"unlockTime": 1720772293000
}
],
"unavailable": "100000000000000000000", // 不可领取的数量
"bindUid": "1231****3333", // 绑定了的交易所账号
"bindAddress": "0x44****3333", // 绑定了的交易所钱包地址
"records": [
{
"address": "0x50a8e60041a206acaa5f844a1104896224be6f39",
@ -103,7 +90,8 @@ body:
```js
{
"accid": "bitget交易所账号id"
"accid": "bitget交易所账号id",
"address": "bitget交易所账号绑定的钱包地址"
}

View File

@ -75,3 +75,12 @@ export const randomSign = () => {
hex += Math.random() > 0.5 ? '1b' : '1c'
return hex
}
export const hidePartString = (str: string, showNum: number = 8) => {
if (str.length <= showNum) {
return str
}
let len = str.length
let half = Math.floor(showNum / 2)
return str.slice(0, half) + '...' + str.slice(len - half)
}

View File

@ -5,14 +5,16 @@ import { CECRecord } from 'models/CECRecord'
import { ethers } from 'ethers'
import { CECClaimHistory, CECClaimStatus } from 'models/CECClaimHistory'
import { SyncLocker } from 'common/SyncLocker'
import { CECRecordTotal, CECStatusEnum } from 'models/CECRecordTotal'
import { buildTokenClaimData } from 'services/chain.svr'
import { CECRecordTotal, CECStatusEnum, ClaimStatus, ClaimStatusEnum } from 'models/CECRecordTotal'
import { buildTokenClaimData, updateClaimStatus } from 'services/chain.svr'
import { Wallet } from 'models/Wallet'
import { PlatEnum } from 'enums/PlatEnum'
import { BitgetBindInfo } from 'models/BitgitBindInfo'
import { hidePartString } from 'common/Utils'
const STAGE1_UNLOCK_TIME = Number(process.env.CEC_CLAIM_STAGE1)
const STAGE2_UNLOCK_TIME = Number(process.env.CEC_CLAIM_STAGE2)
const CEC_CLAIM_STAGE = Number(process.env.CEC_CLAIM_STAGE)
const CEC_ADDRESS = process.env.CEC_CONTRACT
const checkAddress = (address: string) => {
@ -24,6 +26,52 @@ const checkAddress = (address: string) => {
}
return ethers.utils.getAddress(address).toLowerCase()
}
/**
* cec claim状态
* , , ,
* @param address
* @returns
*/
const queryCECClaimStatus = async (address: string) => {
let total = 0n
let available = 0n
let claimed = 0n
let unavailable = 0n
const records = await CECRecordTotal.find({ address })
const bindRecord = await BitgetBindInfo.findOne({ wallet: address })
let bit = 0n
for (const record of records) {
let currentTotal = BigInt(record.amount)
total += currentTotal
// fetch status from claimStatus
for (const [key, val] of record.claimStatus.entries()) {
const stage = parseInt(key)
// 预充值阶段, 需要展示第一期可领取的数量
if (stage === 1) {
if (CEC_CLAIM_STAGE === 0) {
available += (currentTotal * BigInt(val.rate)) / 100n
} else {
if (bindRecord && !bindRecord.invalid) {
claimed += (currentTotal * BigInt(val.rate)) / 100n
} else if (val.status === ClaimStatusEnum.NORMAL){
available += (currentTotal * BigInt(val.rate)) / 100n
bit = bit | 1n << BigInt(val.bit)
} else if ( val.status === ClaimStatusEnum.CLAIMED) {
claimed += (currentTotal * BigInt(val.rate)) / 100n
}
}
} else if (stage <= CEC_CLAIM_STAGE && val.status === ClaimStatusEnum.NORMAL) {
available += (currentTotal * BigInt(val.rate)) / 100n
bit = bit | 1n << BigInt(val.bit)
} else if ( val.status === ClaimStatusEnum.CLAIMED) {
claimed += (currentTotal * BigInt(val.rate)) / 100n
}
}
}
unavailable = total - available - claimed
return { total, available, claimed, unavailable, bit, records, bindRecord }
}
/**
* CEC领取相关接口
*/
@ -48,6 +96,8 @@ class CECController extends BaseController {
let available = 0n
let claimed = 0n
const records = await CECRecord.find({ address })
//@ts-ignore
await updateClaimStatus({ address, token: CEC_ADDRESS, records})
const now = Date.now()
const stages = []
const lists = []
@ -106,59 +156,16 @@ class CECController extends BaseController {
async scoreInfoTotal(req: any) {
let { address } = req.params
address = checkAddress(address)
let total = 0n
let available = 0n
let claimed = 0n
const records = await CECRecordTotal.find({ address })
const now = Date.now()
const stages = []
const lists = []
let firstTotal = 0n
let firstAvailable = 0n
let secondAvailable = 0n
for (const record of records) {
let currentTotal = BigInt(record.amount)
total += currentTotal
let firstRate = record.firstRate || 50
let firstAmount = currentTotal * BigInt(firstRate) / 100n
firstTotal += firstAmount
if (STAGE1_UNLOCK_TIME < now && record.status == CECStatusEnum.NORMAL) {
available += firstAmount
firstAvailable += firstAmount
}
if (STAGE2_UNLOCK_TIME < now
&& record.status != CECStatusEnum.STAGE2_CLAIMED
&& record.firstRate < 100) {
available += (currentTotal - firstAmount)
secondAvailable += (currentTotal - firstAmount)
}
}
stages.push({
stage: 1,
amount: firstTotal.toString(),
available: firstAvailable.toString(),
status: 0,
unlocked: STAGE1_UNLOCK_TIME < now,
unlockTime: STAGE1_UNLOCK_TIME
})
stages.push({
stage: 2,
amount: (total - firstTotal).toString(),
available: secondAvailable.toString(),
status: 0,
unlocked: STAGE2_UNLOCK_TIME < now,
unlockTime: STAGE2_UNLOCK_TIME
})
const bindRecord = await BitgetBindInfo.findOne({ address: address })
const { total, available, claimed, unavailable, records, bindRecord } = await queryCECClaimStatus(address)
return {
stage: CEC_CLAIM_STAGE,
total: total.toString(),
available: available.toString(),
claimed: claimed.toString(),
stages,
records: lists,
outerAccount: bindRecord? 1: 0
unavailable: unavailable.toString(),
records: records.map(record => record.toJson()),
bindUid: bindRecord ? hidePartString(bindRecord.biggetAcc) : '',
bindAddress: bindRecord ? hidePartString(bindRecord.address) : ''
}
}
@ -168,7 +175,7 @@ class CECController extends BaseController {
logger.db('claim_cec', req)
const user = req.user
const now = Date.now()
if (STAGE1_UNLOCK_TIME > now) {
if (CEC_CLAIM_STAGE === 0) {
throw new ZError(14, 'not begin')
}
let wallet: string; // 通过该地址查询可以claim的cec数量
@ -187,33 +194,7 @@ class CECController extends BaseController {
wallet = wallet.toLowerCase()
let { address } = req.params // 这个地址用于执行claim的动作
address = checkAddress(address)
const bindRecord = await BitgetBindInfo.findOne({ address: wallet })
if (bindRecord) {
throw new ZError(18, 'already bind exchange account')
}
const records = await CECRecordTotal.find({ address: wallet })
if (records.length === 0) {
throw new ZError(15, 'record not found')
}
let total = 0n
let available = 0n
let bit = 0
for (const record of records) {
let currentTotal = BigInt(record.amount)
total += currentTotal
let firstRate = record.firstRate || 50
let firstAmount = currentTotal * BigInt(firstRate) / 100n
if (STAGE1_UNLOCK_TIME < now && record.status == CECStatusEnum.NORMAL) {
available += firstAmount
bit = bit | 1 << (record.bit * 2)
}
if (STAGE2_UNLOCK_TIME < now
&& record.status != CECStatusEnum.STAGE2_CLAIMED
&& record.firstRate < 100) {
available += (currentTotal - firstAmount)
bit = bit | 1 << (record.bit * 2 + 1)
}
}
const { available, bit } = await queryCECClaimStatus(wallet)
if (available === 0n) {
throw new ZError(16, 'no cec to claim')
}
@ -223,7 +204,7 @@ class CECController extends BaseController {
account: wallet,
token: CEC_ADDRESS,
amount: available.toString(),
bit,
bit: bit.toString(),
nonce
})
return { calls: [{trans_req: data, trans_id: ''}], direct: true }
@ -232,14 +213,14 @@ class CECController extends BaseController {
@router('post /api/cec/bind_account')
async bindAccount(req: any) {
const user = req.user
const { accid } = req.body
if (!accid) {
const { accid, address } = req.body
if (!accid || !address) {
throw new ZError(11, 'accid is required')
}
const now = Date.now()
if (STAGE1_UNLOCK_TIME > now) {
throw new ZError(14, 'not begin')
if (!ethers.utils.isAddress(address)) {
throw new ZError(12, 'address is invalid')
}
let wallet: string; // 通过该地址查询可以claim的cec数量
if (user.plat === PlatEnum.EXTERNAL_WALLET) {
wallet = user.openId || user.openid
@ -260,11 +241,11 @@ class CECController extends BaseController {
throw new ZError(15, 'record not found')
}
let record = await BitgetBindInfo.findOne({ address: wallet })
let record = await BitgetBindInfo.findOne({ wallet })
if (record) {
throw new ZError(17, 'already bind')
}
record = new BitgetBindInfo({ address: wallet, biggetAcc: accid })
record = new BitgetBindInfo({ address, wallet, biggetAcc: accid })
await record.save()
return {}
}

View File

@ -16,18 +16,33 @@ import { BaseModule } from './Base'
export interface BitgetUserClass extends Base, TimeStamps {}
@dbconn()
@index({ address: 1 }, { unique: true })
@index({ wallet: 1 }, { unique: true })
@index({ biggetAcc: 1 }, { unique: false})
@modelOptions({
schemaOptions: { collection: 'bitget_bing_info', timestamps: true },
options: { allowMixed: Severity.ALLOW },
})
export class BitgetBindInfoClass extends BaseModule {
/**
* , , metamask等
*/
@prop({ required: true })
public wallet: string
/**
* ,
*/
@prop({ required: true })
public address: string
/**
* uid
*/
@prop()
public biggetAcc: string
/**
* ,
*/
@prop({ default: false })
public invalid: boolean
}

View File

@ -13,6 +13,23 @@ export enum CECStatusEnum {
STAGE1_CLAIMED = 2,
STAGE2_CLAIMED = 3
}
export enum ClaimStatusEnum {
NORMAL = 1,
CLAIMED = 2
}
@modelOptions({ schemaOptions: { _id: false } })
export class ClaimStatus {
@prop()
public bit: number
@prop()
public rate: number
@prop({ enum: ClaimStatusEnum, default: ClaimStatusEnum.NORMAL })
public status: ClaimStatusEnum
@prop()
public time: number
}
/**
* CEC赚取记录
*/
@ -21,7 +38,7 @@ export enum CECStatusEnum {
@modelOptions({
schemaOptions: { collection: 'cec_record_total', timestamps: true },
})
class CECRecordTotalClass extends BaseModule {
export class CECRecordTotalClass extends BaseModule {
@prop()
public address: string
@ -31,23 +48,27 @@ class CECRecordTotalClass extends BaseModule {
@prop()
public num: number
/**
* claim时作为标记位
*
0: uaw
1: p2a
2: game test parse 1
3: Loyalty Points Rewards
4: Badge staking rewards
5: Gacha Journey
6: Rase of Gacha
7: game season rank
8: hash rate rewards
9: old game event
**/
@prop()
public bit: number
/**
* claim阶段代表的bit位
* uaw: 0, 1
* p2a: 2, 3
* game test parse 1: 4
* Loyalty Points Rewards: 5
* Badge staking rewards: 6, 7
* Gacha Journey: 8, 9
* Rase of Gacha: 10, 11
* game season rank: 12, 13
* hash rate rewards: 14, 15
* old game event: 16, 17
* p2e season 2: 18
* founder's tag holder: 19
*/
@prop({ type: () => ClaimStatus, _id: false })
public claimStatus?: Map<string, ClaimStatus>;
@prop()
public earnTime: string

View File

@ -0,0 +1,66 @@
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn'
import { BaseModule } from '../Base'
import { GeneralEventClass } from './GeneralEvent'
@dbconn()
@index({ chain: 1, address: 1, token: 1, account: 1 }, { unique: true })
@index({ chain: 1, address: 1, token: 1, user: 1 }, { unique: true })
@modelOptions({
schemaOptions: { collection: 'token_claim_record', timestamps: true },
})
export class TokenClaimRecordClass extends BaseModule {
@prop({ required: true })
public address!: string
@prop({ required: true })
public chain: string
@prop({ type: () => [Number], default: [] })
public blockNumbers: number[]
@prop({ type: () => [String], default: [] })
public users: string[]
@prop()
public account: string
@prop()
public token: string
@prop()
public amount: string
@prop()
public bit: string
public static async parseEvent(event: Partial<GeneralEventClass>) {
const address = event.address
const chain = event.chain
const user = event.decodedData?.user
const account = event.decodedData?.account
const token = event.decodedData?.token
const amount = event.decodedData?.amount
const bit = event.decodedData?.bit
const blockNumber = event.blockNumber
let bitBn = BigInt(bit)
let record = await TokenClaimRecord.findOne({ chain, address, account, token })
if (!record) {
record = new TokenClaimRecord({ chain, address, account, token, amount, bit, blockNumbers: [blockNumber], users: [user] })
await record.save()
} else {
if ((BigInt(record.bit) & bitBn) == 0n) {
record.amount = (BigInt(record.amount) + BigInt(amount)).toString()
record.bit = (BigInt(record.bit) | bitBn).toString()
if (!record.blockNumbers.includes(blockNumber)) {
record.blockNumbers.push(blockNumber)
}
if (!record.users.includes(user)) {
record.users.push(user)
}
await record.save()
}
}
}
}
export const TokenClaimRecord = getModelForClass(TokenClaimRecordClass, {
existingConnection: TokenClaimRecordClass['db'],
})

View File

@ -1,7 +1,9 @@
import { Contract } from 'ethers'
import { CECRecordTotalClass, CECStatusEnum } from 'models/CECRecordTotal'
import { CheckIn } from 'models/chain/CheckIn'
import { NftHolder } from 'models/chain/NftHolder'
import { NftStake } from 'models/chain/NftStake'
import { TokenClaimRecord } from 'models/chain/TokenClaimRecord'
import { sign } from 'utils/sign.utils'
import { getMonthBegin, getNDayAgo } from 'utils/utcdate.util'
import { timeoutFetch } from 'zutils/utils/net.util'
@ -167,6 +169,34 @@ export const fetchClaimStatus = async (address: string, taskId: string) => {
return fetchChainStatus(address, `0x${method}${addressStr}${valStr}`)
}
export const updateClaimStatus = async (
{address, account, token, records}:
{address: string, account: string, token: string, records: Partial<CECRecordTotalClass>[]}) => {
const chain = process.env.CHAIN + ''
const record = await TokenClaimRecord.findOne({ chain, address, account, token })
if (!record) {
return
}
for (let item of records) {
let bit = BigInt(item.bit * 2)
let bit1 = bit + 1n
let changed = false
if (item.status == CECStatusEnum.NORMAL && (BigInt(record.bit) & 1n << bit) > 0n ) {
item.status = CECStatusEnum.STAGE1_CLAIMED
changed = true
}
if (item.firstRate < 100 && item.status != CECStatusEnum.STAGE2_CLAIMED && (BigInt(record.bit) & 1n << bit1) > 0n) {
item.status = CECStatusEnum.STAGE2_CLAIMED
changed = true
}
if (changed) {
//@ts-ignore
await item.save()
}
}
}
const claimTokenAbi = [
'function claim(address,address,uint256[4],bytes)',
]