@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
192 lines • 10 kB
JavaScript
;
// Copyright 2017-2025 Parity Technologies (UK) Ltd.
// This file is part of Substrate API Sidecar.
//
// Substrate API Sidecar is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("@polkadot/types");
const bn_js_1 = __importDefault(require("bn.js"));
const http_errors_1 = require("http-errors");
const apiRegistry_1 = require("../../../apiRegistry");
const middleware_1 = require("../../../middleware");
const services_1 = require("../../../services");
const kusamaEarlyErasBlockInfo_json_1 = __importDefault(require("../../../services/accounts/kusamaEarlyErasBlockInfo.json"));
const AbstractController_1 = __importDefault(require("../../AbstractController"));
/**
* GET payout information for a stash account on the relay chain.
*
* 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 on the relay chain. 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.
*/
class RcAccountsStakingPayoutsController extends AbstractController_1.default {
constructor(_api) {
var _a;
const rcApiSpecName = (_a = apiRegistry_1.ApiPromiseRegistry.getSpecNameByType('relay')) === null || _a === void 0 ? void 0 : _a.values();
const rcSpecName = rcApiSpecName ? Array.from(rcApiSpecName)[0] : undefined;
if (!rcSpecName) {
throw new Error('Relay chain API spec name is not defined.');
}
super(rcSpecName, '/rc/accounts/:address/staking-payouts', new services_1.AccountsStakingPayoutsService(rcSpecName));
/**
* Get the payouts of `address` for `depth` starting from the `era` on the relay chain.
*
* @param req Express Request
* @param res Express Response
*/
this.getStakingPayoutsByAccountId = async ({ params: { address }, query: { depth, era, unclaimedOnly, at } }, res) => {
var _a;
const rcApi = (_a = apiRegistry_1.ApiPromiseRegistry.getApiByType('relay')[0]) === null || _a === void 0 ? void 0 : _a.api;
if (!rcApi) {
throw new Error('Relay chain API not found, please use SAS_SUBSTRATE_MULTI_CHAIN_URL env variable');
}
const { specName } = this;
const isKusama = specName.toString().toLowerCase() === 'kusama';
let hash = await this.getHashFromAt(at, { api: rcApi });
let apiAt = await rcApi.at(hash);
const { eraArg, currentEra } = await this.getEraAndHash(apiAt, this.verifyAndCastOr('era', era, undefined));
const sanitizedDepth = this.sanitizeDepth({
isKusama,
depth: depth === null || depth === void 0 ? void 0 : depth.toString(),
era: era === null || era === void 0 ? void 0 : era.toString(),
currentEra,
});
if (isKusama && currentEra < 518) {
const earlyErasBlockInfo = kusamaEarlyErasBlockInfo_json_1.default;
const block = earlyErasBlockInfo[currentEra].start;
hash = await this.getHashFromAt(block.toString(), { api: rcApi });
apiAt = await rcApi.at(hash);
}
if (!apiAt.query.staking) {
throw new http_errors_1.BadRequest('Staking pallet not found for queried runtime');
}
// For RC endpoints, we always use the regular fetchAccountStakingPayout method
// since we're querying the relay chain directly
const result = await this.service.fetchAccountStakingPayout(hash, address, this.verifyAndCastOr('depth', sanitizedDepth, 1), eraArg, unclaimedOnly === 'false' ? false : true, currentEra, apiAt);
RcAccountsStakingPayoutsController.sanitizedSend(res, result);
};
this.initRoutes();
}
initRoutes() {
this.router.use(this.path, middleware_1.validateAddress, (0, middleware_1.validateBoolean)(['unclaimedOnly']));
this.safeMountAsyncGetHandlers([['', this.getStakingPayoutsByAccountId]]);
}
sanitizeDepth({ isKusama, depth, era, currentEra, }) {
if (!isKusama)
return depth;
if (currentEra <= 519) {
if (depth !== undefined)
throw new http_errors_1.InternalServerError('The `depth` query parameter is disabled for eras less than 518.');
if (era !== undefined)
throw new http_errors_1.InternalServerError('The `era` query parameter is disabled for eras less than 518.');
return undefined;
}
if (depth) {
return Math.min(Number(depth), currentEra - 518).toString();
}
return undefined;
}
async getEraAndHash(apiAt, era) {
const currentEra = await this.getCurrentEra(apiAt);
const activeEra = await this.getActiveEra(apiAt, currentEra);
if (era !== undefined && era > activeEra - 1) {
throw new http_errors_1.BadRequest(`The specified era (${era}) is too large. Largest era payout info is available for is ${activeEra - 1}`);
}
return {
eraArg: era !== null && era !== void 0 ? era : activeEra - 1,
currentEra,
};
}
async getCurrentEra(apiAt) {
const currentEraMaybe = (await apiAt.query.staking.currentEra());
if (currentEraMaybe instanceof types_1.Option) {
if (currentEraMaybe.isNone)
throw new http_errors_1.InternalServerError('CurrentEra is None when Some was expected');
return currentEraMaybe.unwrap().toNumber();
}
if (currentEraMaybe instanceof bn_js_1.default) {
return currentEraMaybe.toNumber();
}
throw new http_errors_1.InternalServerError('Query for current_era returned a non-processable result.');
}
async getActiveEra(apiAt, currentEra) {
if (!apiAt.query.staking.activeEra) {
const sessionIndex = (await apiAt.query.session.currentIndex()).toNumber();
return currentEra < 518 || sessionIndex % 6 > 0 ? currentEra : currentEra - 1;
}
const activeEraOption = await apiAt.query.staking.activeEra();
if (activeEraOption.isNone) {
const historicActiveEra = await apiAt.query.staking.currentEra();
if (historicActiveEra.isNone)
throw new http_errors_1.InternalServerError('ActiveEra is None when Some was expected');
return historicActiveEra.unwrap().toNumber();
}
return activeEraOption.unwrap().index.toNumber();
}
}
RcAccountsStakingPayoutsController.controllerName = 'RcAccountsStakingPayouts';
RcAccountsStakingPayoutsController.requiredPallets = [
['Staking', 'System'],
['ParachainStaking', 'ParachainSystem'],
['ParachainStaking', 'System'],
['Staking', 'ParachainSystem'],
];
exports.default = RcAccountsStakingPayoutsController;
//# sourceMappingURL=RcAccountsStakingPayoutsController.js.map