Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch dapp staking user transactions history from SubsSquid Giant Squid #102

Merged
merged 5 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ import { IMonthlyActiveWalletsService, MonthlyActiveWalletsService } from './ser
import { MonthlyActiveWalletsController } from './controllers/MonthlyActiveWalletsController';
import { DappsStakingStatsService, IDappsStakingStatsService } from './services/DappsStakingStatsService';
import { IDappRadarService, DappRadarService } from './services/DappRadarService';
import { GiantSquidService, IGiantSquidService } from './services/GiantSquidService';
import {
BondAndStakeParser,
CallNameMapping,
CallParser,
ICallParser,
NominationTransferParser,
UnbondAndUnstakeParser,
WithdrawFromUnbondedParser,
WithdrawParser,
} from './services/GiantSquid';

const container = new Container();

Expand Down Expand Up @@ -103,6 +114,17 @@ container
.to(DappsStakingStatsService)
.inRequestScope();
container.bind<IDappRadarService>(ContainerTypes.DappRadarService).to(DappRadarService).inRequestScope();
container.bind<IGiantSquidService>(ContainerTypes.GiantSquidService).to(GiantSquidService).inRequestScope();

// Giant squid parsers
container.bind<ICallParser>(CallNameMapping.bond_and_stake).to(BondAndStakeParser).inSingletonScope();
container.bind<ICallParser>(CallNameMapping.unbond_and_unstake).to(UnbondAndUnstakeParser).inSingletonScope();
container.bind<ICallParser>(CallNameMapping.nomination_transfer).to(NominationTransferParser).inSingletonScope();
container.bind<ICallParser>(CallNameMapping.withdraw_unbonded).to(WithdrawParser).inSingletonScope();
container
.bind<ICallParser>(CallNameMapping.withdraw_from_unregistered)
.to(WithdrawFromUnbondedParser)
.inSingletonScope();

// controllers registration
container.bind<IControllerBase>(ContainerTypes.Controller).to(TokenStatsController);
Expand Down
2 changes: 2 additions & 0 deletions src/containertypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export const ContainerTypes = {
MonthlyActiveWalletsService: 'MonthlyActiveWalletsService',
DappsStakingStatsService: 'DappsStakingStatsService',
DappRadarService: 'DappRadarService',
GiantSquidService: 'GiantSquidService',
A: 'A',
bobo-k2 marked this conversation as resolved.
Show resolved Hide resolved
};
5 changes: 4 additions & 1 deletion src/controllers/DappsStakingController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { PeriodType, PeriodTypeEra } from '../services/ServiceBase';
import { IStatsIndexerService } from '../services/StatsIndexerService';
import { ControllerBase } from './ControllerBase';
import { IControllerBase } from './IControllerBase';
import { IGiantSquidService } from '../services/GiantSquidService';

