Skip to content

Commit

Permalink
Add rewards transactions to dapp staking history (#106)
Browse files Browse the repository at this point in the history
* Basic Squid service

* Call parsers implementation

* swagger.json fix

* PR comments fixes

* Added rewards to user transactions history

* Linter fix

* Better batch call handling

* Linter fixes

* Improved address to public key conversion

* linted

* eslint changed its mind

---------

Co-authored-by: Gregory Luneau <[email protected]>
  • Loading branch information
bobo-k2 and gluneau authored Aug 31, 2023
1 parent 85e1ad1 commit c83d54f
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 31 deletions.
3 changes: 2 additions & 1 deletion src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -125,6 +125,7 @@ container
.bind<ICallParser>(CallNameMapping.withdraw_from_unregistered)
.to(WithdrawFromUnbondedParser)
.inSingletonScope();
container.bind<ICallParser>(CallNameMapping.batch).to(BatchCallParser).inSingletonScope();

// controllers registration
container.bind<IControllerBase>(ContainerTypes.Controller).to(TokenStatsController);
Expand Down
36 changes: 22 additions & 14 deletions src/controllers/DappsStakingController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
},
);

Expand Down Expand Up @@ -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);
}
},
);

Expand Down
1 change: 1 addition & 0 deletions src/services/GiantSquid/CallNameMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export const CallNameMapping: Map = {
nomination_transfer: 'NominationTransfer',
withdraw_unbonded: 'Withdraw',
withdraw_from_unregistered: 'WithdrawFromUnregistered',
batch: 'Batch',
};
29 changes: 29 additions & 0 deletions src/services/GiantSquid/CallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
};
}
}
11 changes: 11 additions & 0 deletions src/services/GiantSquid/ResponseData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
55 changes: 39 additions & 16 deletions src/services/GiantSquidService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<DappStakingCallResponse>(this.getApiUrl(network), {
operationName: 'MyQuery',
Expand All @@ -62,7 +80,12 @@ export class GiantSquidService extends ServiceBase implements IGiantSquidService
for (const call of calls) {
if (CallNameMapping[call.callName]) {
const parser = container.get<ICallParser>(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.
}
Expand Down

0 comments on commit c83d54f

Please sign in to comment.