@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
242 lines • 9.73 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 });
exports.sanitizeNumbers = sanitizeNumbers;
const types_1 = require("@polkadot/types");
const types_codec_1 = require("@polkadot/types-codec");
const util_1 = require("@polkadot/util");
const bn_js_1 = __importDefault(require("bn.js"));
const http_errors_1 = require("http-errors");
const polkadot_js_1 = require("../types/polkadot-js");
/**
* Forcibly serialize all instances of AbstractInt to base 10. With Codec
* based types we can provide a strong guarantee that the output will be of AnyJson
*
* @param value a type that implements polkadot-js Codec
* @param options - set of options specific to sanitization
*/
function sanitizeCodec(value, options = {}) {
// If objects have an overlapping prototype chain
// we check lower down the chain first. More specific before less specific.
if (value instanceof types_1.Option) {
return value.isSome ? sanitizeNumbers(value.unwrap(), options) : null;
}
if (value instanceof types_1.Compact) {
return sanitizeNumbers(value.unwrap(), options);
}
if (value instanceof types_1.Struct) {
return value.defKeys.reduce((jsonStruct, key) => {
const property = value.get(key);
if (!property) {
return jsonStruct;
}
jsonStruct[key] = sanitizeNumbers(property, options);
/**
* If the data we are sanitizing is metadata, ex: `/runtime/metadata`,
* we want to sanitize all exceptions that arent caught using `sanitizeNumbers`
*/
if (options === null || options === void 0 ? void 0 : options.metadataOpts) {
sanitizeMetadataExceptions(key, jsonStruct, property, options.metadataOpts);
}
return jsonStruct;
}, {});
}
if (value instanceof types_codec_1.Json) {
// This is essentially a Map with [keys: strings]: any
const json = {};
value.forEach((element, prop) => {
json[prop] = sanitizeNumbers(element, options);
});
return json;
}
if (value instanceof types_1.Enum) {
if (value.isBasic) {
return value.toJSON();
}
return {
// Replicating camelCaseing introduced in https://github.com/polkadot-js/api/pull/3024
// Specifically see: https://github.com/polkadot-js/api/blob/516fbd4a90652841d4e81636e74ca472e2dc5621/packages/types/src/codec/Enum.ts#L346
[(0, util_1.stringCamelCase)(value.type)]: sanitizeNumbers(value.value, options),
};
}
if (value instanceof types_1.BTreeSet) {
const jsonSet = [];
value.forEach((element) => {
jsonSet.push(sanitizeNumbers(element, options));
});
return jsonSet;
}
if (value instanceof types_1.Set) {
// CodecSet is essentially just a JS Set<string>
return value.strings;
}
// Should cover BTreeMap and HashMap
if (value instanceof types_codec_1.CodecMap) {
return mapTypeSanitizeKeyValue(value);
}
// Should cover Vec, VecAny, VecFixed, Tuple
if (value instanceof types_codec_1.AbstractArray) {
return value.map((val) => sanitizeNumbers(val, options));
}
// Should cover Uint, Int etc...
if (value instanceof types_codec_1.AbstractInt) {
return value.toString(10);
}
// All other codecs are not nested
return value.toJSON();
}
/**
* Forcibly serialize all instances of AbstractInt to base 10 and otherwise
* normalize data presentation. We try to guarantee that data is
* of type AnyJson, but it is not a strong guarantee.
*
* Under the hood AbstractInt is
* a BN.js, which has a .toString(radix) that lets us convert to base 10.
* The likely reason for the inconsistency in polkadot-js natives .toJSON
* is that over a certain value some Int like types have a flag that tells
* them to serialize to Hex.
*
* @param data - any arbitrary data that Sidecar might send
* @param options - set of options specific to sanitization
*/
function sanitizeNumbers(data, options = {}) {
if (data !== 0 && !data) {
// All falsy values are valid AnyJson, but we want to force numbers to strings
return data;
}
if ((0, polkadot_js_1.isCodec)(data)) {
return sanitizeCodec(data, options);
}
if (data instanceof Set) {
const jsonSet = [];
for (const element of data) {
jsonSet.push(sanitizeNumbers(element, options));
}
return jsonSet;
}
if (data instanceof Map) {
return mapTypeSanitizeKeyValue(data, options);
}
if (data instanceof bn_js_1.default || typeof data === 'number') {
return data.toString(10);
}
if (Array.isArray(data)) {
return data.map((val) => sanitizeNumbers(val, options));
}
if ((0, polkadot_js_1.isToJSONable)(data)) {
// Handle non-codec types that have their own toJSON
return sanitizeNumbers(data.toJSON(), options);
}
// Pretty much everything non-primitive is an object, so we need to check this last
if ((0, util_1.isObject)(data)) {
return Object.entries(data).reduce((sanitizedObject, [key, value]) => {
sanitizedObject[key] = sanitizeNumbers(value, options);
return sanitizedObject;
}, {});
}
if (!(0, polkadot_js_1.isAnyJson)(data)) {
// TODO this may be removed in the future
console.error('data could not be forced to `AnyJson` `sanitizeNumber`');
console.error(data);
}
return data;
}
/**
* Sanitize both the key and values of a map based type, ensuring that the key
* is either a number or string.
*
* @param map Map | CodecMap
* @param options - set of options specific to sanitization
*/
function mapTypeSanitizeKeyValue(map, options = {}) {
const jsonMap = {};
map.forEach((value, key) => {
let nonCodecKey = sanitizeNumbers(key, options);
if (typeof nonCodecKey === 'object') {
nonCodecKey = JSON.stringify(nonCodecKey);
}
if (!(typeof nonCodecKey === 'string' || typeof nonCodecKey === 'number')) {
throw new http_errors_1.InternalServerError('Unexpected non-string and non-number key while sanitizing a Map-like type');
}
jsonMap[nonCodecKey] = sanitizeNumbers(value, options);
});
return jsonMap;
}
/**
* Based on the metadata version, we ensure arbitrary exceptions are sanitized
* properly.
*
* @param key Current key of an object
* @param struct Current struct being sanitized
* @param property Current value of the inputted key
* @param metadataOpts metadata specific options
*/
function sanitizeMetadataExceptions(key, struct, property, metadataOpts) {
switch (metadataOpts.version) {
case 14:
sanitizeMetadataExceptionsV14(key, struct, property, metadataOpts);
break;
default:
break;
}
}
/**
* When v14 metadata is being sanitized, we ensure arbitrary exceptions are sanitized
* properly.
*
* @param key Current key of an object
* @param struct Current struct being sanitized
* @param property Current value of the inputted key
* @param metadataOpts metadata specific options
*/
function sanitizeMetadataExceptionsV14(key, struct, property, metadataOpts) {
const { registry } = metadataOpts;
const integerTypes = ['u128', 'u64', 'u32', 'u16', 'u8'];
const value = struct[key];
/**
* With V14 metadata the only key that is named 'value' lives inside of the
* pallets key. The expected structure containing `{ type, value }`.
*/
if (key === 'value' && property instanceof types_codec_1.Bytes && (0, util_1.isHex)(value)) {
const u8aValue = (0, util_1.hexToU8a)(value);
/**
* Get the lookup typedef. It is safe to assume that we have the struct
* `type` field when `key === value` is true.
*/
const typeDef = registry.lookup.getTypeDef(parseFloat(struct.type));
/**
* Checks u128, u64, u32, u16, u8
*/
if (integerTypes.includes(typeDef.type)) {
struct[key] = (0, util_1.u8aToBn)(u8aValue.subarray(0, u8aValue.byteLength), {
isLe: true,
}).toString(10);
}
/**
* The value is not an integer, and needs to be converted to its
* correct type, then transformed to JSON.
*/
if ((0, util_1.isHex)(struct[key]) && (typeDef.lookupName || typeDef.type)) {
const typeName = typeDef.lookupName || typeDef.type;
struct[key] = sanitizeNumbers(registry.createType(typeName, u8aValue).toJSON(), { metadataOpts });
}
}
}
//# sourceMappingURL=sanitizeNumbers.js.map