Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework toasts #1741

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 168 additions & 30 deletions src/components/Toast/Toast.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ import { QUERY_KEYS } from "utils/queryKeys"
import { Buffer } from "buffer"
import { keccak256 } from "ethers/lib/utils"
import { omit } from "utils/rx"
import {
differenceInHours,
differenceInMinutes,
differenceInSeconds,
} from "date-fns"
import { differenceInHours, differenceInMinutes } from "date-fns"
import { useRpcProvider } from "providers/rpcProvider"
import request, { gql } from "graphql-request"
import { useIndexerUrl } from "api/provider"
import { Parachain, SubstrateApis } from "@galacticcouncil/xcm-core"
import {
EvmParachain,
Parachain,
SubstrateApis,
} from "@galacticcouncil/xcm-core"
import { chainsMap } from "@galacticcouncil/xcm-cfg"

const moonbeamRpc = (chainsMap.get("moonbeam") as Parachain).ws
//const txInfoSubscan = "https://hydration.api.subscan.io/api/scan/extrinsic"
const getSubscanEndpoint = (network: string, method: string) => {
return `https://${network}.api.subscan.io/api/scan/${method}`
}

type TExtrinsic = {
hash: string
Expand Down Expand Up @@ -105,6 +107,34 @@ const getExtrinsic = async (indexerUrl: string, hash: string) => {
}
}

const getEvmExtrinsic = async (
indexerUrl: string,
blockNumber: number,
index: number,
) => {
return {
...(await request<{
extrinsics: TSuccessExtrinsic[]
}>(
indexerUrl,
gql`
query GetEvmExtrinsic($blockNumber: Int, $index: Int) {
extrinsics(
where: {
block: { height_eq: $blockNumber }
indexInBlock_eq: $index
}
) {
success
error
}
}
`,
{ blockNumber, index },
)),
}
}

const getExtrinsicIndex = (
{ extrinsics }: { extrinsics: TExtrinsic[] },
i?: number,
Expand Down Expand Up @@ -169,63 +199,171 @@ const getWormholeTx = async (extrinsicIndex: string) => {
}
}

const extractKeyFromURL = (url: string, isEvm: boolean) => {
if (isEvm) {
const origin = new URL(url)?.origin

const chain = [...chainsMap.values()].find((chain) => {
if (chain.isEvmParachain()) {
return (
(chain as EvmParachain).client.chain.blockExplorers?.default.url ===
origin
)
}

return false
})

return chain?.key
}

const match = url.match(/^https?:\/\/([^.]+)\.subscan/)
return match ? match[1] : null
}

