Skip to content

Commit

Permalink
Add tokens holders table and totalSupply
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtemKolodko committed Nov 7, 2024
1 parent c9ac8fb commit 6fbafc2
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 57 deletions.
8 changes: 4 additions & 4 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,17 @@ export class AppController {
}

@Get('/user/:address')
async getUserByAddress(@Param('address') address: string) {
const user = await this.userService.getUserByAddress(address)
async getUserByAddress(@Param('address') userAddress: string) {
const user = await this.userService.getUserByAddress(userAddress)
if(!user) {
throw new NotFoundException('User not found')
}
return user
}

@Get('/user/:address/tokens/created')
async getUserTokensCreated(@Param('address') address: string) {
return await this.userService.getTokensCreated(address)
async getUserTokensCreated(@Param('address') userAddress: string) {
return await this.userService.getTokensCreated(userAddress)
}

@Post('/uploadImage')
Expand Down
60 changes: 18 additions & 42 deletions src/app.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {Injectable, Logger} from '@nestjs/common';
import {Between, DataSource} from "typeorm";
import {Comment, Token} from "./entities";
import {Comment, Token, TokenBalance, UserAccount} from "./entities";
import {AddCommentDto, GetCommentsDto} from "./dto/comment.dto";
import {GetTokensDto} from "./dto/token.dto";
import {Trade} from "./entities";
import * as moment from "moment";
import {GetTradesDto} from "./dto/trade.dto";
import {UserService} from "./user/user.service";

Expand Down Expand Up @@ -96,46 +95,23 @@ export class AppService {
return identifiers[0].id
}

// @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
// async handleCron() {
// const totalAttempts = 3
// for(let i = 0; i < totalAttempts; i++) {
// try {
// const winnerTokenId = await this.getDailyWinnerTokenId()
// this.logger.log(`Daily winner tokenId: ${winnerTokenId}`)
// break;
// } catch (e) {
// this.logger.error(`Failed to get daily winner, attempt: ${i+1}/${totalAttempts}`, e)
// }
// }
// }

async getDailyWinnerTokenId(): Promise<string | null> {
const dateStart = moment().subtract(1, 'days').startOf('day')
const dateEnd = moment().subtract(1, 'day').endOf('day')

const tokensMap = new Map<string, bigint>()
const tokens = await this.getTokens({ offset: 0, limit: 1000 })
for(const token of tokens) {
const tokenSwaps = await this.dataSource.manager.find(Trade, {
where: {
token: {
id: token.id
},
createdAt: Between(dateStart.toDate(), dateEnd.toDate())
async getTokenHolder(tokenAddress: string, userAddress: string) {
return await this.dataSource.manager.findOne(TokenBalance, {
where: {
token: {
address: tokenAddress.toLowerCase()
},
user: {
address: userAddress.toLowerCase()
}
})
const totalAmount = tokenSwaps.reduce((acc, item) => acc += BigInt(item.amountOut), 0n)
tokensMap.set(token.id, totalAmount)
}
const sortedMapArray = ([...tokensMap.entries()]
.sort(([aKey, aValue], [bKey, bValue]) => {
return aValue - bValue > 0 ? -1 : 1
}));
if(sortedMapArray.length > 0) {
const [winnerTokenId] = sortedMapArray[0]
return winnerTokenId
}
return null
}
})
}

async createTokenHolder(token: Token, user: UserAccount) {
return await this.dataSource.manager.insert(TokenBalance, {
token,
user,
})
}
}
6 changes: 4 additions & 2 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import { Token } from './token.entity';
import { IndexerState } from './indexer.state.entity';
import { Trade } from './trade.entity';
import { Comment } from './comment.entity';
import { TokenBalance } from './token.balances.entity';

const entities = [
UserAccount,
Token,
IndexerState,
Trade,
Comment
Comment,
TokenBalance
];

export { UserAccount, Token, IndexerState, Trade, Comment };
export { UserAccount, Token, IndexerState, Trade, Comment, TokenBalance };
export default entities;
39 changes: 39 additions & 0 deletions src/entities/token.balances.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
ManyToOne, UpdateDateColumn
} from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import {Token} from "./token.entity";
import {UserAccount} from "./user-account.entity";

@Entity({ name: 'token_balances' })
export class TokenBalance {
@ApiProperty()
@PrimaryGeneratedColumn('uuid')
id: string;

@ManyToOne(() => UserAccount, {
eager: true
})
user: UserAccount

@ManyToOne(() => Token, {
eager: true
})
token: Token

@ApiProperty()
@Column({ type: 'decimal', default: 0 })
balance: string;

@ApiProperty()
@UpdateDateColumn({ name: 'updateAt' })
updatedAt: Date;

@ApiProperty()
@CreateDateColumn({ name: 'createdAt' })
createdAt: Date;
}
4 changes: 4 additions & 0 deletions src/entities/token.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export class Token {
@Column()
symbol: string;

@ApiProperty()
@Column({ type: 'decimal', default: 0 })
totalSupply: string;

@ApiProperty()
@Column()
uri: string;
Expand Down
56 changes: 49 additions & 7 deletions src/indexer/indexer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {Injectable, Logger} from '@nestjs/common';
import {Contract, ContractAbi, EventLog, Web3} from "web3";
import {TokenMetadata, TradeEventLog, TradeType} from "../types";
import axios from "axios";
import process from "node:process";
import {IndexerState, Token, Trade} from "../entities";
import process from "process";
import {IndexerState, Token, TokenBalance, Trade} from "../entities";
import {ConfigService} from "@nestjs/config";
import {UserService} from "../user/user.service";
import {DataSource} from "typeorm";
Expand All @@ -15,6 +15,7 @@ export class IndexerService {
private readonly logger = new Logger(IndexerService.name);
private readonly web3: Web3
private readonly tokenFactoryContract: Contract<ContractAbi>
private readonly tokenContract: Contract<ContractAbi>
private readonly blocksIndexingRange = 1000

constructor(
Expand Down Expand Up @@ -79,13 +80,13 @@ export class IndexerService {

private async processTradeEvents(events: TradeEventLog[]) {
for(const event of events) {
const { data, type } = event
const { type, data } = event
const txnHash = data.transactionHash.toLowerCase()
const blockNumber = Number(data.blockNumber)
const values = data.returnValues
const tokenAddress = (values['token'] as string).toLowerCase()
const amountIn = String(values['amount0In'] as bigint)
const amountOut = String(values['amount0Out'] as bigint)
const amountIn = values['amount0In'] as bigint
const amountOut = values['amount0Out'] as bigint
const fee = String(values['fee'] as bigint)
const timestamp = Number(values['timestamp'] as bigint)

Expand All @@ -108,15 +109,56 @@ export class IndexerService {
process.exit(1)
}

const tokenRepository = this.dataSource.manager.getRepository(Token)
const tokenHoldersRepository = this.dataSource.manager.getRepository(TokenBalance)

if(type === 'buy') {
try {
let holder = await this.appService.getTokenHolder(tokenAddress, userAddress)
if(!holder) {
await this.appService.createTokenHolder(token, user)
holder = await this.appService.getTokenHolder(tokenAddress, userAddress)
}
holder.balance = String(BigInt(holder.balance) + amountOut)
await tokenHoldersRepository.save(holder)

token.totalSupply = String(BigInt(token.totalSupply) + amountOut)
await tokenRepository.save(token)

this.logger.log(`Updated token balance [${type}]: userAddress=${userAddress}, balance=${holder.balance}, token total supply=${token.totalSupply}`)
} catch (e) {
this.logger.error(`Failed to process token holder balance [${type}]: tokenAddress=${tokenAddress}, userAddress=${userAddress}`, e)
throw new Error(e);
}
} else {
try {
let holder = await this.appService.getTokenHolder(tokenAddress, userAddress)
if(!holder) {
this.logger.log(`Failed to find token holder, exit`)
process.exit(1)
}
holder.balance = String(BigInt(holder.balance) - amountIn)
await tokenHoldersRepository.save(holder)

token.totalSupply = String(BigInt(token.totalSupply) - amountIn)
await tokenRepository.save(token)

this.logger.log(`Updated token balance [${type}]: userAddress=${userAddress}, balance=${holder.balance}, token total supply=${token.totalSupply}`)
} catch (e) {
this.logger.error(`Failed to process token holder balance [${type}]: tokenAddress=${tokenAddress}, userAddress=${userAddress}`, e)
throw new Error(e);
}
}

try {
await this.dataSource.manager.insert(Trade, {
type,
txnHash,
blockNumber,
user,
token,
amountIn,
amountOut,
amountIn: String(amountIn),
amountOut: String(amountOut),
fee,
timestamp
});
Expand Down
10 changes: 8 additions & 2 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ export class UserService {
})
}

async getTokensCreated(address: string) {
async getTokensCreated(userAddress: string) {
return await this.dataSource.manager.find(Token, {
relations: ['user'],
where: {
address: address.toLowerCase(),
user: {
address: userAddress.toLowerCase()
}
},
order: {
createdAt: 'desc'
}
})
}
}

0 comments on commit 6fbafc2

Please sign in to comment.