From 16282f10f52aa1f4f0db76ba181c205fc080a8a5 Mon Sep 17 00:00:00 2001
From: 0xKheops <26880866+0xKheops@users.noreply.github.com>
Date: Tue, 27 Aug 2024 10:48:56 +0900
Subject: [PATCH] feat: fee estimate
---
.../Staking/InlineStakingFeeEstimate.tsx | 30 ++++++++++
.../ui/domains/Staking/InlineStakingForm.tsx | 37 +++++++++----
.../domains/Staking/InlineStakingReview.tsx | 13 +----
.../domains/Staking/useInlineStakingWizard.ts | 39 +++++++++++++
apps/extension/src/ui/util/scaleApi.ts | 55 +++++++++++++++++--
5 files changed, 147 insertions(+), 27 deletions(-)
create mode 100644 apps/extension/src/ui/domains/Staking/InlineStakingFeeEstimate.tsx
diff --git a/apps/extension/src/ui/domains/Staking/InlineStakingFeeEstimate.tsx b/apps/extension/src/ui/domains/Staking/InlineStakingFeeEstimate.tsx
new file mode 100644
index 000000000..861e49dbb
--- /dev/null
+++ b/apps/extension/src/ui/domains/Staking/InlineStakingFeeEstimate.tsx
@@ -0,0 +1,30 @@
+import { classNames } from "@talismn/util"
+import { FC } from "react"
+
+import { TokensAndFiat } from "../Asset/TokensAndFiat"
+import { useInlineStakingWizard } from "./useInlineStakingWizard"
+
+export const InlineStakingFeeEstimate: FC<{ noCountUp?: boolean }> = ({ noCountUp }) => {
+ const { feeEstimate, feeToken, isLoadingFeeEstimate, errorFeeEstimate } = useInlineStakingWizard()
+
+ return (
+ <>
+ {errorFeeEstimate ? (
+
Failed to estimate fee
+ ) : !!feeEstimate && !!feeToken ? (
+
+ ) : isLoadingFeeEstimate ? (
+
+ 0.0000 TKN ($0.00)
+
+ ) : null}
+ >
+ )
+}
diff --git a/apps/extension/src/ui/domains/Staking/InlineStakingForm.tsx b/apps/extension/src/ui/domains/Staking/InlineStakingForm.tsx
index be56f6a80..ef87d64b1 100644
--- a/apps/extension/src/ui/domains/Staking/InlineStakingForm.tsx
+++ b/apps/extension/src/ui/domains/Staking/InlineStakingForm.tsx
@@ -29,6 +29,7 @@ import Tokens from "../Asset/Tokens"
import { TokensAndFiat } from "../Asset/TokensAndFiat"
import { AccountPillButton } from "./AccountPillButton"
import { InlineStakingAccountPicker } from "./InlineStakingAccountPicker"
+import { InlineStakingFeeEstimate } from "./InlineStakingFeeEstimate"
import { InlineStakingPoolPicker } from "./InlineStakingPoolPicker"
import { useNomPoolsBondingDuration } from "./useBondingDuration"
import { useInlineStakingWizard } from "./useInlineStakingWizard"
@@ -419,8 +420,16 @@ const durationFromMs = (ms: number): Duration => {
export const InlineStakingForm = () => {
const { t } = useTranslation()
- const { account, accountPicker, token, pool, poolPicker, isFormValid, setStep } =
- useInlineStakingWizard()
+ const {
+ account,
+ accountPicker,
+ token,
+ pool,
+ poolPicker,
+ isFormValid,
+
+ setStep,
+ } = useInlineStakingWizard()
return (
@@ -478,14 +487,22 @@ export const InlineStakingForm = () => {
{t("Estimated Fee")}
-
+
+ {/* {errorFeeEstimate ? (
+
Failed to estimate fee
+ ) : !!feeEstimate && !!feeToken ? (
+
+ ) : isLoadingFeeEstimate ? (
+
+ 0.0000 TKN ($0.00)
+
+ ) : null} */}
diff --git a/apps/extension/src/ui/domains/Staking/InlineStakingReview.tsx b/apps/extension/src/ui/domains/Staking/InlineStakingReview.tsx
index f326a9dc2..82ce6d73e 100644
--- a/apps/extension/src/ui/domains/Staking/InlineStakingReview.tsx
+++ b/apps/extension/src/ui/domains/Staking/InlineStakingReview.tsx
@@ -5,6 +5,7 @@ import { TokenLogo } from "../Asset/TokenLogo"
import { TokensAndFiat } from "../Asset/TokensAndFiat"
import { SapiSendButton } from "../Transactions/SapiSendButton"
import { InlineStakingAccount } from "./InlineStakingAccount"
+import { InlineStakingFeeEstimate } from "./InlineStakingFeeEstimate"
import { useInlineStakingWizard } from "./useInlineStakingWizard"
export const InlineStakingReview = () => {
@@ -64,14 +65,7 @@ export const InlineStakingReview = () => {
@@ -90,9 +84,6 @@ export const InlineStakingReview = () => {
txMetadata={txMetadata}
/>
)}
- {/* */}
)
}
diff --git a/apps/extension/src/ui/domains/Staking/useInlineStakingWizard.ts b/apps/extension/src/ui/domains/Staking/useInlineStakingWizard.ts
index 730199220..63c57ffb9 100644
--- a/apps/extension/src/ui/domains/Staking/useInlineStakingWizard.ts
+++ b/apps/extension/src/ui/domains/Staking/useInlineStakingWizard.ts
@@ -11,6 +11,7 @@ import { useAccountByAddress } from "@ui/hooks/useAccountByAddress"
import useToken from "@ui/hooks/useToken"
import { useTokenRates } from "@ui/hooks/useTokenRates"
+import { useFeeToken } from "../SendFunds/useFeeToken"
import { useNominationPool } from "./useNominationPools"
type InlineStakingWizardStep = "form" | "review" | "follow-up"
@@ -72,6 +73,7 @@ export const useInlineStakingWizard = () => {
const account = useAccountByAddress(state.address)
const token = useToken(state.tokenId)
+ const feeToken = useFeeToken(token?.id)
const pool = useNominationPool(token?.chain?.id, state.poolId)
const tokenRates = useTokenRates(state.tokenId)
const formatter = useMemo(
@@ -154,6 +156,18 @@ export const useInlineStakingWizard = () => {
},
})
+ const {
+ data: feeEstimate,
+ isLoading: isLoadingFeeEstimate,
+ error: errorFeeEstimate,
+ } = useQuery({
+ queryKey: ["feeEstimate", payloadAndMetadata?.payload], // safe stringify because contains bigint
+ queryFn: () => {
+ if (!sapi || !payloadAndMetadata?.payload) return null
+ return sapi.getFeeEstimate(payloadAndMetadata.payload)
+ },
+ })
+
const onSubmitted = useCallback(
(hash: Hex) => {
if (hash) setState((prev) => ({ ...prev, step: "follow-up", hash }))
@@ -189,6 +203,24 @@ export const useInlineStakingWizard = () => {
// }
// }, [sapi, setState, state])
+ // useEffect(() => {
+ // console.log("[sapi] useInlineStakingWizard", {
+ // payload: payloadAndMetadata?.payload,
+ // feeEstimate,
+ // isLoadingFeeEstimate,
+ // isLoadingPayload,
+ // errorPayload,
+ // errorFeeEstimate,
+ // })
+ // }, [
+ // errorFeeEstimate,
+ // errorPayload,
+ // feeEstimate,
+ // isLoadingFeeEstimate,
+ // isLoadingPayload,
+ // payloadAndMetadata?.payload,
+ // ])
+
return {
account,
token,
@@ -203,6 +235,8 @@ export const useInlineStakingWizard = () => {
hash,
isSubmitting,
submitErrorMessage,
+ feeToken,
+
setAddress,
setTokenId,
setPoolId,
@@ -214,6 +248,11 @@ export const useInlineStakingWizard = () => {
...payloadAndMetadata,
isLoadingPayload,
errorPayload,
+
+ feeEstimate,
+ isLoadingFeeEstimate,
+ errorFeeEstimate,
+
onSubmitted,
}
}
diff --git a/apps/extension/src/ui/util/scaleApi.ts b/apps/extension/src/ui/util/scaleApi.ts
index 3e0729e51..f0948b0aa 100644
--- a/apps/extension/src/ui/util/scaleApi.ts
+++ b/apps/extension/src/ui/util/scaleApi.ts
@@ -7,7 +7,9 @@ import {
} from "@polkadot-api/substrate-bindings"
import { mergeUint8, toHex } from "@polkadot-api/utils"
import { Metadata, TypeRegistry } from "@polkadot/types"
-import { fromHex, getDynamicBuilder, getLookupFn, V14, V15 } from "@talismn/scale"
+import { IRuntimeVersionBase } from "@polkadot/types/types"
+import { getDynamicBuilder, getLookupFn, V14, V15 } from "@talismn/scale"
+import { sleep } from "@talismn/util"
import { ChainId, SignerPayloadJSON } from "extension-core"
import { DEBUG, log } from "extension-shared"
import { Binary } from "polkadot-api"
@@ -15,7 +17,7 @@ import { Hex } from "viem"
import { api } from "@ui/api"
-import { getSignedExtensionValues } from "./signedExtensions"
+import { getExtrinsicDispatchInfo } from "./getExtrinsicDispatchInfo"
type ScaleMetadata = V14 | V15
type ScaleBuilder = ReturnType
@@ -75,6 +77,9 @@ export const getScaleApi = (
config: PayloadSignerConfig
) => getSignerPayloadJSON(chainId, metadata, builder, pallet, method, args, config, chainInfo),
+ getFeeEstimate: async (payload: SignerPayloadJSON) =>
+ getFeeEstimate(chainId, metadata, builder, payload, chainInfo),
+
submit: (payload: SignerPayloadJSON, signature?: Hex) => api.subSubmit(payload, signature),
}
}
@@ -132,9 +137,9 @@ const getPayloadWithMetadataHash = (
const metadataHash = toHex(merkleizedMetadata.digest()) as Hex
// TODO do this without PJS / registry
- const { extra, additionalSigned } = getSignedExtensionValues(payload, metadata)
- const badExtPayload = mergeUint8(fromHex(payload.method), ...extra, ...additionalSigned)
- log.debug("[sapi] bad ExtPayload", { badExtPayload })
+ // const { extra, additionalSigned } = getSignedExtensionValues(payload, metadata)
+ // const badExtPayload = mergeUint8(fromHex(payload.method), ...extra, ...additionalSigned)
+ // log.debug("[sapi] bad ExtPayload", { badExtPayload })
const stop2 = log.timer("get ExtrinsicPayload using PJS")
const registry = new TypeRegistry()
@@ -142,7 +147,7 @@ const getPayloadWithMetadataHash = (
const extPayload = registry.createType("ExtrinsicPayload", payload)
const barePayload = extPayload.toU8a(true)
stop2()
- log.debug("[sapi] good ExtPayload", { badExtPayload })
+ log.debug("[sapi] good ExtPayload", { barePayload })
const txMetadata = merkleizedMetadata.getProofForExtrinsicPayload(barePayload)
@@ -219,6 +224,44 @@ const getSignerPayloadJSON = async (
return { payload, txMetadata }
}
+const getFeeEstimate = async (
+ chainId: ChainId,
+ metadata: ScaleMetadata,
+ builder: ScaleBuilder,
+ payload: SignerPayloadJSON,
+ chainInfo: ChainInfo
+) => {
+ const fullMetadata = {
+ magicNumber: 1635018093, // magic number for metadata
+ metadata: { tag: "v15" as const, value: metadata as V15 },
+ }
+ const metadataBytes = metadataCodec.enc(fullMetadata)
+
+ const stop = log.timer("[sapi] getFeeEstimate => create Extrinsic")
+ const registry = new TypeRegistry()
+ registry.setMetadata(new Metadata(registry, metadataBytes), payload.signedExtensions)
+ const extrinsic = registry.createType("Extrinsic", payload)
+ stop()
+
+ extrinsic.signFake(payload.address, {
+ nonce: payload.nonce,
+ blockHash: payload.blockHash,
+ genesisHash: payload.genesisHash,
+ //payload,
+ runtimeVersion: {
+ specVersion: chainInfo.specVersion,
+ transactionVersion: chainInfo.transactionVersion,
+ // other fields aren't necessary for signing
+ } as IRuntimeVersionBase,
+ })
+
+ await sleep(2000)
+
+ const { partialFee } = await getExtrinsicDispatchInfo(chainId, extrinsic)
+
+ return BigInt(partialFee)
+}
+
const getConstantValue = (
metadata: ScaleMetadata,
scaleBuilder: ScaleBuilder,