add 领取stake reward的相关接口
This commit is contained in:
parent
99d1241123
commit
3cc1908d46
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
@ -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",
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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,2期rate设为0,
|
||||
* 把第3期的rate改为16, bit位改为未使用的机动位
|
||||
|
58
src/models/stake/CecStakeReward.ts
Normal file
58
src/models/stake/CecStakeReward.ts
Normal 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'] })
|
@ -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()))
|
||||
|
||||
|
79
src/scripts/stakeReward.ts
Normal file
79
src/scripts/stakeReward.ts
Normal 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)
|
||||
})()
|
@ -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']
|
||||
|
Loading…
x
Reference in New Issue
Block a user