export const useProcessToasts = (toasts: ToastData[]) => {
const indexerUrl = useIndexerUrl()
const toast = useToast()
const { api, isLoaded } = useRpcProvider()

useQueries({
queries: toasts.map((toastData) => ({
queryKey: QUERY_KEYS.progressToast(toastData.id),
queryFn: async () => {
const secondsDiff = differenceInSeconds(
const hoursDiff = differenceInHours(
new Date(),
new Date(toastData.dateCreated),
)

// skip processing
if (secondsDiff < 60) return false

const hoursDiff = differenceInHours(
const minutesDiff = differenceInMinutes(
new Date(),
new Date(toastData.dateCreated),
)

const isHiddenToast = minutesDiff > 10

// move to unknown state
if (hoursDiff >= 1 || !toastData.txHash?.length) {
toast.remove(toastData.id)
toast.add("unknown", toastData)
toast.add("unknown", { ...toastData, hidden: true })

return false
}

const res = await getExtrinsic(indexerUrl, toastData.txHash as string)
const isExtrinsic = !!res.extrinsics.length
if (toastData.xcm) {
const link = toastData.link

if (isExtrinsic) {
const isSuccess = res.extrinsics[0].success
if (link) {
const network = extractKeyFromURL(link, toastData.xcm === "evm")

// use subscan to get extrinsic info
// const txInfoRes = await fetch(txInfoSubscan, {
// method: "POST",
// body: JSON.stringify({ hash: toastData.txHash }),
// })
if (network) {
const isSubstrate = toastData.xcm === "substrate"
const endpoint = getSubscanEndpoint(
network,
isSubstrate ? "extrinsic" : "evm/transaction",
)

// const data: { data: { success: boolean } } = await txInfoRes.json()
const body = JSON.stringify({
hash: toastData.txHash,
events_limit: 1,
})

toast.remove(toastData.id)
const subscanRes = await fetch(endpoint, {
method: "POST",
body,
})

if (isSuccess) {
toast.add("success", toastData)
} else {
toast.add("error", toastData)
const resData = await subscanRes.json()

const tx = resData
const isFinalized = isSubstrate
? !!tx.data?.finalized
: tx.data.success || tx.data["error_type"].length

if (tx?.data && isFinalized) {
toast.remove(toastData.id)

if (tx.data.success) {
toast.add("success", { ...toastData, hidden: isHiddenToast })
} else {
toast.add("error", { ...toastData, hidden: isHiddenToast })
}

return true
}

if (minutesDiff >= 5) {
toast.remove(toastData.id)
toast.add("unknown", { ...toastData, hidden: isHiddenToast })

return false
}
}
}

return true
return false
} else {
const isEvm =
toastData.link?.includes("evm") ||
toastData.link?.includes("explorer.nice.hydration.cloud")

if (isEvm) {
const ethTx = await api.rpc.eth.getTransactionByHash(
toastData.txHash,
)

if (ethTx) {
const blockNumber = ethTx.blockNumber.toString()
const indexInBlock = ethTx.transactionIndex.toString()

const { extrinsics } = await getEvmExtrinsic(
indexerUrl,
Number(blockNumber),
Number(indexInBlock),
)

if (!!extrinsics.length) {
const isSuccess = extrinsics[0].success
toast.remove(toastData.id)

if (isSuccess) {
toast.add("success", { ...toastData, hidden: isHiddenToast })
} else {
toast.add("error", { ...toastData, hidden: isHiddenToast })
}

return true
}
}

return false
} else {
const res = await getExtrinsic(
indexerUrl,
toastData.txHash as string,
)

const isExtrinsic = !!res.extrinsics.length

if (isExtrinsic) {
const isSuccess = res.extrinsics[0].success

toast.remove(toastData.id)

if (isSuccess) {
toast.add("success", { ...toastData, hidden: isHiddenToast })
} else {
toast.add("error", { ...toastData, hidden: isHiddenToast })
}

return true
}
}
}

return false
},
refetchInterval: 10000,
enabled: isLoaded,
})),
})
}
Expand Down
8 changes: 7 additions & 1 deletion src/components/Toast/ToastProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ import { AnimatePresence, domAnimation, LazyMotion } from "framer-motion"

import { ToastSidebar } from "./sidebar/ToastSidebar"
import { useBridgeToast, useProcessToasts } from "./Toast.utils"
import { useStore } from "state/store"

export const ToastProvider: FC<PropsWithChildren> = ({ children }) => {
const { toasts, toastsTemp, hide, sidebar, setSidebar } = useToast()
const { transactions } = useStore()

const bridgeToasts = toasts.filter(
(toast) => toast.bridge && toast.variant === "progress",
)

const progressToasts = toasts.filter((toast) => {
return !toast.bridge && toast.variant === "progress"
return (
!toast.bridge &&
toast.variant === "progress" &&
!transactions?.find((transaction) => transaction.id === toast.id)
)
})

useBridgeToast(bridgeToasts)
Expand Down
10 changes: 5 additions & 5 deletions src/i18n/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -358,10 +358,6 @@
"liquidity.reviewTransaction.modal.error.copy": "Copy error message",
"liquidity.reviewTransaction.modal.error.copied": "Message copied!",
"liquidity.reviewTransaction.modal.error.tip": "Not enough HDX for TIP",
"liquidity.reviewTransaction.toast.pending": "Submitting transaction...",
"liquidity.reviewTransaction.toast.success": "Transaction successful.",
"liquidity.reviewTransaction.toast.error": "Transaction failed.",
"liquidity.reviewTransaction.toast.unknown": "Transaction status unknown.",
"liquidity.reviewTransaction.calldata.encoded": "Encoded call data",
"liquidity.reviewTransaction.calldata.encodedHash": "Encoded call hash",
"liquidity.reviewTransaction.calldata.decoded": "Decoded",
Expand Down Expand Up @@ -1234,5 +1230,9 @@
"claimingRange.modal.warning.title": "Are you sure?",
"claimingRange.modal.warning.description": " Don't claim rewards too early. Claiming forfeits any non-claimable rewards to the other LPs. Before claiming, make sure that your claim threshold is configured accordingly.",
"claimingRange.modal.description1": "After joining a farm, a portion of the accumulated rewards is locked. These rewards unlock over time as you remain in the farm and follow loyalty factor curve",
"claimingRange.modal.description2": "Claiming forfeits the locked part of the rewards. Use this setting to tweak your preference over claiming faster or losing less rewards (default). This allows you to compound your rewards with ease."
"claimingRange.modal.description2": "Claiming forfeits the locked part of the rewards. Use this setting to tweak your preference over claiming faster or losing less rewards (default). This allows you to compound your rewards with ease.",
"toast.pending": "Submitting transaction...",
"toast.success": "Transaction successful.",
"toast.error": "Transaction failed.",
"toast.unknown": "Transaction status unknown."
}
Loading
Loading