Skip to content

Commit

Permalink
feat: display nonce and lifetime in substrate tx details (#1778)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xKheops authored Jan 17, 2025
1 parent f792c4b commit 1144b7d
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 13 deletions.
80 changes: 70 additions & 10 deletions apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsSub.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { TypeRegistry } from "@polkadot/types"
import { classNames } from "@talismn/util"
import { useQuery } from "@tanstack/react-query"
import { FC, useEffect, useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Button, Drawer } from "talisman-ui"
Expand All @@ -21,6 +23,19 @@ import { ViewDetailsButton } from "./ViewDetailsButton"
import { ViewDetailsField } from "./ViewDetailsField"
import { ViewDetailsTxObject } from "./ViewDetailsTxObject"

export const ViewDetailsSub: FC = () => {
const { isOpen, open, close } = useOpenClose()

return (
<>
<ViewDetailsButton onClick={open} hide={isOpen} />
<Drawer anchor="bottom" containerId="main" isOpen={isOpen} onDismiss={close}>
<ViewDetailsContent onClose={close} />
</Drawer>
</>
)
}

const ViewDetailsContent: FC<{
onClose: () => void
}> = ({ onClose }) => {
Expand Down Expand Up @@ -75,6 +90,8 @@ const ViewDetailsContent: FC<{
return { methodName, args }
}, [extrinsic, t])

const { data: lifetimeRows } = useLifetimeRows()

useEffect(() => {
genericEvent("open sign transaction view details", { type: "substrate" })
}, [genericEvent])
Expand All @@ -100,6 +117,16 @@ const ViewDetailsContent: FC<{
token={nativeToken}
/>
<ViewDetailsAmount label={t("Tip")} amount={tip} token={nativeToken} />
<ViewDetailsField label={t("Nonce")}>
{decodedPayload?.nonce.toNumber()}
</ViewDetailsField>
<ViewDetailsField label={t("Lifetime")}>
{lifetimeRows?.map((str, i) => (
<div key={str + i} className={classNames(str === "LOADING" && "invisible")}>
{str}
</div>
))}
</ViewDetailsField>
<ViewDetailsField
label={t("Decoding error")}
error={errorDecodingExtrinsic ? t("Failed to decode method.") : ""}
Expand Down Expand Up @@ -130,15 +157,48 @@ const ViewDetailsContent: FC<{
)
}

export const ViewDetailsSub: FC = () => {
const { isOpen, open, close } = useOpenClose()
const useLifetimeRows = () => {
const { t } = useTranslation("request")
const { sapi, payload } = usePolkadotSigningRequest()

return (
<>
<ViewDetailsButton onClick={open} hide={isOpen} />
<Drawer anchor="bottom" containerId="main" isOpen={isOpen} onDismiss={close}>
<ViewDetailsContent onClose={close} />
</Drawer>
</>
)
const period = useMemo(() => {
try {
if (!isJsonPayload(payload)) return null

// if blockhash is equal to genesis hash then transaction is immortal
if (payload.genesisHash === payload.blockHash) return null

const registry = new TypeRegistry()
const ep = registry.createType("ExtrinsicPayload", payload)

return ep.era.isMortalEra ? ep.era.asMortalEra.period.toNumber() : null
} catch (err) {
return null
}
}, [payload])

const mortality = useMemo(() => {
if (!isJsonPayload(payload)) return ""

// note: for mortal transaction the period is never 0
if (!period) return t("Immortal")

const startBlock = parseInt(payload.blockNumber, 16)
return t("From block {{startBlock}} to block {{endBlock}}", {
startBlock,
endBlock: startBlock + period - 1,
})
}, [payload, period, t])

return useQuery({
queryKey: ["System.Number", sapi?.id],
queryFn: () => {
if (!sapi) return null
return sapi.getStorage<number>("System", "Number", [])
},
initialData: null,
refetchInterval: 2_000,
select: (blockNumber) =>
period ? [mortality, t("Current block: {{blockNumber}}", { blockNumber })] : [mortality],
})
}
7 changes: 4 additions & 3 deletions apps/extension/src/ui/util/scaleApi/sapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export const getScaleApi = (
getConstant: <T>(pallet: string, constant: string) =>
getConstantValue<T>(chainId, metadata, builder, pallet, constant),

getStorage: <T>(pallet: string, entry: string, keys: unknown[]) =>
getStorageValue<T>(chainId, builder, pallet, entry, keys),
getStorage: <T>(pallet: string, entry: string, keys: unknown[], at?: string) =>
getStorageValue<T>(chainId, builder, pallet, entry, keys, at),

getDecodedCall: (pallet: string, method: string, args: unknown) =>
getDecodedCall(pallet, method, args),
Expand Down Expand Up @@ -483,11 +483,12 @@ const getStorageValue = async <T>(
pallet: string,
entry: string,
keys: unknown[],
at?: string,
) => {
const storageCodec = scaleBuilder.buildStorage(pallet, entry)
const stateKey = storageCodec.enc(...keys)

const hexValue = await api.subSend<string | null>(chainId, "state_getStorage", [stateKey])
const hexValue = await api.subSend<string | null>(chainId, "state_getStorage", [stateKey, at])
if (!hexValue) return null as T // caller will need to expect null when applicable

return storageCodec.dec(hexValue) as T
Expand Down

0 comments on commit 1144b7d

Please sign in to comment.