add 领取stake reward的相关接口

This commit is contained in:
CounterFire2023 2025-01-15 14:42:03 +08:00
parent 99d1241123
commit 3cc1908d46
8 changed files with 264 additions and 4 deletions

13
.vscode/launch.json vendored
View File

@ -17,6 +17,19 @@
],
"type": "node"
},
{
"name": "Debug Script",
"request": "launch",
"runtimeArgs": [
"run-script",
"cecstake"
],
"runtimeExecutable": "npm",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"name": "Debug Admin",
"request": "launch",

View File

@ -30,6 +30,7 @@
"testdraw": "ts-node -r tsconfig-paths/register src/scripts/testdraw.ts",
"importcec": "ts-node -r tsconfig-paths/register src/scripts/importCEC.ts",
"parsecec": "ts-node -r tsconfig-paths/register src/scripts/cecStatic.ts",
"cecstake": "ts-node -r tsconfig-paths/register src/scripts/stakeReward.ts",
"test:watch": "jest --watch",
"test": "jest"
},

View File

@ -1,18 +1,31 @@
import { BaseController, ROLE_ANON, role, router } from 'zutils'
import { BaseController, ROLE_ANON, ZError, role, router } from 'zutils'
import { CecTvl } from 'models/stake/CecTvl'
import { retry } from 'zutils/utils/promise.util'
import { queryCecTvl } from 'services/chain.svr'
import { ethers } from 'ethers'
import { fromWei } from 'zutils/utils/bn.util'
import { buildTokenClaimData, updateStakeRewardStatus } from 'services/chain.svr'
import { CecStakeReward, ClaimStatusEnum } from 'models/stake/CecStakeReward'
const CHAIN = process.env.CLAIM_CHAIN
const CEC_ADDRESS = process.env.CEC_CONTRACT
const CEC_CLAIM_CONTRACT = process.env.CLAIM_CONTRACT
const awardList = [
{ amount: 5000000, reward: 0},
// { amount: 5000000, reward: 10000},
{ amount: 10000000, reward: 400000},
{ amount: 18000000, reward: 900000},
{ amount: 20000000, reward: 2000000},
]
const checkAddress = (address: string) => {
if (!address) {
throw new ZError(11, 'address is required')
}
if (!ethers.utils.isAddress(address)) {
throw new ZError(12, 'address is invalid')
}
return ethers.utils.getAddress(address).toLowerCase()
}
export default class SimpleStakeController extends BaseController {
@role(ROLE_ANON)
@ -37,8 +50,62 @@ export default class SimpleStakeController extends BaseController {
}
let reward = award.reward
let result = (reward / totalNum)/30*360
result = result || 0
return result
}
@role(ROLE_ANON)
@router('get /api/simple_stake/reward_list/:address')
async rewardList(req) {
let { address } = req.params
const now = Date.now() / 1000
const stakeEndTime = parseInt(process.env.STAKE_END_TIME)
if (now < stakeEndTime) {
throw new ZError(13, 'stake not end')
}
address = checkAddress(address)
const records = await CecStakeReward.find({ address }).sort({ stakeTime: 1 })
await updateStakeRewardStatus({ address: CEC_CLAIM_CONTRACT, account: address, token: CEC_ADDRESS, records })
let results = []
for (const record of records) {
results.push(record.toJson())
}
return results
}
@role(ROLE_ANON)
@router('post /api/simple_stake/claim')
async claim(req) {
let { address } = req.params
address = checkAddress(address)
const now = Date.now()
const stakeEndTime = parseInt(process.env.STAKE_END_TIME) * 1000
if (now < stakeEndTime) {
throw new ZError(13, 'stake not end')
}
const records = await CecStakeReward.find({ address }).sort({ stakeTime: 1 })
await updateStakeRewardStatus({ address: CEC_CLAIM_CONTRACT, account: address, token: CEC_ADDRESS, records })
let available = BigInt(0)
let claimed = false
for (const record of records) {
if (record.status === ClaimStatusEnum.CLAIMED) {
claimed = true
break
}
available = BigInt(record.amount)
}
if (claimed) {
throw new ZError(14, 'already claimed')
}
const nonce = now + '' + ((Math.random() * 1000) | 0)
const bit = 0n | (1n << BigInt(27n))
let data = await buildTokenClaimData({
address,
account: address,
token: CEC_ADDRESS,
amount: available.toString(),
bit: bit.toString(),
nonce,
})
return { calls: [{ trans_req: data, trans_id: '' }], direct: true }
}
}

View File

