Newer
Older
import { ApiPromise } from '@polkadot/api';
import { Option } from '@polkadot/types';
import { RequestHandler } from 'express';
import { BadRequest, InternalServerError } from 'http-errors';
import { validateAddress } from '../../middleware';
import { AccountsStakingPayoutsService } from '../../services';
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { IAddressParam } from '../../types/requests';
import AbstractController from '../AbstractController';
/**
* GET payout information for a stash account.
*
* Path params:
* - `address`: SS58 address of the account. Must be a _Stash_ account.
*
* Query params:
* - (Optional) `depth`: The number of eras to query for payouts of. Must be less
* than `HISTORY_DEPTH`. In cases where `era - (depth -1)` is less
* than 0, the first era queried will be 0. Defaults to 1.
* - (Optional) `era`: The era to query at. Max era payout info is available for
* is the latest finished era: `active_era - 1`. Defaults to `active_era - 1`.
* - (Optional) `unclaimedOnly`: Only return unclaimed rewards. Defaults to true.
*
* Returns:
* - `at`:
* - `hash`: The block's hash.
* - `height`: The block's height.
* - `eraPayouts`: array of
* - `era`: Era this information is associated with.
* - `totalEraRewardPoints`: Total reward points for the era.
* - `totalEraPayout`: Total payout for the era. Validators split the payout
* based on the portion of `totalEraRewardPoints` they have.
* - `payouts`: array of
* - `validatorId`: AccountId of the validator the payout is coming from.
* - `nominatorStakingPayout`: Payout for the reward destination associated with the
* accountId the query was made for.
* - `claimed`: Whether or not the reward has been claimed.
* - `totalValidatorRewardPoints`: Number of reward points earned by the validator.
* - `validatorCommission`: The percentage of the total payout that the validator
* takes as commission, expressed as a Perbill.
* - `totalValidatorExposure`: The sum of the validator's and its nominators' stake.
* - `nominatorExposure`: The amount of stake the nominator has behind the validator.
*
* Description:
* Returns payout information for the last specified eras. If specifying both
* the depth and era query params, this endpoint will return information for
* (era - depth) through era. (i.e. if depth=5 and era=20 information will be
* returned for eras 16 through 20). N.B. You cannot query eras less then
* `current_era - HISTORY_DEPTH`.
*
* N.B. The `nominator*` fields correspond to the address being queried, even if it
* is a validator's _stash_ address. This is because a validator is technically
* nominating itself.
*
* `payouts` Is an array of payouts for a nominating stash address and information
* about the validator they were nominating. `eraPayouts` contains an array of
* objects that has staking reward metadata for each era, and an array of the
* aformentioned payouts.
*
*/
export default class AccountsStakingPayoutsController extends AbstractController<
AccountsStakingPayoutsService
> {
constructor(api: ApiPromise) {
super(
api,
'/accounts/:address/staking-payouts',
new AccountsStakingPayoutsService(api)
);
this.initRoutes();
}
protected initRoutes(): void {
this.router.use(this.path, validateAddress);
this.safeMountAsyncGetHandlers([
['', this.getStakingPayoutsByAccountId],
]);
}
/**
* Get the payouts of `address` for `depth` starting from the `era`.
*
* @param req Express Request
* @param res Express Response
*/
private getStakingPayoutsByAccountId: RequestHandler<
IAddressParam
> = async (
{ params: { address }, query: { depth, era, unclaimedOnly } },
res
): Promise<void> => {
const { hash, eraArg, currentEra } = await this.getEraAndHash(
this.verifyAndCastOr('era', era, undefined)
);
const unclaimedOnlyArg = unclaimedOnly === 'false' ? false : true;
AccountsStakingPayoutsController.sanitizedSend(
res,
await this.service.fetchAccountStakingPayout(
hash,
address,
this.verifyAndCastOr('depth', depth, 1) as number,
eraArg,
unclaimedOnlyArg,
currentEra
)
);
};
private async getEraAndHash(era?: number) {
const [
hash,
activeEraOption,
currentEraMaybeOption,
] = await Promise.all([
this.api.rpc.chain.getFinalizedHead(),
this.api.query.staking.activeEra(),
this.api.query.staking.currentEra(),
]);
if (activeEraOption.isNone) {
throw new InternalServerError(
'ActiveEra is None when Some was expected'
);
}
const activeEra = activeEraOption.unwrap().index.toNumber();
if (currentEraMaybeOption instanceof Option) {
if (currentEraMaybeOption.isNone) {
throw new InternalServerError(
'CurrentEra is None when Some was expected'
);
}
currentEra = currentEraMaybeOption.unwrap().toNumber();
} else if ((currentEraMaybeOption as unknown) instanceof BN) {
// EraIndex extends u32, which extends BN so this should always be true
currentEra = (currentEraMaybeOption as BN).toNumber();
} else {
throw new InternalServerError(
'Query for current_era returned a non-processable result.'
);
}
if (era !== undefined && era > activeEra - 1) {
throw new BadRequest(
`The specified era (${era}) is too large. ` +
`Largest era payout info is available for is ${
activeEra - 1
}`
);
}
return {
hash,
eraArg: era === undefined ? activeEra - 1 : era,