增加用户claim usdt的接口

This commit is contained in:
CounterFire2023 2024-01-12 15:07:53 +08:00
parent e8e40d0d23
commit a0975a852d
10 changed files with 254 additions and 68 deletions

View File

@ -489,3 +489,52 @@ body:
}]
}
```
### 18.\* 用户已质押列表
#### Request
- URL`/api/stake/list`
- 方法:`GET`
- 头部:
- Authorization: Bearer JWT_token
#### Response
```json
[
{
"nft": "nft地址",
"nftid": "1", // nftid
"start": 12312111, //质押开始时间
"cd": 11111, // cd时间, 从开始质押开始计算cd, cd未到的话, 无法赎回
"stakeTime": 11222, //质押时长
}
]
```
### 19.\* Claim USDT
#### Request
- URL`/api/lottery/claim_usdt`
- 方法:`GET`
- 头部:
- Authorization: Bearer JWT_token
#### Response
```json
{
"token": "0x1304E6AA241eE3C9ea44Db9e593e85Ae76eC41F1",
"amount": "20000000000000000",
"startTime": 1705041164,
"saltNonce": "111",
"signature": "签名"
}
```

20
src/configs/boost.ts Normal file
View File

@ -0,0 +1,20 @@
const ROUND = 1000000;
export const BOOST_CFG = [
{
value: 2,
probability: 500000
},
{
value: 3,
probability: 300000
},
{
value: 4,
probability: 150000
},
{
value: 5,
probability: 50000
},
]

View File

@ -0,0 +1,41 @@
import { SyncLocker } from "common/SyncLocker";
import { ZError } from "common/ZError";
import BaseController from "common/base.controller";
import { router } from "decorators/router";
import { FastifyRequest } from "fastify";
import { ActivityItem } from "models/ActivityItem";
import { TokenClaimHistory } from "models/TokenClaimHistory";
import { queryStakeList } from "services/chain.svr";
import { sign } from "utils/chain.util";
const MAX_LIMIT = 50
export default class ChainController extends BaseController {
@router('get /api/stake/list')
async stakeList(req) {
const user = req.user;
const records = await queryStakeList(user.address)
const result = records.map((r) => r.toJson())
return result
}
@router('post /api/lottery/claim_usdt')
async preClaimUsdt(req: FastifyRequest) {
new SyncLocker().checkLock(req);
let user = req.user;
const minClaimNum = +process.env.MINI_CLAIM_USDT
const record = await ActivityItem.findOne({user: user.id, activity: user.activity, item: 'usdt'})
if (!record || record.amount < minClaimNum) {
throw new ZError(10, 'no enough usdt')
}
const amount = record.amount + '';
record.amount = 0;
await record.save()
const token = process.env.USDT_CONTRACT;
await TokenClaimHistory.addOne({user: user.id, activity: user.activity, token, amount})
const res = await sign({user: user.address, token, amount})
return res
}
}

View File

@ -7,6 +7,7 @@ import { FUSION_CFG } from "configs/fusion";
import { ALL_ITEMS } from "configs/items";
import { LOTTERY_CFG } from "configs/lottery";
import { router } from "decorators/router";
import { FastifyRequest } from "fastify";
import { ActivityItem } from "models/ActivityItem";
import { LotteryRecord } from "models/LotteryRecord";
import { updateRankScore } from "services/rank.svr";

View File

@ -1,6 +1,7 @@
import BaseController, {ROLE_ANON} from 'common/base.controller'
import { SyncLocker } from 'common/SyncLocker'
import {ZError} from 'common/ZError'
import { BOOST_CFG } from 'configs/boost'
import { role, router } from 'decorators/router'
import logger from 'logger/logger'
import { ActivityUser } from 'models/ActivityUser'
@ -17,6 +18,20 @@ import { aesDecrypt, base58ToHex } from 'utils/security.util'
const LOGIN_TIP = 'This signature is just to verify your identity'
const ROUND = 1000000;
const generateBoost = (rewards: {probability: number}[]) => {
let total = 0;
let random = Math.floor(Math.random() * ROUND);
let reward = null;
for (let r of rewards) {
total += r.probability;
if (random < total) {
reward = r;
break;
}
}
return reward.value
}
class SignController extends BaseController {
@role(ROLE_ANON)
@router('get /api/wallet/nonce')
@ -126,7 +141,7 @@ class SignController extends BaseController {
if (user.boost > 1 && user.boostExpire && user.boostExpire > Date.now()) {
throw new ZError(11, 'already boosted')
}
user.boost = 2;
user.boost = generateBoost(BOOST_CFG)
user.boostExpire = nextday();
await user.save();
return { boost: user.boost, boostExpire: user.boostExpire };

View File

@ -0,0 +1,43 @@
import { Severity, getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn'
import { BaseModule } from './Base'
@dbconn()
@index({ user: 1 }, { unique: false })
@index({ activity: 1 }, { unique: false })
@index({user: 1, activity: 1, type: 1}, { unique: false })
@modelOptions({ schemaOptions: { collection: 'token_claim_record', timestamps: true }, options: { allowMixed: Severity.ALLOW } })
class TokenClaimHistoryClass extends BaseModule {
@prop({ required: true})
public user: string
@prop({ required: true})
public activity: string
@prop()
public token: string
@prop()
public amount: number
/**
* 0: 待确认
* 1: 已确认
* -1: 已明确失败
*/
@prop({default: 0})
public status: number
// 转账交易hash
@prop()
public hash: string
public static async addOne(params: Partial<TokenClaimHistoryClass>) {
const record = new TokenClaimHistory(params)
await record.save()
return record
}
}
export const TokenClaimHistory = getModelForClass(TokenClaimHistoryClass, { existingConnection: TokenClaimHistoryClass['db'] })

View File

@ -0,0 +1,53 @@
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn'
import { BaseModule } from '../Base'
@dbconn('chain')
@index({ chain: 1, nft: 1, tokenId: 1, start: 1 }, { unique: true })
@index({ chain:1, user: 1, nft: 1}, {unique: false})
@modelOptions({
schemaOptions: { collection: 'nft_stake_info', timestamps: true },
})
export class NftStakeClass extends BaseModule {
@prop({ required: true })
public chain: string
@prop()
public blockNumber: number
// stake info
@prop()
public address: string
@prop()
public user: string
@prop()
public nft: string
@prop()
public tokenId: string
@prop()
public start: number
@prop()
public stakeTime: number
// 1: staked, 2: redeemed
@prop()
public status: number
@prop()
public redeemTime: number
@prop({ default: 0 })
public version: number
public toJson() {
return {
nft: this.nft,
nftid: this.tokenId,
start: this.start,
cd: +process.env.STAKE_CD,
stakeTime: this.stakeTime
}
}
}
export const NftStake = getModelForClass(NftStakeClass, {
existingConnection: NftStakeClass['db'],
})

View File

@ -1,66 +0,0 @@
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn'
import { BaseModule } from '../Base'
@dbconn('chain')
@index({ chain: 1, address: 1, tokenId: 1 }, { unique: false })
@index({ chain: 1, address: 1, from: 1, to: 1 }, { unique: false })
@index({ chain: 1, hash: 1, logIndex: 1}, { unique: true })
@modelOptions({
schemaOptions: { collection: 'nft_transfer_event', timestamps: true },
})
export class NftTransferEventClass extends BaseModule {
@prop({ required: true })
public address!: string
@prop({ required: true })
public chain: string
@prop({ required: true })
public logIndex: number
@prop()
public event: string
@prop({ required: true })
public hash: string
@prop()
public blockNumber: number
@prop()
public blockHash: string
@prop()
public removed: boolean
@prop()
public from: string
@prop()
public to: string
@prop()
public tokenId: string
@prop()
public blockTime: number
@prop({ default: 0 })
public version: number
public static async saveEvent(event: any) {
const tokenId = event.tokenId || event.value
if (!tokenId) {
return
}
const logIndex = parseInt(event.logIndex || '0')
const from = event.from.toLowerCase()
const to = event.to.toLowerCase()
const hash = event.hash || event.transactionHash
const data = {
address: event.address.toLowerCase(),
blockNumber: parseInt(event.blockNumber),
removed: event.removed,
from,
to,
tokenId,
// blockTime: new Date(event.time).getTime(),
$inc: { version: 1 },
}
return NftTransferEvent.insertOrUpdate({ hash, logIndex, chain: event.chain }, data)
}
}
export const NftTransferEvent = getModelForClass(NftTransferEventClass, {
existingConnection: NftTransferEventClass['db'],
})

View File

@ -1,4 +1,5 @@
import { NftHolder } from "models/chain/NftHolder"
import { NftStake } from "models/chain/NftStake"
export const queryCheckInList = async (address: string, days: string | number | string[], limit: number = 0) => {
const url = process.env.CHAIN_SVR + '/task/check_in'
@ -43,3 +44,10 @@ export const checkHadGacha = async (user: string) => {
const record = await NftHolder.findOne({user, chain, address})
return !!record
}
export const queryStakeList = async (userAddress: string) => {
const chain = process.env.CHAIN+''
const address = process.env.BADGE_CONTRACT
let records = await NftStake.find({chain, nft: address, user: userAddress.toLowerCase()})
return records
}

View File

@ -1,4 +1,6 @@
import { recoverTypedSignature, SignTypedDataVersion } from '@metamask/eth-sig-util'
import { soliditySha3, toWei } from 'web3-utils'
import Web3 from 'web3';
export function recoverTypedSignatureV4(signObj: any, signature: string) {
return recoverTypedSignature({
@ -43,3 +45,23 @@ export function buildLoginSignMsg(nonce: string, tips: string) {
}
return signObj
}
export const sign = async ({ user, token, amount, saltNonce }
: {user: string, token: string, amount: number | string, saltNonce?: string}) => {
const web3 = new Web3();
let privateKey = process.env.SIGN_PRIVATE_KEY;
const acc = web3.eth.accounts.privateKeyToAccount(privateKey);
const account = web3.eth.accounts.wallet.add(acc);
const executor = account.address
const amountBn = toWei(amount+'');
const chainId = process.env.CHAIN;
const claimContract = process.env.CLAIM_CONTRACT;
const startTime = Date.now() / 1000 | 0
saltNonce = saltNonce || ((Math.random() * 1000) | 0) + '';
let signStr = soliditySha3.apply(this,
[user, token, claimContract, chainId, amountBn, startTime, saltNonce]);
let signature = await web3.eth.sign(signStr, executor);
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
return {token, amount: amountBn, startTime, saltNonce, signature}
}