feat: decode ChargeAssetTxPayment
This commit is contained in:
@@ -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 (
|
||||
<span className="tabular-nums">
|
||||
<TokenAmount>{chargeAssetTxPayment.tip}</TokenAmount> in{" "}
|
||||
{assetMetadata.symbol
|
||||
? `${assetMetadata.symbol} (${assetMetadata.pallet} #${asset.index})`
|
||||
: `${assetMetadata.pallet} #${asset.index}`}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -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<string, unknown>
|
||||
@@ -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 ? (
|
||||
<ExpandableLine
|
||||
id={id}
|
||||
inlineContent={InlineView ? <InlineView id={id} value={value} /> : null}
|
||||
>
|
||||
<ExpandedView id={id} value={value} />
|
||||
</ExpandableLine>
|
||||
) : InlineView ? (
|
||||
<InlineLine
|
||||
id={id}
|
||||
inlineContent={InlineView ? <InlineView id={id} value={value} /> : null}
|
||||
/>
|
||||
) : null
|
||||
const { expanded, inline } = knownExtension
|
||||
const expandedElement = expanded?.(value)
|
||||
const inlineElement = inline?.(value)
|
||||
if (expandedElement) {
|
||||
return (
|
||||
<ExpandableLine id={id} inlineContent={inlineElement}>
|
||||
{expandedElement}
|
||||
</ExpandableLine>
|
||||
)
|
||||
}
|
||||
if (inlineElement) {
|
||||
return <InlineLine id={id} inlineContent={inlineElement} />
|
||||
}
|
||||
}
|
||||
|
||||
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 }) => <MortalityAnalyzer mortality={value as any} />,
|
||||
inline: ({ value }) => <InlineMortality mortality={value as any} />,
|
||||
expanded: (value) => <MortalityAnalyzer mortality={value as any} />,
|
||||
inline: (value) => <InlineMortality mortality={value as any} />,
|
||||
},
|
||||
ChargeAssetTxPayment: {
|
||||
inline: (value) =>
|
||||
value.asset_id ? (
|
||||
canDecodeAsset(value) ? (
|
||||
<ChargeAssetTx chargeAssetTxPayment={value} />
|
||||
) : null
|
||||
) : (
|
||||
<div className="inline-block">
|
||||
<TokenAmount>{value.tip}</TokenAmount>
|
||||
<span> {JSON.stringify(value, jsonSerialize)}</span>
|
||||
</div>
|
||||
),
|
||||
expanded: (value) => (value.asset_id ? <JsonDisplay src={value} /> : null),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<span
|
||||
className={cn("tabular-nums", className)}
|
||||
>{`${formattedValue} ${properties.tokenSymbol}`}</span>
|
||||
<span className={cn("tabular-nums", className)}>
|
||||
{formatToken(children, {
|
||||
decimals: properties.tokenDecimals,
|
||||
symbol: properties.tokenSymbol,
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user