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"
|
"type": "node"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Script",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"run-script",
|
||||||
|
"cecstake"
|
||||||
|
],
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"type": "node"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug Admin",
|
"name": "Debug Admin",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
"testdraw": "ts-node -r tsconfig-paths/register src/scripts/testdraw.ts",
|
"testdraw": "ts-node -r tsconfig-paths/register src/scripts/testdraw.ts",
|
||||||
"importcec": "ts-node -r tsconfig-paths/register src/scripts/importCEC.ts",
|
"importcec": "ts-node -r tsconfig-paths/register src/scripts/importCEC.ts",
|
||||||
"parsecec": "ts-node -r tsconfig-paths/register src/scripts/cecStatic.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:watch": "jest --watch",
|
||||||
"test": "jest"
|
"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 { CecTvl } from 'models/stake/CecTvl'
|
||||||
import { retry } from 'zutils/utils/promise.util'
|
import { ethers } from 'ethers'
|
||||||
import { queryCecTvl } from 'services/chain.svr'
|
|
||||||
import { fromWei } from 'zutils/utils/bn.util'
|
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 CHAIN = process.env.CLAIM_CHAIN
|
||||||
|
const CEC_ADDRESS = process.env.CEC_CONTRACT
|
||||||
|
const CEC_CLAIM_CONTRACT = process.env.CLAIM_CONTRACT
|
||||||
|
|
||||||
const awardList = [
|
const awardList = [
|
||||||
{ amount: 5000000, reward: 0},
|
{ amount: 5000000, reward: 0},
|
||||||
|
// { amount: 5000000, reward: 10000},
|
||||||
{ amount: 10000000, reward: 400000},
|
{ amount: 10000000, reward: 400000},
|
||||||
{ amount: 18000000, reward: 900000},
|
{ amount: 18000000, reward: 900000},
|
||||||
{ amount: 20000000, reward: 2000000},
|
{ 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 {
|
export default class SimpleStakeController extends BaseController {
|
||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@ -37,8 +50,62 @@ export default class SimpleStakeController extends BaseController {
|
|||||||
}
|
}
|
||||||
let reward = award.reward
|
let reward = award.reward
|
||||||
let result = (reward / totalNum)/30*360
|
let result = (reward / totalNum)/30*360
|
||||||
result = result || 0
|
|
||||||
return result
|
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
|
* discord ticket: 26, 7, 9, 11, 13, 15, 17, 20, 21, 22, 23, 24, 25
|
||||||
* p2e season 2(Contribution Clash): 18
|
* p2e season 2(Contribution Clash): 18
|
||||||
* founder's tag holder: 19
|
* founder's tag holder: 19
|
||||||
|
* CEC stake reward: 27 // 2025年1月添加
|
||||||
* 机动: 100, 101, 102, 103, 104, 105, 106, 107, 108, 109
|
* 机动: 100, 101, 102, 103, 104, 105, 106, 107, 108, 109
|
||||||
* 机动位的用法: 比如现在是第三期, 要给某用户补发badge的奖励, 但他已经领了1,2期的奖励, 那就把他1,2期rate设为0,
|
* 机动位的用法: 比如现在是第三期, 要给某用户补发badge的奖励, 但他已经领了1,2期的奖励, 那就把他1,2期rate设为0,
|
||||||
* 把第3期的rate改为16, bit位改为未使用的机动位
|
* 把第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'
|
import { CecTvl } from 'models/stake/CecTvl'
|
||||||
|
|
||||||
const CHAIN = process.env.CLAIM_CHAIN
|
const CHAIN = process.env.CLAIM_CHAIN
|
||||||
|
const startTime = Number(process.env.STAKE_START_TIME)
|
||||||
/**
|
/**
|
||||||
* 每日定时更新TVL缓存
|
* 每日定时更新TVL缓存
|
||||||
*/
|
*/
|
||||||
@singleton
|
@singleton
|
||||||
export default class StakeSchedule {
|
export default class StakeSchedule {
|
||||||
async updateCache() {
|
async updateCache() {
|
||||||
|
if (Date.now() / 1000 < startTime) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
let preday = getDayBegin(yesterday(new Date()))
|
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 { NftHolder } from 'models/chain/NftHolder'
|
||||||
import { NftStake } from 'models/chain/NftStake'
|
import { NftStake } from 'models/chain/NftStake'
|
||||||
import { TokenClaimRecord } from 'models/chain/TokenClaimRecord'
|
import { TokenClaimRecord } from 'models/chain/TokenClaimRecord'
|
||||||
|
import { CecStakeRewardClass } from 'models/stake/CecStakeReward'
|
||||||
import { sign } from 'utils/sign.utils'
|
import { sign } from 'utils/sign.utils'
|
||||||
import { getMonthBegin, getNDayAgo } from 'utils/utcdate.util'
|
import { getMonthBegin, getNDayAgo } from 'utils/utcdate.util'
|
||||||
import { timeoutFetch } from 'zutils/utils/net.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 claimTokenAbi = ['function claim(address,address,uint256[4],bytes)']
|
||||||
|
|
||||||
const claimKeyArr = ['address', 'address', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256']
|
const claimKeyArr = ['address', 'address', 'address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256']
|
||||||
|
Loading…
x
Reference in New Issue
Block a user