增加用户claim usdt的接口
This commit is contained in:
parent
e8e40d0d23
commit
a0975a852d
49
docs/api.md
49
docs/api.md
@ -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
20
src/configs/boost.ts
Normal 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
|
||||
},
|
||||
]
|
41
src/controllers/chain.controller.ts
Normal file
41
src/controllers/chain.controller.ts
Normal 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
|
||||
}
|
||||
}
|
@ -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";
|
||||
|
@ -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 };
|
||||
|
43
src/models/TokenClaimHistory.ts
Normal file
43
src/models/TokenClaimHistory.ts
Normal 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'] })
|
||||
|
53
src/models/chain/NftStake.ts
Normal file
53
src/models/chain/NftStake.ts
Normal 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'],
|
||||
})
|
@ -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'],
|
||||
})
|
@ -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
|
||||
}
|
@ -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}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user