根据最新需求修改cec claim相关接口
This commit is contained in:
parent
fa64a84b15
commit
49fa01ce7e
@ -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交易所账号绑定的钱包地址"
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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 {}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
66
src/models/chain/TokenClaimRecord.ts
Normal file
66
src/models/chain/TokenClaimRecord.ts
Normal 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'],
|
||||
})
|
@ -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)',
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user