UNPKG

@substrate/api-sidecar

Version:

REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.

245 lines 13.1 kB
"use strict"; // 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. * * 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. * - (Optional) `useRcBlock`: When true, treats the `at` parameter as a relay chain block * to find corresponding Asset Hub blocks. Only supported for Asset Hub endpoints. * * Returns: * - When using `useRcBlock=true`: An array of response objects, one for each Asset Hub block found * in the specified relay chain block. Returns empty array `[]` if no Asset Hub blocks found. * - When using `useRcBlock=false` or omitted: A single response object. * * Response object structure: * - `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. * - `rcBlockNumber`: The relay chain block number used for the query. Only present when `useRcBlock=true`. * - `ahTimestamp`: The Asset Hub block timestamp. Only present when `useRcBlock=true`. * * 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. * */ class AccountsStakingPayoutsController extends AbstractController_1.default { constructor(api) { super(api, '/accounts/:address/staking-payouts', new services_1.AccountsStakingPayoutsService(api)); /** * Get the payouts of `address` for `depth` starting from the `era`. * * @param req Express Request * @param res Express Response */ this.getStakingPayoutsByAccountId = async ({ params: { address }, query: { depth, era, unclaimedOnly, at, useRcBlock } }, res) => { const { specName } = this; const isKusama = specName.toString().toLowerCase() === 'kusama'; const { isAssetHubMigrated } = apiRegistry_1.ApiPromiseRegistry.assetHubInfo; if (useRcBlock === 'true') { const rcAtResults = await this.getHashFromRcAt(at); // Return empty array if no Asset Hub blocks found if (rcAtResults.length === 0) { AccountsStakingPayoutsController.sanitizedSend(res, []); return; } // Process each Asset Hub block found const results = []; for (const { ahHash, rcBlockHash, rcBlockNumber } of rcAtResults) { let hash = ahHash; let apiAt = await this.api.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()); apiAt = await this.api.at(hash); } if (!apiAt.query.staking) { throw new http_errors_1.BadRequest('Staking pallet not found for queried runtime'); } let result; if (isAssetHubMigrated) { result = await this.service.fetchAccountStakingPayoutAssetHub(hash, address, this.verifyAndCastOr('depth', sanitizedDepth, 1), eraArg, unclaimedOnly === 'false' ? false : true, currentEra, apiAt); } else { result = await this.service.fetchAccountStakingPayout(hash, address, this.verifyAndCastOr('depth', sanitizedDepth, 1), eraArg, unclaimedOnly === 'false' ? false : true, currentEra, apiAt); } const finalApiAt = await this.api.at(hash); const ahTimestamp = await finalApiAt.query.timestamp.now(); const enhancedResult = { ...result, rcBlockHash: rcBlockHash.toString(), rcBlockNumber, ahTimestamp: ahTimestamp.toString(), }; results.push(enhancedResult); } AccountsStakingPayoutsController.sanitizedSend(res, results); } else { let hash = await this.getHashFromAt(at); let apiAt = await this.api.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()); apiAt = await this.api.at(hash); } if (!apiAt.query.staking) { throw new http_errors_1.BadRequest('Staking pallet not found for queried runtime'); } let result; if (isAssetHubMigrated) { result = await this.service.fetchAccountStakingPayoutAssetHub(hash, address, this.verifyAndCastOr('depth', sanitizedDepth, 1), eraArg, unclaimedOnly === 'false' ? false : true, currentEra, apiAt); } else { result = await this.service.fetchAccountStakingPayout(hash, address, this.verifyAndCastOr('depth', sanitizedDepth, 1), eraArg, unclaimedOnly === 'false' ? false : true, currentEra, apiAt); } AccountsStakingPayoutsController.sanitizedSend(res, result); } }; this.initRoutes(); } initRoutes() { this.router.use(this.path, middleware_1.validateAddress, (0, middleware_1.validateBoolean)(['unclaimedOnly']), middleware_1.validateUseRcBlock); 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(); } } AccountsStakingPayoutsController.controllerName = 'AccountsStakingPayouts'; AccountsStakingPayoutsController.requiredPallets = [ ['Staking', 'System'], ['ParachainStaking', 'ParachainSystem'], ['ParachainStaking', 'System'], ['Staking', 'ParachainSystem'], ]; exports.default = AccountsStakingPayoutsController; //# sourceMappingURL=AccountsStakingPayoutsController.js.map