diff --git a/src/container.ts b/src/container.ts index 0307006..9147eb9 100644 --- a/src/container.ts +++ b/src/container.ts @@ -31,9 +31,9 @@ import { DappsStakingStatsService, IDappsStakingStatsService } from './services/ import { IDappRadarService, DappRadarService } from './services/DappRadarService'; import { GiantSquidService, IGiantSquidService } from './services/GiantSquidService'; import { + BatchCallParser, BondAndStakeParser, CallNameMapping, - CallParser, ICallParser, NominationTransferParser, UnbondAndUnstakeParser, @@ -125,6 +125,7 @@ container .bind(CallNameMapping.withdraw_from_unregistered) .to(WithdrawFromUnbondedParser) .inSingletonScope(); +container.bind(CallNameMapping.batch).to(BatchCallParser).inSingletonScope(); // controllers registration container.bind(ContainerTypes.Controller).to(TokenStatsController); diff --git a/src/controllers/DappsStakingController.ts b/src/controllers/DappsStakingController.ts index e27f32c..b6367c4 100644 --- a/src/controllers/DappsStakingController.ts +++ b/src/controllers/DappsStakingController.ts @@ -268,13 +268,17 @@ export class DappsStakingController extends ControllerBase implements IControlle enum: ['7 eras', '30 eras', '90 eras', 'all'] } */ - res.json( - await this._statsService.getContractStatistics( - req.params.network as NetworkType, - req.params.contractAddress, - req.params.period as PeriodTypeEra, - ), - ); + try { + res.json( + await this._statsService.getContractStatistics( + req.params.network as NetworkType, + req.params.contractAddress, + req.params.period as PeriodTypeEra, + ), + ); + } catch (err) { + this.handleError(res, err as Error); + } }, ); @@ -302,13 +306,17 @@ export class DappsStakingController extends ControllerBase implements IControlle } */ // this._giantSquidService.getUserCalls(req.params.network as NetworkType, req.params.userAddress, req.params.period as PeriodType); - res.json( - await this._giantSquidService.getUserCalls( - req.params.network as NetworkType, - req.params.userAddress, - req.params.period as PeriodType, - ), - ); + try { + res.json( + await this._giantSquidService.getUserCalls( + req.params.network as NetworkType, + req.params.userAddress, + req.params.period as PeriodType, + ), + ); + } catch (err) { + this.handleError(res, err as Error); + } }, ); diff --git a/src/services/GiantSquid/CallNameMapping.ts b/src/services/GiantSquid/CallNameMapping.ts index 2d64239..75eaa99 100644 --- a/src/services/GiantSquid/CallNameMapping.ts +++ b/src/services/GiantSquid/CallNameMapping.ts @@ -8,4 +8,5 @@ export const CallNameMapping: Map = { nomination_transfer: 'NominationTransfer', withdraw_unbonded: 'Withdraw', withdraw_from_unregistered: 'WithdrawFromUnregistered', + batch: 'Batch', }; diff --git a/src/services/GiantSquid/CallParser.ts b/src/services/GiantSquid/CallParser.ts index 74f55de..1042fac 100644 --- a/src/services/GiantSquid/CallParser.ts +++ b/src/services/GiantSquid/CallParser.ts @@ -69,3 +69,32 @@ export class WithdrawFromUnbondedParser extends CallParser implements ICallParse return result; } } + +// Parses Reward events from a batch call and calculates total rewards per batch. +@injectable() +export class BatchCallParser extends CallParser implements ICallParser { + public parse(call: DappStakingCallData): UserEvent { + const EVENT_NAME = 'Reward'; + const PALLET_NAME = 'DappsStaking'; + + let reward = BigInt(0); + for (const event of call.extrinsic.events) { + if (event.eventName === EVENT_NAME && event.palletName === PALLET_NAME) { + reward += BigInt(event.argsStr[4]); + } + } + + // If reward is 0 this means that batch call was not claim call and we should not return it. + if (reward === BigInt(0)) { + throw new Error("Batch doesn't contain claim calls"); + } + + return { + timestamp: new Date(call.timestamp).getTime(), + transaction: EVENT_NAME, + transactionHash: call.extrinsicHash, + transactionSuccess: call.success, + amount: reward.toString(), + }; + } +} diff --git a/src/services/GiantSquid/ResponseData.ts b/src/services/GiantSquid/ResponseData.ts index f9025c1..27635f8 100644 --- a/src/services/GiantSquid/ResponseData.ts +++ b/src/services/GiantSquid/ResponseData.ts @@ -10,4 +10,15 @@ export interface DappStakingCallData { extrinsicHash: string; success: boolean; timestamp: string; + extrinsic: { + events: DappStakingEvent[]; + }; +} + +export interface DappStakingEvent { + argsStr: string[]; + eventName: string; + palletName: string; + extrinsicHash: string; + timestamp: string; } diff --git a/src/services/GiantSquidService.ts b/src/services/GiantSquidService.ts index 55e41b6..56685db 100644 --- a/src/services/GiantSquidService.ts +++ b/src/services/GiantSquidService.ts @@ -3,6 +3,7 @@ import axios from 'axios'; import { NetworkType } from '../networks'; import { Guard } from '../guard'; import { decodeAddress } from '@polkadot/util-crypto'; +import { u8aToHex } from '@polkadot/util'; import { PeriodType, ServiceBase } from './ServiceBase'; import { UserEvent } from '../models/DappStaking'; import { DappStakingCallData, DappStakingCallResponse } from './GiantSquid/ResponseData'; @@ -25,24 +26,41 @@ export class GiantSquidService extends ServiceBase implements IGiantSquidService return []; } - const privateKey = `0x${Buffer.from(decodeAddress(address)).toString('hex')}`; + const publicKey = u8aToHex(decodeAddress(address)); 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 - } - }`; + calls(where: { + callerPublicKey_eq: "${publicKey}", + palletName_eq: "DappsStaking", + timestamp_gte: "${range.start.toISOString()}", + timestamp_lte: "${range.end.toISOString()}", + OR: { + callerPublicKey_eq: "${publicKey}", + callName_contains: "batch", + palletName_eq: "Utility", + timestamp_gte: "${range.start.toISOString()}", + timestamp_lte: "${range.end.toISOString()}", + }, + callName_not_contains: "claim" + }, orderBy: block_timestamp_DESC) { + callName + argsStr + palletName + success + timestamp + extrinsicHash + extrinsic { + events { + argsStr + eventName + palletName + extrinsicHash + timestamp + } + } + } + }`; const result = await axios.post(this.getApiUrl(network), { operationName: 'MyQuery', @@ -62,7 +80,12 @@ export class GiantSquidService extends ServiceBase implements IGiantSquidService for (const call of calls) { if (CallNameMapping[call.callName]) { const parser = container.get(CallNameMapping[call.callName]); - result.push(parser.parse(call)); + try { + result.push(parser.parse(call)); + } catch (e) { + console.log(e); + // Nothing special to do here. Batch call parser raised an error because batch the call doesn't contain claim calls. + } } else { // Call is not supported. Do nothing. Currently only calls defined in CallNameMapping are supported. }