@ -64,6 +64,7 @@ export class CECRecordTotalClass extends BaseModule {
* discord ticket: 26, 7, 9, 11, 13, 15, 17, 20, 21, 22, 23, 24, 25
* p2e season 2(Contribution Clash): 18
* founder's tag holder: 19
* CEC stake reward: 27 // 2025年1月添加
* 机动: 100, 101, 102, 103, 104, 105, 106, 107, 108, 109
* 机动位的用法: 比如现在是第三期, badge的奖励, 1,2, 1,2rate设为0,
* 3rate改为16, bit位改为未使用的机动位

View File

@ -0,0 +1,58 @@
import { dbconn } from 'decorators/dbconn'
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { BaseModule } from '../Base'
export enum ClaimStatusEnum {
NORMAL = 1,
CLAIMED = 2,
}
/**
* 2025/01/25 CEC质押活动
* bit: 27
*/
@dbconn()
@index({ address: 1, stakeTime: 1 }, { unique: false })
@modelOptions({
schemaOptions: { collection: 'cec_stake_reward', timestamps: true },
})
export class CecStakeRewardClass extends BaseModule {
@prop()
public address: string
@prop()
public stakeAmount: string
@prop()
public stakeNum: number
@prop()
public stakeTime: number
@prop()
public amount: string
// 用于显示
@prop()
public num: number
@prop()
public apy: number
@prop({ enum: ClaimStatusEnum, default: ClaimStatusEnum.NORMAL })
public status: ClaimStatusEnum
@prop()
public desc: string
public toJson() {
return {
address: this.address,
amount: this.amount.toString(),
num: this.num,
desc: this.desc,
apy: this.apy,
status: this.status,
stakeAmount: this.stakeAmount,
stakeTime: this.stakeTime,
}
}
}
export const CecStakeReward = getModelForClass(CecStakeRewardClass, { existingConnection: CecStakeRewardClass['db'] })

View File

@ -11,12 +11,16 @@ import { singleton } from 'zutils'
import { CecTvl } from 'models/stake/CecTvl'
const CHAIN = process.env.CLAIM_CHAIN
const startTime = Number(process.env.STAKE_START_TIME)
/**
* TVL缓存
*/
@singleton
export default class StakeSchedule {
async updateCache() {
if (Date.now() / 1000 < startTime) {
return
}
try {
let preday = getDayBegin(yesterday(new Date()))

View File

@ -0,0 +1,79 @@
import * as dotenv from 'dotenv'
const envFile = process.env.NODE_ENV && process.env.NODE_ENV === 'production' ? `.env.production` : '.env.development'
dotenv.config({ path: envFile })
console.log(process.env.DB_MAIN)
import { fromWei } from 'zutils/utils/bn.util'
import { GeneralEvent } from '../models/chain/GeneralEvent'
import { CecStakeReward } from '../models/stake/CecStakeReward'
const awardList = [
// { amount: 5000000, reward: 10000}, // for test
{ amount: 5000000, reward: 0},
{ amount: 10000000, reward: 400000},
{ amount: 18000000, reward: 900000},
{ amount: 20000000, reward: 2000000},
]
const main = async () => {
const startTime = Number(process.env.STAKE_START_TIME)
const endTime = Number(process.env.STAKE_END_TIME)
const events = await GeneralEvent.find({
chain: process.env.CLAIM_CHAIN,
address: process.env.CEC_CONTRACT.toLowerCase(),
event: 'Transfer',
'decodedData.to': process.env.STAKE_CONTRACT.toLowerCase(),
$and: [{blockTime: { $gte: startTime }}, {blockTime: { $lte: endTime }}]
})
let total = 0n;
let timeAndAmount = 0n
for (let event of events) {
total += BigInt(event.decodedData.value)
timeAndAmount += BigInt(event.decodedData.value) * BigInt(endTime - event.blockTime)
}
let reward = 0n
let max = 20000000n * 10n**18n
let totalOrMax = total < max ? total : max
for (let item of awardList) {
if (totalOrMax < BigInt(item.amount * 10**18)) {
reward = BigInt(item.reward)
break
}
}
if (reward === 0n) {
console.log('total:', total.toString())
return
}
// const rewardNum = parseFloat(reward.toString())
// const totalNum = parseFloat(fromWei(total, 'ether'))
// const apy = rewardNum/totalNum / 30 * 360
reward = reward * 10n**18n
for (let event of events) {
let amount = BigInt(endTime - event.blockTime) * BigInt(event.decodedData.value) * BigInt(reward) / timeAndAmount
let amountNm = parseFloat(fromWei(amount, 'ether'))
let stakeAmount = parseFloat(fromWei(event.decodedData.value, 'ether'))
let apy = amountNm/stakeAmount / 30 * 360
let record = new CecStakeReward()
record.address = event.decodedData.from
record.stakeAmount = event.decodedData.value
record.stakeNum = stakeAmount
record.stakeTime = event.blockTime
record.amount = amount.toString()
record.num = amountNm
record.apy = apy
record.status = 1
record.desc = ''
await record.save()
}
}
;(async () => {
try {
await main();
} catch (e) {
console.log(e)
}
process.exit(0)
})()

View File

@ -4,6 +4,7 @@ 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 { CecStakeRewardClass } from 'models/stake/CecStakeReward'
import { sign } from 'utils/sign.utils'
import { getMonthBegin, getNDayAgo } from 'utils/utcdate.util'
import { timeoutFetch } from 'zutils/utils/net.util'
@ -208,6 +209,42 @@ export const updateClaimStatus = async ({
}
}
export const updateStakeRewardStatus = async ({
address,
account,
token,
records,
}: {
address: string
account: string
token: string
records: Partial<CecStakeRewardClass>[]
}) => {
const chain = process.env.CLAIM_CHAIN + ''
const record = await TokenClaimRecord.findOne({
chain,
address: address.toLowerCase(),
token: token.toLowerCase(),
account,
})
if (!record) {
return
}
const bitTotal = BigInt(record.bit)
for (let record of records) {
let changed = false
let bit = BigInt(27)
if (record.status == ClaimStatusEnum.NORMAL && (bitTotal & (1n << bit)) > 0n) {
record.status = ClaimStatusEnum.CLAIMED
changed = true
//@ts-ignore
await record.save()
}
}
}
const claimTokenAbi = ['function claim(address,address,uint256[4],bytes)']
const claimKeyArr = ['address', 'address', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256']