diff --git a/packages/next-common/components/preImages/desktop.js b/packages/next-common/components/preImages/desktop.js index 4ebc49fd0b..9e0da26e9d 100644 --- a/packages/next-common/components/preImages/desktop.js +++ b/packages/next-common/components/preImages/desktop.js @@ -1,10 +1,10 @@ import useColumns from "next-common/components/styledList/useColumns"; import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard"; import { useState } from "react"; -import useOldPreimage from "next-common/hooks/useOldPreimage"; -import useOldPreimagePapi from "next-common/hooks/useOldPreimagePapi"; -import usePreimage from "next-common/hooks/usePreimage"; -import usePreimagePapi from "next-common/hooks/usePreimagePapi"; +import useOldPreimage from "next-common/hooks/useOldPreimageNew"; +import useOldPreimagePapi from "next-common/hooks/useOldPreimagePapiNew"; +import usePreimage from "next-common/hooks/usePreimageNew"; +import usePreimagePapi from "next-common/hooks/usePreimagePapiNew"; import { useChainSettings } from "next-common/context/chain"; import { useDispatch, useSelector } from "react-redux"; import { diff --git a/packages/next-common/components/preImages/mobile.js b/packages/next-common/components/preImages/mobile.js index a0acd60c80..bf7674799d 100644 --- a/packages/next-common/components/preImages/mobile.js +++ b/packages/next-common/components/preImages/mobile.js @@ -1,9 +1,9 @@ import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard"; import React, { useState, useEffect, useRef } from "react"; -import usePreimage from "next-common/hooks/usePreimage"; -import usePreimagePapi from "next-common/hooks/usePreimagePapi"; -import useOldPreimage from "next-common/hooks/useOldPreimage"; -import useOldPreimagePapi from "next-common/hooks/useOldPreimagePapi"; +import usePreimage from "next-common/hooks/usePreimageNew"; +import usePreimagePapi from "next-common/hooks/usePreimagePapiNew"; +import useOldPreimage from "next-common/hooks/useOldPreimageNew"; +import useOldPreimagePapi from "next-common/hooks/useOldPreimagePapiNew"; import { useChainSettings } from "next-common/context/chain"; import { useDispatch } from "react-redux"; import { incPreImagesTrigger } from "next-common/store/reducers/preImagesSlice"; diff --git a/packages/next-common/hooks/common/useSubStorage.js b/packages/next-common/hooks/common/useSubStorage.js index b6babd219e..b15feb933a 100644 --- a/packages/next-common/hooks/common/useSubStorage.js +++ b/packages/next-common/hooks/common/useSubStorage.js @@ -151,7 +151,7 @@ export default function useSubStorage( const paramsKey = normalizedParams .map((p) => { - if (p === null || p === undefined) return "null"; + if (isNil(p)) return "null"; if (typeof p === "object") { try { return JSON.stringify(p); diff --git a/packages/next-common/hooks/useOldPreimageCommon.js b/packages/next-common/hooks/useOldPreimageCommon.js index d7fa844d5a..4dc8d7ebaf 100644 --- a/packages/next-common/hooks/useOldPreimageCommon.js +++ b/packages/next-common/hooks/useOldPreimageCommon.js @@ -103,7 +103,7 @@ function unwrapMaybeValue(value) { export function toPreimageLength(value) { const unwrappedValue = unwrapMaybeValue(value); - if (unwrappedValue === null || unwrappedValue === undefined) { + if (isNil(unwrappedValue)) { return null; } @@ -116,7 +116,7 @@ export function toPreimageLength(value) { export function toPreimageCount(value) { const unwrappedValue = unwrapMaybeValue(value); - if (unwrappedValue === null || unwrappedValue === undefined) { + if (isNil(unwrappedValue)) { return undefined; } diff --git a/packages/next-common/hooks/useOldPreimageNew.js b/packages/next-common/hooks/useOldPreimageNew.js new file mode 100644 index 0000000000..1390f36f59 --- /dev/null +++ b/packages/next-common/hooks/useOldPreimageNew.js @@ -0,0 +1,127 @@ +import { useAsync } from "react-use"; +import { useContextApi } from "next-common/context/api"; +import { BN_ZERO } from "@polkadot/util"; +import { Option } from "@polkadot/types"; +import { + buildNoBytesResult, + buildBaseResult, + buildCompletedResult, + buildPreimageForKey, + decodeCallBytes, + extractCallData, + isHashOnlyStorageKey, + parseHashOrBounded, + resolveInlinePreimage, +} from "./usePreimageNewCommon"; + +const oldPreimageResultCache = new Map(); + +function parseDeposit(rawDeposit) { + if (!rawDeposit) return undefined; + return { who: rawDeposit[0].toString(), amount: rawDeposit[1] }; +} + +function parseStatusFor(optStatus) { + const status = optStatus.unwrapOr(null); + if (!status) return { status: null }; + + if (status.isRequested) { + const req = status.asRequested; + // Older versions: asRequested is an option with no structured fields. + if (req instanceof Option) { + return { status, statusName: "requested" }; + } + return { + status, + statusName: "requested", + count: req.count.toNumber(), + deposit: parseDeposit(req.deposit.unwrapOr(null)), + proposalLength: req.len.unwrapOr(BN_ZERO), + }; + } + + if (status.isUnrequested) { + const unreq = status.asUnrequested; + // Older versions, asUnrequested is an option. + if (unreq instanceof Option) { + return { + status, + statusName: "unrequested", + deposit: parseDeposit(unreq.unwrapOr(null)), + }; + } + return { + status, + statusName: "unrequested", + deposit: parseDeposit(unreq.deposit), + proposalLength: unreq.len, + }; + } + + console.error(`Unhandled PalletPreimageRequestStatus type: ${status.type}`); + return { status }; +} + +async function fetchOldPreimage(proposalHash, api, hashOnly) { + const base = buildBaseResult(proposalHash, hashOnly, api.registry, null); + + if (!api.query.preimage?.statusFor) return base; + + const optStatus = await api.query.preimage.statusFor(proposalHash); + const parsedStatus = parseStatusFor(optStatus); + const withStatus = { ...base, ...parsedStatus }; + + if (!parsedStatus.status) return withStatus; + + if (!api.query.preimage?.preimageFor) return withStatus; + + const bytesKey = buildPreimageForKey( + proposalHash, + withStatus.proposalLength, + hashOnly, + ); + const optBytes = await api.query.preimage.preimageFor(...bytesKey); + const callData = extractCallData(optBytes); + + if (!callData) return buildNoBytesResult(withStatus); + + const decoded = decodeCallBytes( + callData, + api.registry, + withStatus.proposalLength, + ); + return buildCompletedResult(withStatus, decoded); +} + +export default function useOldPreimage(hashOrBounded) { + const api = useContextApi(); + + const { value, loading } = useAsync(async () => { + if (!hashOrBounded || !api) return null; + + const { proposalHash, inlineData } = parseHashOrBounded(hashOrBounded, api); + if (!proposalHash) return null; + + if (oldPreimageResultCache.has(proposalHash)) { + return oldPreimageResultCache.get(proposalHash); + } + + const hashOnly = isHashOnlyStorageKey(api); + + const result = inlineData + ? resolveInlinePreimage(proposalHash, hashOnly, api, inlineData) + : await fetchOldPreimage(proposalHash, api, hashOnly); + + if (result?.isCompleted) { + oldPreimageResultCache.set(proposalHash, result); + } + + return result; + }, [hashOrBounded, api]); + + const isStatusLoaded = Boolean(api) && !loading; + const isPreimageLoaded = + Boolean(api) && !loading && Boolean(value?.isCompleted); + + return [value ?? {}, isStatusLoaded, isPreimageLoaded]; +} diff --git a/packages/next-common/hooks/useOldPreimagePapiNew.js b/packages/next-common/hooks/useOldPreimagePapiNew.js new file mode 100644 index 0000000000..2c2b5f9b96 --- /dev/null +++ b/packages/next-common/hooks/useOldPreimagePapiNew.js @@ -0,0 +1,111 @@ +import { useAsync } from "react-use"; +import { Binary } from "polkadot-api"; +import { useContextPapi } from "next-common/context/papi"; +import { + fetchPapiPreimageBytes, + decodePreimageWithPapi, + toPreimageCount, + toPreimageLength, + convertPapiDepositTuple, + getPapiStatusName, +} from "./useOldPreimageCommon"; +import { + parsePapiHashOrBounded, + buildNoBytesResult, + buildBasePapiResult, + resolveInlinePapiPreimage, +} from "./usePreimageNewCommon"; + +const oldPapiPreimageResultCache = new Map(); + +function parsePapiStatusFor(rawStatus) { + if (!rawStatus) return { status: null }; + + const statusName = getPapiStatusName(rawStatus); + const { type, value = {} } = rawStatus; + const result = { status: rawStatus, statusName }; + + if (type === "Requested") { + result.count = toPreimageCount(value.count); + result.deposit = convertPapiDepositTuple(value.deposit); + result.proposalLength = toPreimageLength(value.len); + } else if (type === "Unrequested") { + result.deposit = convertPapiDepositTuple(value.deposit); + result.proposalLength = toPreimageLength(value.len); + } else { + console.error(`Unhandled PAPI Preimage.StatusFor type: ${type}`); + } + + return result; +} + +async function fetchOldPapiPreimage(proposalHash, papi, client) { + const base = buildBasePapiResult(proposalHash, null); + + if (!papi.query?.Preimage?.StatusFor) return base; + + const rawStatus = await papi.query.Preimage.StatusFor.getValue( + Binary.fromHex(proposalHash), + ); + + const parsedStatus = parsePapiStatusFor(rawStatus); + const withStatus = { ...base, ...parsedStatus }; + + if (!parsedStatus.status) return withStatus; + + const bytes = await fetchPapiPreimageBytes( + papi, + proposalHash, + withStatus.proposalLength, + ); + + if (!bytes) return buildNoBytesResult(withStatus); + + let decoded; + try { + decoded = await decodePreimageWithPapi(withStatus, bytes, client); + } catch { + // ignore + } + + if (!decoded) { + return { + ...withStatus, + isCompleted: true, + proposalError: "Unable to decode preimage bytes into a valid Call", + }; + } + + return decoded; +} + +export default function useOldPreimagePapi(hashOrBounded) { + const { api: papi, client } = useContextPapi(); + + const { value, loading } = useAsync(async () => { + if (!hashOrBounded || !papi || !client) return null; + + const { proposalHash, inlineData } = parsePapiHashOrBounded(hashOrBounded); + if (!proposalHash) return null; + + if (oldPapiPreimageResultCache.has(proposalHash)) { + return oldPapiPreimageResultCache.get(proposalHash); + } + + const result = inlineData + ? await resolveInlinePapiPreimage(proposalHash, inlineData, client) + : await fetchOldPapiPreimage(proposalHash, papi, client); + + if (result?.isCompleted) { + oldPapiPreimageResultCache.set(proposalHash, result); + } + + return result; + }, [hashOrBounded, papi, client]); + + const isStatusLoaded = Boolean(papi) && Boolean(client) && !loading; + const isPreimageLoaded = + Boolean(papi) && Boolean(client) && !loading && Boolean(value?.isCompleted); + + return [value ?? {}, isStatusLoaded, isPreimageLoaded]; +} diff --git a/packages/next-common/hooks/usePreimageNew.js b/packages/next-common/hooks/usePreimageNew.js new file mode 100644 index 0000000000..794dcb7452 --- /dev/null +++ b/packages/next-common/hooks/usePreimageNew.js @@ -0,0 +1,127 @@ +import { useAsync } from "react-use"; +import { useContextApi } from "next-common/context/api"; +import { BN_ZERO } from "@polkadot/util"; +import { Option } from "@polkadot/types"; +import { + buildNoBytesResult, + buildBaseResult, + buildCompletedResult, + buildPreimageForKey, + decodeCallBytes, + extractCallData, + isHashOnlyStorageKey, + parseHashOrBounded, + resolveInlinePreimage, +} from "./usePreimageNewCommon"; + +const preimageResultCache = new Map(); + +function parseTicket(rawTicket) { + if (!rawTicket) return undefined; + return { who: rawTicket[0].toString(), amount: rawTicket[1] }; +} + +function parseRequestStatus(optStatus) { + const status = optStatus.unwrapOr(null); + if (!status) return { status: null }; + + if (status.isRequested) { + const req = status.asRequested; + // Older versions: asRequested is an option with no structured fields. + if (req instanceof Option) { + return { status, statusName: "requested" }; + } + return { + status, + statusName: "requested", + count: req.count.toNumber(), + ticket: parseTicket(req.maybeTicket.unwrapOr(null)), + proposalLength: req.maybeLen.unwrapOr(BN_ZERO), + }; + } + + if (status.isUnrequested) { + const unreq = status.asUnrequested; + // Older versions, asUnrequested is an option. + if (unreq instanceof Option) { + return { + status, + statusName: "unrequested", + ticket: parseTicket(unreq.unwrapOr(null)), + }; + } + return { + status, + statusName: "unrequested", + ticket: parseTicket(unreq.ticket), + proposalLength: unreq.len, + }; + } + + console.error(`Unhandled PalletPreimageRequestStatus type: ${status.type}`); + return { status }; +} + +async function fetchPreimage(proposalHash, api, hashOnly) { + const base = buildBaseResult(proposalHash, hashOnly, api.registry, null); + + if (!api.query.preimage?.requestStatusFor) return base; + + const optStatus = await api.query.preimage.requestStatusFor(proposalHash); + const parsedStatus = parseRequestStatus(optStatus); + const withStatus = { ...base, ...parsedStatus }; + + if (!parsedStatus.status) return withStatus; + + if (!api.query.preimage?.preimageFor) return withStatus; + + const bytesKey = buildPreimageForKey( + proposalHash, + withStatus.proposalLength, + hashOnly, + ); + const optBytes = await api.query.preimage.preimageFor(...bytesKey); + const callData = extractCallData(optBytes); + + if (!callData) return buildNoBytesResult(withStatus); + + const decoded = decodeCallBytes( + callData, + api.registry, + withStatus.proposalLength, + ); + return buildCompletedResult(withStatus, decoded); +} + +export default function usePreimage(hashOrBounded) { + const api = useContextApi(); + + const { value, loading } = useAsync(async () => { + if (!hashOrBounded || !api) return null; + + const { proposalHash, inlineData } = parseHashOrBounded(hashOrBounded, api); + if (!proposalHash) return null; + + if (preimageResultCache.has(proposalHash)) { + return preimageResultCache.get(proposalHash); + } + + const hashOnly = isHashOnlyStorageKey(api); + + const result = inlineData + ? resolveInlinePreimage(proposalHash, hashOnly, api, inlineData) + : await fetchPreimage(proposalHash, api, hashOnly); + + if (result?.isCompleted) { + preimageResultCache.set(proposalHash, result); + } + + return result; + }, [hashOrBounded, api]); + + const isStatusLoaded = Boolean(api) && !loading; + const isPreimageLoaded = + Boolean(api) && !loading && Boolean(value?.isCompleted); + + return [value ?? {}, isStatusLoaded, isPreimageLoaded]; +} diff --git a/packages/next-common/hooks/usePreimageNewCommon.js b/packages/next-common/hooks/usePreimageNewCommon.js new file mode 100644 index 0000000000..66b9879d5b --- /dev/null +++ b/packages/next-common/hooks/usePreimageNewCommon.js @@ -0,0 +1,222 @@ +import { + BN, + BN_ZERO, + formatNumber, + isString, + isU8a, + u8aToHex, +} from "@polkadot/util"; +import { blake2AsHex } from "@polkadot/util-crypto"; +import { decodePreimageWithPapi } from "./useOldPreimageCommon"; + +export function parsePapiHashOrBounded(hashOrBounded) { + if (isString(hashOrBounded)) { + return { proposalHash: hashOrBounded }; + } + + if (isU8a(hashOrBounded)) { + return { proposalHash: hashOrBounded.toHex() }; + } + + if (hashOrBounded.isInline) { + const inlineData = hashOrBounded.asInline.toU8a(true); + const proposalHash = blake2AsHex(inlineData); + return { proposalHash, inlineData }; + } + + if (hashOrBounded.isLegacy) { + return { proposalHash: hashOrBounded.asLegacy.hash_.toHex() }; + } + + if (hashOrBounded.isLookup) { + return { proposalHash: hashOrBounded.asLookup.hash_.toHex() }; + } + + console.error( + `Unhandled FrameSupportPreimagesBounded type: ${hashOrBounded.type}`, + ); + return {}; +} + +export function buildNoBytesResult(base) { + return { + ...base, + isCompleted: true, + proposal: null, + proposalError: null, + proposalWarning: "No preimage bytes found", + proposalLength: base.proposalLength || BN_ZERO, + }; +} + +export function buildBasePapiResult(proposalHash, inlineData) { + return { + proposalHash, + count: 0, + isCompleted: false, + isHashParam: false, + registry: null, + status: null, + ticket: undefined, + deposit: undefined, + statusName: null, + proposalLength: inlineData ? new BN(inlineData.length) : null, + proposal: null, + proposalError: null, + proposalWarning: null, + }; +} + +export async function resolveInlinePapiPreimage( + proposalHash, + inlineData, + client, +) { + const base = buildBasePapiResult(proposalHash, inlineData); + + const decoded = await decodePreimageWithPapi(base, inlineData, client); + + if (!decoded) { + return { + ...base, + isCompleted: true, + proposalError: "Unable to decode preimage bytes into a valid Call", + }; + } + + return decoded; +} + +// ── polkadot-js shared (used by usePreimage + useOldPreimage) ──────────────── + +export function parseHashOrBounded(hashOrBounded, api) { + if (isString(hashOrBounded)) { + return { proposalHash: hashOrBounded }; + } + + if (isU8a(hashOrBounded)) { + return { proposalHash: hashOrBounded.toHex() }; + } + + if (hashOrBounded.isInline) { + const inlineData = hashOrBounded.asInline.toU8a(true); + const proposalHash = u8aToHex(api?.registry.hash(inlineData)); + return { proposalHash, inlineData }; + } + + if (hashOrBounded.isLegacy) { + return { proposalHash: hashOrBounded.asLegacy.hash_.toHex() }; + } + + if (hashOrBounded.isLookup) { + return { proposalHash: hashOrBounded.asLookup.hash_.toHex() }; + } + + console.error( + `Unhandled FrameSupportPreimagesBounded type: ${hashOrBounded.type}`, + ); + return {}; +} + +export function isHashOnlyStorageKey(api) { + if (!api?.query.preimage?.preimageFor?.creator.meta.type.isMap) { + return false; + } + const { type } = api.registry.lookup.getTypeDef( + api.query.preimage.preimageFor.creator.meta.type.asMap.key, + ); + return type === "H256"; +} + +export function buildPreimageForKey(proposalHash, proposalLength, hashOnly) { + if (hashOnly) { + return [proposalHash]; + } + return [[proposalHash, proposalLength || BN_ZERO]]; +} + +export function extractCallData(optBytes) { + if (!optBytes) return null; + + const callData = isU8a(optBytes) + ? optBytes + : optBytes.unwrapOr?.(null) ?? optBytes; + + if (!callData) return null; + if (isU8a(callData) && callData.length === 0) return null; + if (isString(callData) && callData === "0x") return null; + if (typeof callData?.toHex === "function" && callData.toHex() === "0x") + return null; + + return callData; +} + +export function decodeCallBytes(callData, registry, proposalLength) { + let proposal = null; + let proposalError = null; + let proposalWarning = null; + let resolvedLength; + + try { + proposal = registry.createType("Call", callData); + const callLength = proposal.encodedLength; + + if (proposalLength) { + const storeLength = proposalLength.toNumber(); + if (callLength !== storeLength) { + proposalWarning = `Decoded call length does not match on-chain stored preimage length (${formatNumber( + callLength, + )} bytes vs ${formatNumber(storeLength)} bytes)`; + } + } else { + resolvedLength = new BN(callLength); + } + } catch { + proposalError = "Unable to decode preimage bytes into a valid Call"; + } + + return { proposal, proposalError, proposalWarning, resolvedLength }; +} + +export function buildBaseResult(proposalHash, hashOnly, registry, inlineData) { + return { + proposalHash, + count: 0, + isCompleted: false, + isHashParam: hashOnly, + registry, + status: null, + ticket: undefined, + deposit: undefined, + statusName: null, + proposalLength: inlineData ? new BN(inlineData.length) : null, + proposal: null, + proposalError: null, + proposalWarning: null, + }; +} + +export function buildCompletedResult(base, decoded) { + return { + ...base, + isCompleted: true, + proposal: decoded.proposal ?? null, + proposalError: decoded.proposalError ?? null, + proposalWarning: decoded.proposalWarning ?? null, + proposalLength: decoded.resolvedLength ?? base.proposalLength, + }; +} + +export function resolveInlinePreimage(proposalHash, hashOnly, api, inlineData) { + const base = buildBaseResult( + proposalHash, + hashOnly, + api.registry, + inlineData, + ); + const callData = extractCallData(inlineData); + const decoded = callData + ? decodeCallBytes(callData, api.registry, base.proposalLength) + : { proposal: null, proposalError: null, proposalWarning: null }; + return buildCompletedResult(base, decoded); +} diff --git a/packages/next-common/hooks/usePreimagePapiNew.js b/packages/next-common/hooks/usePreimagePapiNew.js new file mode 100644 index 0000000000..8b87c92fe1 --- /dev/null +++ b/packages/next-common/hooks/usePreimagePapiNew.js @@ -0,0 +1,113 @@ +import { useAsync } from "react-use"; +import { Binary } from "polkadot-api"; +import { useContextPapi } from "next-common/context/papi"; +import { + fetchPapiPreimageBytes, + decodePreimageWithPapi, + toPreimageCount, + toPreimageLength, + convertPapiDepositTuple, + getPapiStatusName, +} from "./useOldPreimageCommon"; +import { + parsePapiHashOrBounded, + buildNoBytesResult, + buildBasePapiResult, + resolveInlinePapiPreimage, +} from "./usePreimageNewCommon"; + +const papiPreimageResultCache = new Map(); + +function parsePapiRequestStatus(rawStatus) { + if (!rawStatus) return { status: null }; + + const statusName = getPapiStatusName(rawStatus); // Lowercase first letter + const { type, value = {} } = rawStatus; + const result = { status: rawStatus, statusName }; + + if (type === "Requested") { + result.count = toPreimageCount(value.count); + result.ticket = convertPapiDepositTuple( + value.maybeTicket ?? value.maybe_ticket, + ); + result.proposalLength = toPreimageLength(value.maybeLen ?? value.maybe_len); + } else if (type === "Unrequested") { + result.ticket = convertPapiDepositTuple(value.ticket); + result.proposalLength = toPreimageLength(value.len); + } else { + console.error(`Unhandled PAPI Preimage.RequestStatusFor type: ${type}`); + } + + return result; +} + +async function fetchPapiPreimage(proposalHash, papi, client) { + const base = buildBasePapiResult(proposalHash, null); + + if (!papi.query?.Preimage?.RequestStatusFor) return base; + + const rawStatus = await papi.query.Preimage.RequestStatusFor.getValue( + Binary.fromHex(proposalHash), + ); + + const parsedStatus = parsePapiRequestStatus(rawStatus); + const withStatus = { ...base, ...parsedStatus }; + + if (!parsedStatus.status) return withStatus; + + const bytes = await fetchPapiPreimageBytes( + papi, + proposalHash, + withStatus.proposalLength, + ); + + if (!bytes) return buildNoBytesResult(withStatus); + + let decoded; + try { + decoded = await decodePreimageWithPapi(withStatus, bytes, client); + } catch { + // ignore + } + + if (!decoded) { + return { + ...withStatus, + isCompleted: true, + proposalError: "Unable to decode preimage bytes into a valid Call", + }; + } + + return decoded; +} + +export default function usePreimagePapi(hashOrBounded) { + const { api: papi, client } = useContextPapi(); + + const { value, loading } = useAsync(async () => { + if (!hashOrBounded || !papi || !client) return null; + + const { proposalHash, inlineData } = parsePapiHashOrBounded(hashOrBounded); + if (!proposalHash) return null; + + if (papiPreimageResultCache.has(proposalHash)) { + return papiPreimageResultCache.get(proposalHash); + } + + const result = inlineData + ? await resolveInlinePapiPreimage(proposalHash, inlineData, client) + : await fetchPapiPreimage(proposalHash, papi, client); + + if (result?.isCompleted) { + papiPreimageResultCache.set(proposalHash, result); + } + + return result; + }, [hashOrBounded, papi, client]); + + const isStatusLoaded = Boolean(papi) && Boolean(client) && !loading; + const isPreimageLoaded = + Boolean(papi) && Boolean(client) && !loading && Boolean(value?.isCompleted); + + return [value ?? {}, isStatusLoaded, isPreimageLoaded]; +} diff --git a/packages/next-common/utils/callDecoder/decoder.mjs b/packages/next-common/utils/callDecoder/decoder.mjs index 1fb2d9a532..85286a922c 100644 --- a/packages/next-common/utils/callDecoder/decoder.mjs +++ b/packages/next-common/utils/callDecoder/decoder.mjs @@ -1,3 +1,4 @@ +import { isNil } from "lodash-es"; import { toTypedCallTree } from "./treeBuilder.mjs"; import { normalizeCallTree } from "./treeNormalize.mjs"; import { @@ -141,7 +142,7 @@ function getCallDocs(metadata, section, method) { ); const callsType = pallet?.calls?.type; - if (callsType === undefined) { + if (isNil(callsType)) { return null; } diff --git a/packages/next-common/utils/callDecoder/typeName.mjs b/packages/next-common/utils/callDecoder/typeName.mjs index 2dfd86ad4e..3fcbe712c8 100644 --- a/packages/next-common/utils/callDecoder/typeName.mjs +++ b/packages/next-common/utils/callDecoder/typeName.mjs @@ -1,6 +1,8 @@ +import { isNil } from "lodash-es"; + function getPathBasedTypeName(metadata, typeId) { // try path-based naming - if (typeId === undefined || typeId === null) { + if (isNil(typeId)) { return null; } const rawLookup = metadata?.lookup?.[typeId];