Skip to content

Commit

Permalink
feat: sell token indexer
Browse files Browse the repository at this point in the history
  • Loading branch information
EjembiEmmanuel committed Nov 30, 2024
1 parent 00952b4 commit 2fdf497
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 5 deletions.
17 changes: 12 additions & 5 deletions apps/nestjs-indexer/src/indexer/indexer.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { Module } from '@nestjs/common';
import { IndexerService } from './indexer.service';
import { BuyTokenIndexer } from './buy-token.indexer';
import { TokenLaunchIndexer } from './token-launch.indexer';
import { BuyTokenModule } from 'src/services/buy-token/buy-token.module';
import { BuyTokenIndexer } from './buy-token.indexer';
import { SellTokenIndexer } from './sell-token.indexer';
import { TokenLaunchModule } from 'src/services/token-launch/token-launch.module';
import { BuyTokenModule } from 'src/services/buy-token/buy-token.module';
import { SellTokenModule } from 'src/services/sell-token/sell-token.module';

@Module({
imports: [TokenLaunchModule, BuyTokenModule],
providers: [TokenLaunchIndexer, BuyTokenIndexer, IndexerService],
exports: [TokenLaunchIndexer, BuyTokenIndexer],
imports: [TokenLaunchModule, BuyTokenModule, SellTokenModule],
providers: [
TokenLaunchIndexer,
BuyTokenIndexer,
SellTokenIndexer,
IndexerService,
],
exports: [TokenLaunchIndexer, BuyTokenIndexer, SellTokenIndexer],
})
export class IndexerModule {}
159 changes: 159 additions & 0 deletions apps/nestjs-indexer/src/indexer/sell-token.indexer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { FieldElement, v1alpha2 as starknet } from '@apibara/starknet';
import { Inject, Injectable, Logger } from '@nestjs/common';
import { formatUnits } from 'viem';
import constants from 'src/common/constants';
import { uint256, validateAndParseAddress, hash } from 'starknet';
import { SellTokenService } from 'src/services/sell-token/sell-token.service';
import { IndexerService } from './indexer.service';
import { ContractAddress } from 'src/common/types';

