diff --git a/src/components/Extrinsics/ChargeAssetTx.tsx b/src/components/Extrinsics/ChargeAssetTx.tsx new file mode 100644 index 0000000..5ac4040 --- /dev/null +++ b/src/components/Extrinsics/ChargeAssetTx.tsx @@ -0,0 +1,106 @@ +import { client$ } from "@/state/chains/chain.state" +import { XcmV5Junctions } from "@polkadot-api/descriptors" +import { + Binary, + decAnyMetadata, + unifyMetadata, +} from "@polkadot-api/substrate-bindings" +import { HexString } from "polkadot-api" +import { FC, useEffect, useState } from "react" +import { TokenAmount } from "../TokenAmount" + +type ChargeAssetTxPayment = { + tip: bigint + // Add more supported types as we go + asset_id?: { + parents: number + interior: XcmV5Junctions + } +} + +const getAsset = (value: ChargeAssetTxPayment) => { + if (value.asset_id?.parents !== 0) return null + if (value.asset_id.interior.type !== "X2") return null + const [first, second] = value.asset_id.interior.value + if (first.type !== "PalletInstance" || second.type !== "GeneralIndex") + return null + return { + pallet: first.value, + index: second.value, + } +} +export const canDecodeAsset = (value: ChargeAssetTxPayment) => + getAsset(value) !== null + +const loadAsset = async ( + asset: { + pallet: number + index: bigint + }, + block?: HexString, +) => { + const client = await client$.getValue() + const metadata = unifyMetadata( + decAnyMetadata( + await client.getMetadata( + block ?? (await client.getFinalizedBlock()).hash, + ), + ), + ) + const pallet = metadata.pallets.find((p) => p.index === asset.pallet) + if (!pallet) return null + const hasMetadata = pallet.storage?.items.find((it) => it.name === "Metadata") + if (!hasMetadata) return null + const assetMetadata: any = await client + .getUnsafeApi() + .query[pallet.name].Metadata.getValue(Number(asset.index)) + if (assetMetadata.decimals == null) return null + return { + pallet: pallet.name, + symbol: Binary.toText(assetMetadata.symbol) as string, + decimals: assetMetadata.decimals as number, + } +} + +export const ChargeAssetTx: FC<{ + chargeAssetTxPayment: { + tip: bigint + // Add more types as we go + asset_id?: { + parents: number + interior: XcmV5Junctions + } + } +}> = ({ chargeAssetTxPayment }) => { + const asset = getAsset(chargeAssetTxPayment) + const [assetMetadata, setAssetMetadata] = useState<{ + pallet: string + symbol: string + decimals: number + } | null>(null) + + useEffect(() => { + if (!asset) return + + let cancelled = false + loadAsset(asset).then( + (r) => !cancelled && setAssetMetadata(r), + (ex) => console.error(ex), + ) + return () => { + cancelled = true + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [asset?.index, asset?.pallet]) + + if (!asset || !assetMetadata) return null + + return ( + + {chargeAssetTxPayment.tip} in{" "} + {assetMetadata.symbol + ? `${assetMetadata.symbol} (${assetMetadata.pallet} #${asset.index})` + : `${assetMetadata.pallet} #${asset.index}`} + + ) +} diff --git a/src/components/Extrinsics/SignedExtensions.tsx b/src/components/Extrinsics/SignedExtensions.tsx index 9349881..79937b2 100644 --- a/src/components/Extrinsics/SignedExtensions.tsx +++ b/src/components/Extrinsics/SignedExtensions.tsx @@ -2,14 +2,10 @@ import { ExpandBtn } from "@/components/Expand" import { JsonDisplay } from "@/components/JsonDisplay" import { Dot } from "lucide-react" import { jsonSerialize } from "polkadot-api/utils" -import { - ComponentType, - FC, - PropsWithChildren, - ReactNode, - useState, -} from "react" +import { FC, PropsWithChildren, ReactNode, useState } from "react" import { InlineMortality, MortalityAnalyzer } from "./MortalityAnalyzer" +import { canDecodeAsset, ChargeAssetTx } from "./ChargeAssetTx" +import { TokenAmount } from "../TokenAmount" export const SignedExtensions: FC<{ extra: Record @@ -30,20 +26,19 @@ export const SignedExtensions: FC<{ const SignedExtension: FC<{ id: string; value: unknown }> = ({ id, value }) => { const knownExtension = knownSignedExtensions[id] if (knownExtension) { - const { expanded: ExpandedView, inline: InlineView } = knownExtension - return ExpandedView ? ( - : null} - > - - - ) : InlineView ? ( - : null} - /> - ) : null + const { expanded, inline } = knownExtension + const expandedElement = expanded?.(value) + const inlineElement = inline?.(value) + if (expandedElement) { + return ( + + {expandedElement} + + ) + } + if (inlineElement) { + return + } } const inlineJson = JSON.stringify(value, jsonSerialize) @@ -59,13 +54,27 @@ const SignedExtension: FC<{ id: string; value: unknown }> = ({ id, value }) => { export const knownSignedExtensions: Record< string, { - inline?: ComponentType<{ id: string; value: unknown }> - expanded?: ComponentType<{ id: string; value: unknown }> + inline?: (value: any) => ReactNode + expanded?: (value: any) => ReactNode } > = { CheckMortality: { - expanded: ({ value }) => , - inline: ({ value }) => , + expanded: (value) => , + inline: (value) => , + }, + ChargeAssetTxPayment: { + inline: (value) => + value.asset_id ? ( + canDecodeAsset(value) ? ( + + ) : null + ) : ( +
+ {value.tip} + {JSON.stringify(value, jsonSerialize)} +
+ ), + expanded: (value) => (value.asset_id ? : null), }, } diff --git a/src/components/TokenAmount.tsx b/src/components/TokenAmount.tsx index 2bbb963..62aecc2 100644 --- a/src/components/TokenAmount.tsx +++ b/src/components/TokenAmount.tsx @@ -3,6 +3,23 @@ import { chainProperties$ } from "@/state/chain-props.state" import { useStateObservable } from "@react-rxjs/core" import { FC } from "react" +export const formatToken = ( + amount: bigint, + properties: { + decimals: number + symbol?: string + }, +) => { + const formattedValue = ( + Number(amount) / + 10 ** properties.decimals + ).toLocaleString(undefined, { + maximumSignificantDigits: 3, + }) + + return `${formattedValue} ${properties.symbol}` +} + export const TokenAmount: FC<{ children: bigint className?: string @@ -15,16 +32,12 @@ export const TokenAmount: FC<{ return children.toLocaleString() } - const formattedValue = ( - Number(children) / - 10 ** properties.tokenDecimals - ).toLocaleString(undefined, { - maximumSignificantDigits: 3, - }) - return ( - {`${formattedValue} ${properties.tokenSymbol}`} + + {formatToken(children, { + decimals: properties.tokenDecimals, + symbol: properties.tokenSymbol, + })} + ) }