Skip to content

Commit 6fbafc2

Browse files
committed
Add tokens holders table and totalSupply
1 parent c9ac8fb commit 6fbafc2

File tree

7 files changed

+126
-57
lines changed

7 files changed

+126
-57
lines changed

src/app.controller.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,17 @@ export class AppController {
8686
}
8787

8888
@Get('/user/:address')
89-
async getUserByAddress(@Param('address') address: string) {
90-
const user = await this.userService.getUserByAddress(address)
89+
async getUserByAddress(@Param('address') userAddress: string) {
90+
const user = await this.userService.getUserByAddress(userAddress)
9191
if(!user) {
9292
throw new NotFoundException('User not found')
9393
}
9494
return user
9595
}
9696

9797
@Get('/user/:address/tokens/created')
98-
async getUserTokensCreated(@Param('address') address: string) {
99-
return await this.userService.getTokensCreated(address)
98+
async getUserTokensCreated(@Param('address') userAddress: string) {
99+
return await this.userService.getTokensCreated(userAddress)
100100
}
101101

102102
@Post('/uploadImage')

src/app.service.ts

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import {Injectable, Logger} from '@nestjs/common';
22
import {Between, DataSource} from "typeorm";
3-
import {Comment, Token} from "./entities";
3+
import {Comment, Token, TokenBalance, UserAccount} from "./entities";
44
import {AddCommentDto, GetCommentsDto} from "./dto/comment.dto";
55
import {GetTokensDto} from "./dto/token.dto";
66
import {Trade} from "./entities";
7-
import * as moment from "moment";
87
import {GetTradesDto} from "./dto/trade.dto";
98
import {UserService} from "./user/user.service";
109

@@ -96,46 +95,23 @@ export class AppService {
9695
return identifiers[0].id
9796
}
9897

99-
// @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
100-
// async handleCron() {
101-
// const totalAttempts = 3
102-
// for(let i = 0; i < totalAttempts; i++) {
103-
// try {
104-
// const winnerTokenId = await this.getDailyWinnerTokenId()
105-
// this.logger.log(`Daily winner tokenId: ${winnerTokenId}`)
106-
// break;
107-
// } catch (e) {
108-
// this.logger.error(`Failed to get daily winner, attempt: ${i+1}/${totalAttempts}`, e)
109-
// }
110-
// }
111-
// }
112-
113-
async getDailyWinnerTokenId(): Promise<string | null> {
114-
const dateStart = moment().subtract(1, 'days').startOf('day')
115-
const dateEnd = moment().subtract(1, 'day').endOf('day')
116-
117-
const tokensMap = new Map<string, bigint>()
118-
const tokens = await this.getTokens({ offset: 0, limit: 1000 })
119-
for(const token of tokens) {
120-
const tokenSwaps = await this.dataSource.manager.find(Trade, {
121-
where: {
122-
token: {
123-
id: token.id
124-
},
125-
createdAt: Between(dateStart.toDate(), dateEnd.toDate())
98+
async getTokenHolder(tokenAddress: string, userAddress: string) {
99+
return await this.dataSource.manager.findOne(TokenBalance, {
100+
where: {
101+
token: {
102+
address: tokenAddress.toLowerCase()
103+
},
104+
user: {
105+
address: userAddress.toLowerCase()
126106
}
127-
})
128-
const totalAmount = tokenSwaps.reduce((acc, item) => acc += BigInt(item.amountOut), 0n)
129-
tokensMap.set(token.id, totalAmount)
130-
}
131-
const sortedMapArray = ([...tokensMap.entries()]
132-
.sort(([aKey, aValue], [bKey, bValue]) => {
133-
return aValue - bValue > 0 ? -1 : 1
134-
}));
135-
if(sortedMapArray.length > 0) {
136-
const [winnerTokenId] = sortedMapArray[0]
137-
return winnerTokenId
138-
}
139-
return null
107+
}
108+
})
109+
}
110+
111+
async createTokenHolder(token: Token, user: UserAccount) {
112+
return await this.dataSource.manager.insert(TokenBalance, {
113+
token,
114+
user,
115+
})
140116
}
141117
}

src/entities/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import { Token } from './token.entity';
33
import { IndexerState } from './indexer.state.entity';
44
import { Trade } from './trade.entity';
55
import { Comment } from './comment.entity';
6+
import { TokenBalance } from './token.balances.entity';
67

78
const entities = [
89
UserAccount,
910
Token,
1011
IndexerState,
1112
Trade,
12-
Comment
13+
Comment,
14+
TokenBalance
1315
];
1416

15-
export { UserAccount, Token, IndexerState, Trade, Comment };
17+
export { UserAccount, Token, IndexerState, Trade, Comment, TokenBalance };
1618
export default entities;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
Column,
3+
CreateDateColumn,
4+
Entity,
5+
PrimaryGeneratedColumn,
6+
ManyToOne, UpdateDateColumn
7+
} from 'typeorm';
8+
import { ApiProperty } from '@nestjs/swagger';
9+
import {Token} from "./token.entity";
10+
import {UserAccount} from "./user-account.entity";
11+
12+
@Entity({ name: 'token_balances' })
13+
export class TokenBalance {
14+
@ApiProperty()
15+
@PrimaryGeneratedColumn('uuid')
16+
id: string;
17+
18+
@ManyToOne(() => UserAccount, {
19+
eager: true
20+
})
21+
user: UserAccount
22+
23+
@ManyToOne(() => Token, {
24+
eager: true
25+
})
26+
token: Token
27+
28+
@ApiProperty()
29+
@Column({ type: 'decimal', default: 0 })
30+
balance: string;
31+
32+
@ApiProperty()
33+
@UpdateDateColumn({ name: 'updateAt' })
34+
updatedAt: Date;
35+
36+
@ApiProperty()
37+
@CreateDateColumn({ name: 'createdAt' })
38+
createdAt: Date;
39+
}

src/entities/token.entity.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export class Token {
4040
@Column()
4141
symbol: string;
4242

43+
@ApiProperty()
44+
@Column({ type: 'decimal', default: 0 })
45+
totalSupply: string;
46+
4347
@ApiProperty()
4448
@Column()
4549
uri: string;

src/indexer/indexer.service.ts

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import {Injectable, Logger} from '@nestjs/common';
22
import {Contract, ContractAbi, EventLog, Web3} from "web3";
33
import {TokenMetadata, TradeEventLog, TradeType} from "../types";
44
import axios from "axios";
5-
import process from "node:process";
6-
import {IndexerState, Token, Trade} from "../entities";
5+
import process from "process";
6+
import {IndexerState, Token, TokenBalance, Trade} from "../entities";
77
import {ConfigService} from "@nestjs/config";
88
import {UserService} from "../user/user.service";
99
import {DataSource} from "typeorm";
@@ -15,6 +15,7 @@ export class IndexerService {
1515
private readonly logger = new Logger(IndexerService.name);
1616
private readonly web3: Web3
1717
private readonly tokenFactoryContract: Contract<ContractAbi>
18+
private readonly tokenContract: Contract<ContractAbi>
1819
private readonly blocksIndexingRange = 1000
1920

2021
constructor(
@@ -79,13 +80,13 @@ export class IndexerService {
7980

8081
private async processTradeEvents(events: TradeEventLog[]) {
8182
for(const event of events) {
82-
const { data, type } = event
83+
const { type, data } = event
8384
const txnHash = data.transactionHash.toLowerCase()
8485
const blockNumber = Number(data.blockNumber)
8586
const values = data.returnValues
8687
const tokenAddress = (values['token'] as string).toLowerCase()
87-
const amountIn = String(values['amount0In'] as bigint)
88-
const amountOut = String(values['amount0Out'] as bigint)
88+
const amountIn = values['amount0In'] as bigint
89+
const amountOut = values['amount0Out'] as bigint
8990
const fee = String(values['fee'] as bigint)
9091
const timestamp = Number(values['timestamp'] as bigint)
9192

@@ -108,15 +109,56 @@ export class IndexerService {
108109
process.exit(1)
109110
}
110111

112+
const tokenRepository = this.dataSource.manager.getRepository(Token)
113+
const tokenHoldersRepository = this.dataSource.manager.getRepository(TokenBalance)
114+
115+
if(type === 'buy') {
116+
try {
117+
let holder = await this.appService.getTokenHolder(tokenAddress, userAddress)
118+
if(!holder) {
119+
await this.appService.createTokenHolder(token, user)
120+
holder = await this.appService.getTokenHolder(tokenAddress, userAddress)
121+
}
122+
holder.balance = String(BigInt(holder.balance) + amountOut)
123+
await tokenHoldersRepository.save(holder)
124+
125+
token.totalSupply = String(BigInt(token.totalSupply) + amountOut)
126+
await tokenRepository.save(token)
127+
128+
this.logger.log(`Updated token balance [${type}]: userAddress=${userAddress}, balance=${holder.balance}, token total supply=${token.totalSupply}`)
129+
} catch (e) {
130+
this.logger.error(`Failed to process token holder balance [${type}]: tokenAddress=${tokenAddress}, userAddress=${userAddress}`, e)
131+
throw new Error(e);
132+
}
133+
} else {
134+
try {
135+
let holder = await this.appService.getTokenHolder(tokenAddress, userAddress)
136+
if(!holder) {
137+
this.logger.log(`Failed to find token holder, exit`)
138+
process.exit(1)
139+
}
140+
holder.balance = String(BigInt(holder.balance) - amountIn)
141+
await tokenHoldersRepository.save(holder)
142+
143+
token.totalSupply = String(BigInt(token.totalSupply) - amountIn)
144+
await tokenRepository.save(token)
145+
146+
this.logger.log(`Updated token balance [${type}]: userAddress=${userAddress}, balance=${holder.balance}, token total supply=${token.totalSupply}`)
147+
} catch (e) {
148+
this.logger.error(`Failed to process token holder balance [${type}]: tokenAddress=${tokenAddress}, userAddress=${userAddress}`, e)
149+
throw new Error(e);
150+
}
151+
}
152+
111153
try {
112154
await this.dataSource.manager.insert(Trade, {
113155
type,
114156
txnHash,
115157
blockNumber,
116158
user,
117159
token,
118-
amountIn,
119-
amountOut,
160+
amountIn: String(amountIn),
161+
amountOut: String(amountOut),
120162
fee,
121163
timestamp
122164
});

src/user/user.service.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ export class UserService {
2626
})
2727
}
2828

29-
async getTokensCreated(address: string) {
29+
async getTokensCreated(userAddress: string) {
3030
return await this.dataSource.manager.find(Token, {
31+
relations: ['user'],
3132
where: {
32-
address: address.toLowerCase(),
33+
user: {
34+
address: userAddress.toLowerCase()
35+
}
3336
},
37+
order: {
38+
createdAt: 'desc'
39+
}
3440
})
3541
}
3642
}

0 commit comments

Comments
 (0)