@Injectable()
export class SellTokenIndexer {
private readonly logger = new Logger(SellTokenIndexer.name);
private readonly eventKeys: string[];

constructor(
@Inject(SellTokenService)
private readonly sellTokenService: SellTokenService,

@Inject(IndexerService)
private readonly indexerService: IndexerService,
) {
this.eventKeys = [
validateAndParseAddress(hash.getSelectorFromName('SellToken')),
];
}

async onModuleInit() {
this.indexerService.registerIndexer(
this.eventKeys,
this.handleEvents.bind(this),
);
}

private async handleEvents(
header: starknet.IBlockHeader,
event: starknet.IEvent,
transaction: starknet.ITransaction,
) {
this.logger.log('Received event');
const eventKey = validateAndParseAddress(FieldElement.toHex(event.keys[0]));

switch (eventKey) {
case validateAndParseAddress(hash.getSelectorFromName('SellToken')):
this.logger.log('Event name: SellToken');
this.handleSellTokenEvent(header, event, transaction);
break;
default:
this.logger.warn(`Unknown event type: ${eventKey}`);
}
}

private async handleSellTokenEvent(
header: starknet.IBlockHeader,
event: starknet.IEvent,
transaction: starknet.ITransaction,
) {
const {
blockNumber,
blockHash: blockHashFelt,
timestamp: blockTimestamp,
} = header;

const blockHash = validateAndParseAddress(
`0x${FieldElement.toBigInt(blockHashFelt).toString(16)}`,
) as ContractAddress;

const transactionHashFelt = transaction.meta.hash;
const transactionHash = validateAndParseAddress(
`0x${FieldElement.toBigInt(transactionHashFelt).toString(16)}`,
) as ContractAddress;

const transferId = `${transactionHash}_${event.index}`;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, callerFelt, tokenAddressFelt] = event.keys;

const ownerAddress = validateAndParseAddress(
`0x${FieldElement.toBigInt(callerFelt).toString(16)}`,
) as ContractAddress;

const tokenAddress = validateAndParseAddress(
`0x${FieldElement.toBigInt(tokenAddressFelt).toString(16)}`,
) as ContractAddress;

const [
amountLow,
amountHigh,
priceLow,
priceHigh,
protocolFeeLow,
protocolFeeHigh,
lastPriceLow,
lastPriceHigh,
timestampFelt,
quoteAmountLow,
quoteAmountHigh,
] = event.data;

const amountRaw = uint256.uint256ToBN({
low: FieldElement.toBigInt(amountLow),
high: FieldElement.toBigInt(amountHigh),
});
const amount = formatUnits(amountRaw, constants.DECIMALS).toString();

const priceRaw = uint256.uint256ToBN({
low: FieldElement.toBigInt(priceLow),
high: FieldElement.toBigInt(priceHigh),
});
const price = formatUnits(priceRaw, constants.DECIMALS);

const protocolFeeRaw = uint256.uint256ToBN({
low: FieldElement.toBigInt(protocolFeeLow),
high: FieldElement.toBigInt(protocolFeeHigh),
});
const protocolFee = formatUnits(
protocolFeeRaw,
constants.DECIMALS,
).toString();

const lastPriceRaw = uint256.uint256ToBN({
low: FieldElement.toBigInt(lastPriceLow),
high: FieldElement.toBigInt(lastPriceHigh),
});
const lastPrice = formatUnits(lastPriceRaw, constants.DECIMALS).toString();

const quoteAmountRaw = uint256.uint256ToBN({
low: FieldElement.toBigInt(quoteAmountLow),
high: FieldElement.toBigInt(quoteAmountHigh),
});
const quoteAmount = formatUnits(
quoteAmountRaw,
constants.DECIMALS,
).toString();

const timestamp = new Date(
Number(FieldElement.toBigInt(timestampFelt)) * 1000,
);

const data = {
transferId,
network: 'starknet-sepolia',
transactionHash,
blockNumber: Number(blockNumber),
blockHash,
blockTimestamp: new Date(Number(blockTimestamp.seconds) * 1000),
ownerAddress,
memecoinAddress: tokenAddress,
amount: Number(amount),
price,
protocolFee,
lastPrice,
quoteAmount,
timestamp,
transactionType: 'sell',
};

await this.sellTokenService.create(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sell-token.interface';
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface SellToken {
transferId: string;
network: string;
blockHash: string;
blockNumber: number;
blockTimestamp: Date;
transactionHash: string;
memecoinAddress: string;
ownerAddress: string;
lastPrice: string;
quoteAmount: string;
price: string;
amount: number;
protocolFee: string;
timestamp: Date;
transactionType: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { SellTokenService } from './sell-token.service';

@Module({
imports: [],
providers: [SellTokenService],
exports: [SellTokenService],
})
export class SellTokenModule {}
78 changes: 78 additions & 0 deletions apps/nestjs-indexer/src/services/sell-token/sell-token.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { SellToken } from './interfaces';

@Injectable()
export class SellTokenService {
private readonly logger = new Logger(SellTokenService.name);
constructor(private readonly prismaService: PrismaService) {}

async create(data: SellToken) {
try {
const sellTokenRecord =
await this.prismaService.token_transactions.findUnique({
where: { transfer_id: data.transferId },
});

if (sellTokenRecord) {
this.logger.warn(
`Record with transfer ID ${data.transferId} already exists`,
);
return;
}

const tokenLaunchRecord = await this.prismaService.token_launch.findFirst(
{ where: { memecoin_address: data.memecoinAddress } },
);

if (!tokenLaunchRecord) {
this.logger.warn(
`Record with memecoin address ${data.memecoinAddress} doesn't exists`,
);
} else {
const newSupply =
Number(tokenLaunchRecord.current_supply ?? 0) + Number(data.amount);
const newLiquidityRaised =
Number(tokenLaunchRecord.liquidity_raised ?? 0) -
Number(data.quoteAmount);
const newTotalTokenHolded =
Number(tokenLaunchRecord.total_token_holded ?? 0) -
Number(data.amount);

await this.prismaService.token_launch.update({
where: { transaction_hash: tokenLaunchRecord.transaction_hash },
data: {
current_supply: newSupply.toString(),
liquidity_raised: newLiquidityRaised.toString(),
total_token_holded: newTotalTokenHolded.toString(),
},
});
}

await this.prismaService.token_transactions.create({
data: {
transfer_id: data.transferId,
network: data.network,
block_hash: data.blockHash,
block_number: data.blockNumber,
block_timestamp: data.blockTimestamp,
transaction_hash: data.transactionHash,
memecoin_address: data.memecoinAddress,
owner_address: data.ownerAddress,
last_price: data.lastPrice,
quote_amount: data.quoteAmount,
price: data.price,
amount: data.amount,
protocol_fee: data.protocolFee,
time_stamp: data.timestamp,
transaction_type: data.transactionType,
},
});
} catch (error) {
this.logger.error(
`Error creating buy token record: ${error.message}`,
error.stack,
);
}
}
}

0 comments on commit 2fdf497

Please sign in to comment.