@injectable()
export class DappsStakingController extends ControllerBase implements IControllerBase {
Expand All @@ -21,6 +22,7 @@ export class DappsStakingController extends ControllerBase implements IControlle
@inject(ContainerTypes.FirebaseService) private _firebaseService: IFirebaseService,
@inject(ContainerTypes.DappsStakingStatsService) private _statsService: IDappsStakingStatsService,
@inject(ContainerTypes.DappRadarService) private _dappRadarService: IDappRadarService,
@inject(ContainerTypes.GiantSquidService) private _giantSquidService: IGiantSquidService,
) {
super();
}
Expand Down Expand Up @@ -299,8 +301,9 @@ export class DappsStakingController extends ControllerBase implements IControlle
enum: ['7 days', '30 days', '90 days', '1 year']
}
*/
// this._giantSquidService.getUserCalls(req.params.network as NetworkType, req.params.userAddress, req.params.period as PeriodType);
res.json(
await this._statsService.getUserEvents(
await this._giantSquidService.getUserCalls(
req.params.network as NetworkType,
req.params.userAddress,
req.params.period as PeriodType,
Expand Down
8 changes: 8 additions & 0 deletions src/models/DappStaking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface UserEvent {
timestamp: number;
contractAddress?: string;
transaction: string;
amount?: string;
transactionHash: string;
transactionSuccess: boolean;
}
8 changes: 1 addition & 7 deletions src/services/DappsStakingStatsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IApiFactory } from '../client/ApiFactory';
import { ContainerTypes } from '../containertypes';
import { NetworkType } from '../networks';
import { PeriodType, PeriodTypeEra, ServiceBase } from './ServiceBase';
import { UserEvent } from '../models/DappStaking';

const API_URLS = {
astar: 'https://api.subquery.network/sq/bobo-k2/astar-dapp-staking-v2',
Expand All @@ -24,13 +25,6 @@ interface ContractStatsResponse {
};
}

interface UserEvent {
timestamp: number;
contractAddress: number;
transaction: string;
amount: string;
}

interface UserEventsResponse {
data: {
userTransactions: {
Expand Down
5 changes: 5 additions & 0 deletions src/services/GiantSquid/Call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class Call {
name: string;
contractAddress?: string;
amount?: bigint;
}
11 changes: 11 additions & 0 deletions src/services/GiantSquid/CallNameMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface Map {
[key: string]: string;
}

export const CallNameMapping: Map = {
bond_and_stake: 'BondAndStake',
unbond_and_unstake: 'UnbondAndUnstake',
nomination_transfer: 'NominationTransfer',
withdraw_unbonded: 'Withdraw',
withdraw_from_unregistered: 'WithdrawFromUnregistered',
};
71 changes: 71 additions & 0 deletions src/services/GiantSquid/CallParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { injectable } from 'inversify';
import { UserEvent } from '../../models/DappStaking';
import { DappStakingCallData } from './ResponseData';
import { CallNameMapping } from './CallNameMapping';

export interface ICallParser {
parse(call: DappStakingCallData): UserEvent;
}

@injectable()
export class CallParser implements ICallParser {
public parse(call: DappStakingCallData): UserEvent {
return {
timestamp: new Date(call.timestamp).getTime(),
transaction: CallNameMapping[call.callName],
transactionHash: call.extrinsicHash,
transactionSuccess: call.success,
};
}
}

@injectable()
export class BondAndStakeParser extends CallParser implements ICallParser {
public parse(call: DappStakingCallData): UserEvent {
const result = super.parse(call);
result.contractAddress = call.argsStr[1];
result.amount = call.argsStr[2];

return result;
}
}

@injectable()
export class UnbondAndUnstakeParser extends CallParser implements ICallParser {
public parse(call: DappStakingCallData): UserEvent {
const result = super.parse(call);
result.contractAddress = call.argsStr[1];
result.amount = call.argsStr[2];

return result;
}
}

@injectable()
export class NominationTransferParser extends CallParser implements ICallParser {
public parse(call: DappStakingCallData): UserEvent {
const result = super.parse(call);
result.contractAddress = call.argsStr[3];
result.amount = call.argsStr[4];

return result;
}
}

@injectable()
export class WithdrawParser extends CallParser implements ICallParser {
public parse(call: DappStakingCallData): UserEvent {
const result = super.parse(call);

return result;
}
}

@injectable()
export class WithdrawFromUnbondedParser extends CallParser implements ICallParser {
public parse(call: DappStakingCallData): UserEvent {
const result = super.parse(call);

return result;
}
}
13 changes: 13 additions & 0 deletions src/services/GiantSquid/ResponseData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface DappStakingCallResponse {
data: {
calls: DappStakingCallData[];
};
}

export interface DappStakingCallData {
callName: string;
argsStr: string[];
extrinsicHash: string;
success: boolean;
timestamp: string;
}
4 changes: 4 additions & 0 deletions src/services/GiantSquid/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './CallParser';
export * from './Call';
export * from './ResponseData';
export * from './CallNameMapping';
73 changes: 73 additions & 0 deletions src/services/GiantSquidService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { injectable } from 'inversify';
import axios from 'axios';
import { NetworkType } from '../networks';
import { Guard } from '../guard';
import { decodeAddress } from '@polkadot/util-crypto';
import { PeriodType, ServiceBase } from './ServiceBase';
import { UserEvent } from '../models/DappStaking';
import { DappStakingCallData, DappStakingCallResponse } from './GiantSquid/ResponseData';
import container from '../container';
import { ICallParser } from './GiantSquid/CallParser';
import { CallNameMapping } from './GiantSquid/CallNameMapping';

export interface IGiantSquidService {
getUserCalls(network: NetworkType, address: string, period: PeriodType): Promise<UserEvent[]>;
}

// Handles calls to SubSquid giant squid indexer.
@injectable()
export class GiantSquidService extends ServiceBase implements IGiantSquidService {
public async getUserCalls(network: NetworkType, address: string, period: PeriodType): Promise<UserEvent[]> {
Guard.ThrowIfUndefined('network', network);
Guard.ThrowIfUndefined('address', address);

if (network !== 'shiden' && network !== 'astar' && network !== 'shibuya') {
return [];
}

const privateKey = `0x${Buffer.from(decodeAddress(address)).toString('hex')}`;
const range = this.getDateRange(period);

const query = `query MyQuery {
calls(where: {
palletName_eq: "DappsStaking",
callerPublicKey_eq: "${privateKey}",
timestamp_gte: "${range.start.toISOString()}",
timestamp_lte: "${range.end.toISOString()}",
callName_not_contains: "claim"
}, orderBy: block_id_DESC) {
callName
argsStr
extrinsicHash
success
timestamp
}
}`;

const result = await axios.post<DappStakingCallResponse>(this.getApiUrl(network), {
operationName: 'MyQuery',
query,
});

return this.parseUserCalls(result.data.data.calls);
}

private getApiUrl(network: NetworkType): string {
return `https://squid.subsquid.io/gs-explorer-${network}/graphql`;
}

private parseUserCalls(calls: DappStakingCallData[]): UserEvent[] {
const result: UserEvent[] = [];

for (const call of calls) {
if (CallNameMapping[call.callName]) {
const parser = container.get<ICallParser>(CallNameMapping[call.callName]);
result.push(parser.parse(call));
} else {
// Call is not supported. Do nothing. Currently only calls defined in CallNameMapping are supported.
}
}

return result;
}
}