Skip to content

Commit

Permalink
feat: erc721 contract total supply
Browse files Browse the repository at this point in the history
  • Loading branch information
phamphong9981 committed Sep 16, 2024
1 parent eebe700 commit 1a394f4
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 5 deletions.
30 changes: 30 additions & 0 deletions migrations/evm/20240916032201_erc721_contract_totalSupply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Knex } from 'knex';
import { Erc721Token } from '../../src/models';

export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('erc721_contract', (table) => {
table.integer('total_supply').defaultTo(0).index();
});
await knex.raw(`set statement_timeout to 0`);
const totalSupplies = await Erc721Token.query(knex)
.select('erc721_token.erc721_contract_address')
.count()
.groupBy('erc721_token.erc721_contract_address');
if (totalSupplies.length > 0) {
const stringListUpdates = totalSupplies
.map(
(totalSuply) =>
`('${totalSuply.erc721_contract_address}', ${totalSuply.count})`
)
.join(',');
await knex.raw(
`UPDATE erc721_contract SET total_supply = temp.total_supply from (VALUES ${stringListUpdates}) as temp(address, total_supply) where temp.address = erc721_contract.address`
);
}
}

export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('erc721_contract', (table) => {
table.dropColumn('total_supply');
});
}
2 changes: 2 additions & 0 deletions src/models/erc721_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export class Erc721Contract extends BaseModel {

last_updated_height!: number;

total_supply!: number;

static get tableName() {
return 'erc721_contract';
}
Expand Down
12 changes: 12 additions & 0 deletions src/services/evm/erc721.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ export default class Erc721Service extends BullableService {
if (erc721Activities.length > 0) {
// create chunk array
const listChunkErc721Activities = [];
const erc721Contracts = _.keyBy(
await Erc721Contract.query()
.whereIn(
'address',
erc721Activities.map((e) => e.erc721_contract_address)
)
.transacting(trx),
'address'
);
const erc721HolderStatsOnDB: Erc721HolderStatistic[] = [];
const erc721TokensOnDB: Erc721Token[] = [];
for (
Expand Down Expand Up @@ -159,14 +168,17 @@ export default class Erc721Service extends BullableService {
(o) => `${o.erc721_contract_address}_${o.owner}`
);
const erc721Handler = new Erc721Handler(
erc721Contracts,
erc721Tokens,
erc721Activities,
erc721HolderStats
);
erc721Handler.process();
await Erc721Handler.updateErc721(
Object.values(erc721Handler.erc721Contracts),
erc721Activities,
Object.values(erc721Handler.erc721Tokens),
Object.values(erc721Handler.erc721HolderStats),
trx
);
}
Expand Down
46 changes: 44 additions & 2 deletions src/services/evm/erc721_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export const ERC721_ACTION = {
APPROVAL_FOR_ALL: 'approval_for_all',
};
export class Erc721Handler {
erc721Contracts: Dictionary<Erc721Contract>;

// key: {contract_address}_{token_id}
// value: erc721 token
erc721Tokens: Dictionary<Erc721Token>;
Expand All @@ -74,10 +76,12 @@ export class Erc721Handler {
erc721HolderStats: Dictionary<Erc721HolderStatistic>;

constructor(
erc721Contracts: Dictionary<Erc721Contract>,
erc721Tokens: Dictionary<Erc721Token>,
erc721Activities: Erc721Activity[],
erc721HolderStats: Dictionary<Erc721HolderStatistic>
) {
this.erc721Contracts = erc721Contracts;
this.erc721Tokens = erc721Tokens;
this.erc721Activities = erc721Activities;
this.erc721HolderStats = erc721HolderStats;
Expand All @@ -92,7 +96,8 @@ export class Erc721Handler {
}

handlerErc721Transfer(erc721Activity: Erc721Activity) {
// update erc721 token
const erc721Contract =
this.erc721Contracts[`${erc721Activity.erc721_contract_address}`];
const token =
this.erc721Tokens[
`${erc721Activity.erc721_contract_address}_${erc721Activity.token_id}`
Expand All @@ -101,6 +106,9 @@ export class Erc721Handler {
// update new owner and last updated height
token.owner = erc721Activity.to;
token.last_updated_height = erc721Activity.height;
if (erc721Activity.to === ZERO_ADDRESS) {
erc721Contract.total_supply -= 1;
}
} else if (erc721Activity.from === ZERO_ADDRESS) {
// handle mint
this.erc721Tokens[
Expand All @@ -112,6 +120,7 @@ export class Erc721Handler {
last_updated_height: erc721Activity.height,
burned: false,
});
erc721Contract.total_supply += 1;
} else {
throw new Error('Handle erc721 tranfer error');
}
Expand All @@ -121,14 +130,21 @@ export class Erc721Handler {
this.erc721HolderStats[
`${erc721Activity.erc721_contract_address}_${erc721Activity.from}`
];
if (!erc721HolderStatFrom) {
throw new Error(
`Erc721 holder ${ erc721Activity.from } havent been audited`
);
}
erc721HolderStatFrom.count -= 1;
}
if (erc721Activity.to !== ZERO_ADDRESS) {
const erc721HolderStatTo =
this.erc721HolderStats[
`${erc721Activity.erc721_contract_address}_${erc721Activity.to}`
];
erc721HolderStatTo.count += 1;
erc721HolderStatTo.count = erc721HolderStatTo
? erc721HolderStatTo.count + 1
: 1;
}
}

Expand Down Expand Up @@ -287,11 +303,28 @@ export class Erc721Handler {
}

static async updateErc721(
erc721Contracts: Erc721Contract[],
erc721Activities: Erc721Activity[],
erc721Tokens: Erc721Token[],
erc721HolderStats: Erc721HolderStatistic[],
trx: Knex.Transaction
) {
// update erc721 contract: total supply
if (erc721Contracts.length > 0) {
const stringListUpdates = erc721Contracts
.map(
(erc721Contract) =>
`('${erc721Contract.id}', ${erc721Contract.total_supply})`
)
.join(',');
await knex
.raw(
`UPDATE erc721_contract SET total_supply = temp.total_supply from (VALUES ${stringListUpdates}) as temp(id, total_supply) where temp.id = erc721_contract.id`
)
.transacting(trx);
}
let updatedTokens: Dictionary<Erc721Token> = {};
// update erc721 token: new token & new holder
if (erc721Tokens.length > 0) {
updatedTokens = _.keyBy(
await Erc721Token.query()
Expand All @@ -311,6 +344,7 @@ export class Erc721Handler {
(o) => `${o.erc721_contract_address}_${o.token_id}`
);
}
// insert new erc721 activities
if (erc721Activities.length > 0) {
erc721Activities.forEach((activity) => {
const token =
Expand All @@ -330,6 +364,14 @@ export class Erc721Handler {
)
.transacting(trx);
}
// update erc721 holder statistic
if (erc721HolderStats.length > 0) {
await Erc721HolderStatistic.query()
.transacting(trx)
.insert(erc721HolderStats)
.onConflict(['erc721_contract_address', 'owner'])
.merge();
}
}

static async calErc721Stats(addresses?: string[]): Promise<Erc721Contract[]> {
Expand Down
31 changes: 28 additions & 3 deletions src/services/evm/erc721_reindex.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import Moleculer from 'moleculer';
import { PublicClient, getContract } from 'viem';
import { Dictionary } from 'lodash';
import knex from '../../common/utils/db_connection';
import {
Erc721Activity,
Erc721Contract,
Erc721HolderStatistic,
Erc721Stats,
Erc721Token,
} from '../../models';
Expand Down Expand Up @@ -74,6 +76,10 @@ export class Erc721Reindexer {
)
.first()
.throwIfNotFound();
await Erc721HolderStatistic.query()
.delete()
.where('erc721_contract_address', address)
.transacting(trx);
await Erc721Stats.query()
.delete()
.where('erc721_contract_id', erc721Contract.id)
Expand All @@ -100,7 +106,10 @@ export class Erc721Reindexer {
contract.read.name().catch(() => Promise.resolve(undefined)),
contract.read.symbol().catch(() => Promise.resolve(undefined)),
]);
await Erc721Contract.query()
const [tokens, height] = await this.getCurrentTokens(address);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const newErc721Contract: Erc721Contract[] = await Erc721Contract.query()
.insert(
Erc721Contract.fromJson({
evm_smart_contract_id: erc721Contract.evm_smart_contract_id,
Expand All @@ -109,18 +118,34 @@ export class Erc721Reindexer {
name: contractInfo[0],
track: true,
last_updated_height: Number(blockHeight),
total_supply: tokens.length,
})
)
.transacting(trx);
const [tokens, height] = await this.getCurrentTokens(address);
const activities = await Erc721Handler.getErc721Activities(
0,
height,
this.logger,
[address],
trx
);
await Erc721Handler.updateErc721(activities, tokens, trx);
const erc721HolderStats: Dictionary<Erc721HolderStatistic> =
tokens.reduce((acc: Dictionary<Erc721HolderStatistic>, curr) => {
const count = acc[curr.owner] ? acc[curr.owner].count + 1 : 1;
acc[curr.owner] = Erc721HolderStatistic.fromJson({
erc721_contract_address: address,
owner: curr.owner,
count,
});
return acc;
}, {});
await Erc721Handler.updateErc721(
newErc721Contract,
activities,
tokens,
Object.values(erc721HolderStats),
trx
);
});
const erc721Stats = await Erc721Handler.calErc721Stats([address]);
if (erc721Stats.length > 0) {
Expand Down

0 comments on commit 1a394f4

Please sign in to comment.