diff --git a/package.json b/package.json index 613ddcb..9e8eb4e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ghost-dao-interface", "private": true, - "version": "0.7.59-beta-2", + "version": "0.8.0", "type": "module", "scripts": { "dev": "vite", @@ -31,7 +31,7 @@ "@tanstack/react-query-devtools": "^5.67.2", "@wagmi/core": "^2.17.3", "polkadot-api": "^3.3.0", - "@polkadot-api/descriptors": "file:./.papi/descriptors", + "@polkadot-api/descriptors": "file:.papi/descriptors", "react": "^19.0.0", "react-dom": "^19.0.0", "react-ga4": "^2.1.0", diff --git a/src/containers/Bridge/BridgeIn.jsx b/src/containers/Bridge/BridgeIn.jsx index c81a565..d949a45 100644 --- a/src/containers/Bridge/BridgeIn.jsx +++ b/src/containers/Bridge/BridgeIn.jsx @@ -447,7 +447,7 @@ const BridgeIn = ({ chainId, address, config, connect }) => { disabled={actionDisabled} onClick={startGhostBridge} > - Bond + {isPending ? "Pending..." : "Bridge-In"} diff --git a/src/containers/History/BridgeInModal.jsx b/src/containers/History/BridgeInModal.jsx index 96b32b1..f5928da 100644 --- a/src/containers/History/BridgeInModal.jsx +++ b/src/containers/History/BridgeInModal.jsx @@ -3,6 +3,9 @@ import { useMemo, useState } from "react"; import { useTransactionReceipt, useBlockNumber, useTransaction } from 'wagmi'; import { decodeEventLog, decodeFunctionData } from 'viem'; import { Box, Typography, Link, Skeleton, useTheme } from "@mui/material"; +import { ss58Encode } from "@polkadot-labs/hdkd-helpers"; +import { hexToBytes, bytesToHex } from "viem"; +import { blake2AsU8a } from "@polkadot/util-crypto"; import CheckIcon from '@mui/icons-material/Check'; import SyncIcon from '@mui/icons-material/Sync'; @@ -18,7 +21,8 @@ import GhostStyledIcon from "../../components/Icon/GhostIcon"; import { PrimaryButton } from "../../components/Button"; import { abi as GatekeeperAbi } from "../../abi/GhostGatekeeper.json"; -import { useWeaverState } from "../../hooks/staking" +import { executePapiBareTransaction } from "../../hooks/helpers"; +import { useWeaverRoot, useWeaverProof, useWeaverSlotValues, useWeaverMaximumSlots } from "../../hooks/staking" import { useUnstableProvider } from "../../hooks/ghost"; import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { shorten, formatNumber, formatCurrency } from "../../helpers"; @@ -26,6 +30,7 @@ import { shorten, formatNumber, formatCurrency } from "../../helpers"; const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => { if (historyRecord === null) return null; + const [isPending, setIsPending] = useState(false); const [copiedIndex, setCopiedIndex] = useState(null); const { casperApi, client, account } = useUnstableProvider(); const { data: blockNumber } = useBlockNumber({ watch: true }); @@ -35,11 +40,6 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => { hash: historyRecord.txHash, }); - const functionData = useMemo(() => { - if (!txData) return null; - return decodeFunctionData({ abi: GatekeeperAbi, data: txData.input }) - }, [txData]); - const copyToClipboard = (text, index) => { navigator.clipboard.writeText(text).then(() => { setCopiedIndex(index); @@ -55,6 +55,15 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => { } ); + const functionArgs = useMemo(() => { + if (!txData || !chainProperties) return null; + const decoded = decodeFunctionData({ abi: GatekeeperAbi, data: txData.input }); + const prefix = chainProperties.properties.ss58Format; + const receiverRaw = decoded?.args.at(0); + const receiver = receiverRaw ? ss58Encode(receiverRaw, prefix) : null; + return { receiver, amount: decoded?.args.at(1) }; + }, [txData, chainProperties]); + const { data: networkData } = useSWR( casperApi ? ['networkData', casperApi, chainId] : null, async () => { @@ -67,6 +76,19 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => { } ); + const { data: loomState } = useSWR( + casperApi ? ['loomState', casperApi, chainId] : null, + async () => { + const key = BigInt(chainId); + const res = await casperApi.query.GhostWeaver.LoomStates.getValue(key, 0n); + return res; + }, + { + refreshInterval: 12000, + revalidateOnFocus: true + } + ); + const chainInfo = useMemo(() => { const client = config?.getClient(); return { @@ -108,33 +130,36 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => { return allDecodedLogs.find(l => l.eventName === 'ThreadInserted'); }, [allDecodedLogs]); - // TODO: revisit, we need other block to be used here - // TODO: how to verify the fact that merkle root is correct??? - // const xxxx = useWeaverState(chainId, receipt.blockNumber, bridgeInInfo?.args); + const { maximumSlots } = useWeaverMaximumSlots(chainId); + const { merkleRoot: evmMerkleRoot } = useWeaverRoot(chainId, loomState?.at_block, bridgeInInfo?.args); + const { merkleProof } = useWeaverProof(chainId, loomState?.at_block, bridgeInInfo?.args); + const { slotValues } = useWeaverSlotValues(chainId, loomState?.at_block, bridgeInInfo?.args); - // TODO: functionData could be here const localString = new Date(historyRecord.submitted).toLocaleString(); - const receiver = "sfKCC7CEfJLTgGopghSPB5EtNfLVUu34nMtiW3Taf1GuwVz3Z"; - const rawAmount = 1000000000000000000n; - const bridgedAmount = new DecimalBigNumber( - rawAmount, - chainProperties?.properties.tokenDecimals ?? 18 - ); + const bridgedAmount = useMemo(() => { + if (!functionArgs) return null; + return new DecimalBigNumber( + functionArgs.amount, + chainProperties?.properties.tokenDecimals ?? 18 + ) + }, [functionArgs, chainProperties]); const estimatedFee = useMemo(() => { - if (!networkData) return null + if (!networkData) return null; return networkData.incoming_share / 10000000; }, [networkData]); const estimatedReceived = useMemo(() => { + if (!functionArgs) return null; + const hundredPercent = 1000000000; const incomingShare = networkData ? networkData.incoming_share : 0; const result = hundredPercent - incomingShare; - const value = Number(rawAmount) * result / hundredPercent; + const value = Number(functionArgs.amount) * result / hundredPercent; return new DecimalBigNumber(value, chainProperties?.properties.tokenDecimals ?? 18) - }, [networkData, chainProperties]); + }, [functionArgs, networkData, chainProperties]); const finalization = useMemo(() => { if (!receipt || !blockNumber || !networkData) return null; @@ -142,32 +167,124 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => { return Math.max(0, Number(finalityDelay + receipt.blockNumber - blockNumber)); }, [receipt, blockNumber, networkData]); - // TODO: networkId, session, - // TODO: EvmThreadProof: slotIndex global % SLOTS, proofIndex % SLOTS, receiver sf, amount raw + const rootIncluded = useMemo(() => { + if (!loomState || !receipt) return false; + return evmMerkleRoot === loomState.root_hash && receipt.blockNumber < loomState.at_block; + }, [loomState, receipt, evmMerkleRoot]); - // TODO: compute locally the arguments - // https://git.ghostchain.io/ghostchain/ghost-node/src/commit/cc7721e3899f9740e35f65f3feb4cdc621926166/pallets/weaver/src/types.rs#L400 + const weaverArgs = useMemo(() => { + if (!bridgeInInfo) return null; + return { + sessionIndex: bridgeInInfo.args.session, + slotIndex: bridgeInInfo.args.index % maximumSlots, + valueIndex: bridgeInInfo.args.index / maximumSlots, + }; + }, [maximumSlots, bridgeInInfo]); + + const pulledThreadKey = useMemo(() => { + if (!chainId || !weaverArgs) return "0x0000000000000000000000000000000000000000000000000000000000000000"; + + const padU64 = (val) => "0x" + BigInt(val).toString(16).padStart(64, "0"); + + const metadata = [ + padU64(weaverArgs.sessionIndex), + padU64(weaverArgs.valueIndex), + padU64(weaverArgs.slotIndex), + padU64(chainId) + ].map(hexToBytes); + + let currentHash = metadata[0]; + + for (let i = 1; i < metadata.length; i++) { + const combined = new Uint8Array(64); + combined.set(currentHash, 0); + combined.set(metadata[i], 32); + currentHash = blake2AsU8a(combined); + } + + return bytesToHex(currentHash); + }, [chainId, weaverArgs]); + + const { data: threadPulled, mutate: threadPulledMutate } = useSWR( + casperApi ? ['threadPulled', casperApi, pulledThreadKey, chainId] : null, + async () => { + const key = BigInt(chainId); + const res = await casperApi.query.GhostWeaver.PulledThreads.getValue(pulledThreadKey); + return res; + }, + { + revalidateOnFocus: true + } + ); + + const pullCurrentThread = async () => { + setIsPending(true); + + try { + const txArgs = { + network_id: BigInt(chainId), + session: weaverArgs.sessionIndex, + thread_proof: { + type: "EvmThreadProof", + value: { + slot_index: weaverArgs.valueIndex, + proof_index: weaverArgs.slotIndex, + receiver: functionArgs.receiver, + amount: functionArgs.amount, + proof: merkleProof, + slots: slotValues, + } + } + }; + + const tx = casperApi.tx.GhostWeaver.pull_thread(txArgs); + const bareTx = await tx.getBareTx(); + + await executePapiBareTransaction({ client, bareTx }); + await threadPulledMutate(); + } finally { + setIsPending(false); + } + } return ( - {bridgeInInfo && "Bridge-In"} - {!bridgeInInfo && "Parsing Transaction Hash"} - + headerContent={ + + {bridgeInInfo + ? + Bridge-In + + + {bridgeInInfo ? shorten(historyRecord.txHash, 7, -5) : ""} + + + + : Parsing Transaction Hash + } + } - onClose={() => close()} + onClose={() => isPending ? {} : close()} > { /> copyToClipboard(receiver, 0)} + onClick={() => functionArgs?.receiver ? copyToClipboard(functionArgs.receiver, 0) : {}} > - {bridgeInInfo ? shorten(receiver, 11, -8) : } + {functionArgs?.receiver ? shorten(functionArgs.receiver, 11, -8) : } { : } /> - - - - {bridgeInInfo ? shorten(historyRecord.txHash, 11, -8) : } - - - : - } - />
@@ -250,9 +354,14 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => { }} > {formatCurrency(bridgedAmount, 5, chainProperties?.properties.tokenSymbol ?? "")}} + balance={bridgedAmount + ? + {formatCurrency(bridgedAmount.toString(), 5, chainProperties?.properties.tokenSymbol ?? "")} + + : + } /> { {formatCurrency(estimatedReceived.toString(), 5, chainProperties?.properties.tokenSymbol ?? "")}} + balance={estimatedReceived + ? + {formatCurrency(estimatedReceived.toString(), 5, chainProperties?.properties.tokenSymbol ?? "")} + + : + } />
- Claim Bridge-In + + {isPending + ? "Pending..." + : threadPulled === null ? "Transaction Claimed" : "Claim Bridge-In" + } +
) } -const BridgeInProgress = ({ chainId, casperApi, finalization, blockNumber }) => { +const BridgeInProgress = ({ chainId, submissionBlock, claimed, casperApi, finalization, loomState, rootIncluded }) => { const theme = useTheme(); const { data: weaverAuthorities } = useSWR( @@ -286,6 +409,19 @@ const BridgeInProgress = ({ chainId, casperApi, finalization, blockNumber }) => } ); + const { data: networkAttestations } = useSWR( + casperApi ? ['networkAttestations', casperApi] : null, + async () => { + const key = BigInt(chainId); + const res = await casperApi.query.GhostWeaver.NetworkAttestations.getValue(key); + return res; + }, + { + refreshInterval: 12000, + revalidateOnFocus: true + } + ); + const { data: tapestryDraft } = useSWR( casperApi ? ['tapestryDraft', casperApi, chainId] : null, async () => { @@ -312,45 +448,29 @@ const BridgeInProgress = ({ chainId, casperApi, finalization, blockNumber }) => } ); - const { data: loomState } = useSWR( - casperApi ? ['loomState', casperApi, chainId] : null, - async () => { - const key = BigInt(chainId); - const res = await casperApi.query.GhostWeaver.LoomStates.getValue(key, 0n); - return res; - }, - { - refreshInterval: 12000, - revalidateOnFocus: true - } - ); - - const rootIncluded = useMemo(() => { - if (!loomState || !blockNumber) return false; - if (blockNumber >= loomState.at_block) return true; - return false; - }); - return ( {finalization === null || finalization > 0 ? : } - + ) } @@ -374,7 +494,7 @@ const ProofSubmission = ({ theme, claimed, rootIncluded }) => { width: "35px", height: "35px", transformOrigin: "center", - animation: rootIncluded && 'waitingForClaim 4s ease-in-out infinite', + animation: (rootIncluded && !claimed) && 'waitingForClaim 4s ease-in-out infinite', '@keyframes waitingForClaim': { '0%, 100%': { opacity: 0.4 }, '50%': { opacity: 1 }, @@ -387,21 +507,22 @@ const ProofSubmission = ({ theme, claimed, rootIncluded }) => { {rootIncluded ? claimed ? "Success" : "Claim Ready" - : "Pending..." + : "Claim Pending..." } ) } -const RootVerification = ({ theme, tapestryDraft, rootIncluded, totalAuthorities, tapestryStrand }) => { +const RootVerification = ({ theme, finalization, tapestryDraft, rootIncluded, totalAuthorities, tapestryStrand }) => { + const finalizationPending = useMemo(() => finalization > 0, [finalization]); + const rootVerificationText = useMemo(() => { if (rootIncluded) return "Synced"; - if (!tapestryStrand) return "Pending..."; + if (!tapestryDraft || finalizationPending) return "Pending..."; let maxOnesCount = 0; - - tapestryStrand.forEach(item => { + tapestryStrand?.forEach(item => { const dataArray = item[1]?.data || []; const totalOnes = dataArray.reduce((sum, num) => { const onesInNumber = num.toString(2).split('1').length - 1; @@ -414,14 +535,14 @@ const RootVerification = ({ theme, tapestryDraft, rootIncluded, totalAuthorities }); return `${maxOnesCount} / ${totalAuthorities}`; - }, [rootIncluded, tapestryStrand, totalAuthorities]); + }, [rootIncluded, tapestryDraft, tapestryStrand, totalAuthorities, finalizationPending]); return ( { +const SyncTarget = ({ weaverAuthorities, networkAttestations, tapestryDraft, submissionBlock, loomState, rootIncluded, theme }) => { const syncTargetText = useMemo(() => { if (rootIncluded) return "Synced"; - if (!tapestryDraft || !loomState) return "Pending..."; - if (blockNumber >= loomState.at_block) return "Synced" - return `${formatNumber(Number(blockNumber - loomState.at_block))} block behind`; - }, [rootIncluded, tapestryDraft, loomState, blockNumber]); + if (!tapestryDraft || !loomState) { + const total = weaverAuthorities ? weaverAuthorities.length : 0; + const count = networkAttestations + ? networkAttestations.filter(v => v !== undefined).length + : 0; + return `${count} / ${total}` + } + + if (submissionBlock < loomState.at_block) return "Synced"; + + return `${formatNumber(Number(submissionBlock - loomState.at_block))} block behind`; + }, [rootIncluded, tapestryDraft, loomState, weaverAuthorities, submissionBlock, networkAttestations]); return ( { +const HistoryItem = ({ item, chainId, config, currentTime, eraseRecord, setViewModalOpen, isSmallScreen, finalityDelay }) => { const theme = useTheme(); const isVerySmallScreen = useMediaQuery("(max-width: 450px)"); @@ -57,19 +60,15 @@ const HistoryItem = ({ item, chainId, config, currentTime, eraseRecord, setViewM }, [config]); const chipStatus = useMemo(() => { - const delay = 3600000; // useEvmNetwork({ evmChainId: chainId }) - const width = isVerySmallScreen ? "21px" : "100px" + const delay = Number(finalityDelay * networkAvgBlockSpeed(chainId)) * 1000; + const width = isVerySmallScreen ? "21px" : "100px"; if (item.submitted + delay > currentTime) { return } - // darkGray for final - - return - }, [item, currentTime, isVerySmallScreen]) - - // TODO: put correct values into TokenStack based on type + return + }, [item, currentTime, isVerySmallScreen, finalityDelay]) if (isSmallScreen || isVerySmallScreen) { return ( @@ -153,13 +152,30 @@ const HistoryList = ({ chainId, storedTransactions, historyType, config, eraseRe const [viewModalOpen, setViewModalOpen] = useState(null); const { CONSTANTS } = useLocalStorage(); + const { casperApi } = useUnstableProvider(); + + const { data: networkData, error, isLoading } = useSWR( + casperApi ? ['networkData', casperApi, chainId] : null, + async () => { + const key = BigInt(chainId); + const res = await casperApi.query.GhostNetworks.Networks.getValue(key); + return res; + }, + { + revalidateOnFocus: true + } + ); + + const finalityDelay = useMemo(() => { + return networkData ? networkData.finality_delay : 420n; + }, [networkData]); useEffect(() => { const intervalId = setInterval(() => { setCurrentTime(Date.now()); - }, 5000); + }, 1000); return () => clearInterval(intervalId); - }, []) + }, []); const historyContainer = useMemo(() => { if (isSmallScreen) { @@ -173,6 +189,7 @@ const HistoryList = ({ chainId, storedTransactions, historyType, config, eraseRe config={config} chainId={chainId} currentTime={currentTime} + finalityDelay={finalityDelay} eraseRecord={() => eraseRecord(i)} setViewModalOpen={() => setViewModalOpen(i)} isSmallScreen={isSmallScreen} @@ -201,6 +218,7 @@ const HistoryList = ({ chainId, storedTransactions, historyType, config, eraseRe item={item} config={config} currentTime={currentTime} + finalityDelay={finalityDelay} eraseRecord={() => eraseRecord(i)} setViewModalOpen={() => setViewModalOpen(i)} isSmallScreen={isSmallScreen} diff --git a/src/hooks/ghost/UnstableProvider.jsx b/src/hooks/ghost/UnstableProvider.jsx index 9abac39..0da6a07 100644 --- a/src/hooks/ghost/UnstableProvider.jsx +++ b/src/hooks/ghost/UnstableProvider.jsx @@ -11,44 +11,7 @@ import { startFromWorker } from "polkadot-api/smoldot/from-worker" import SmWorker from "polkadot-api/smoldot/worker?worker" import { casper as casperMetadata } from "@polkadot-api/descriptors"; - -// TODO: move me to file -const chainSpec = `{ - "badBlocks": null, - "bootNodes": [ - "/dns/bootnode007.ghostchain.io/tcp/30335/p2p/12D3KooWF9SWxz9dmy6vfndQhoxqCa7PESaoFWEiF8Jkqh4xKDRf", - "/dns/bootnode007.ghostchain.io/tcp/30336/ws/p2p/12D3KooWF9SWxz9dmy6vfndQhoxqCa7PESaoFWEiF8Jkqh4xKDRf", - "/dns/bootnode007.ghostchain.io/tcp/443/wss/p2p/12D3KooWF9SWxz9dmy6vfndQhoxqCa7PESaoFWEiF8Jkqh4xKDRf", - "/dns/bootnode70.chain.ghostchain.io/tcp/30334/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW", - "/dns/bootnode70.chain.ghostchain.io/tcp/30335/ws/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW", - "/dns/bootnode70.chain.ghostchain.io/tcp/30336/wss/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW" - ], - "chainType": "Live", - "forkBlocks": null, - "genesis": { - "stateRootHash": "0xb4784a3c8f2f29ba3f191d563f1166f08f4e0337e114679c0f24ef77d06e10e4" - }, - "id": "casper_staging_testnet", - "lightSyncState": { - "babeEpochChanges": "0x042c315b655c7144df4bfdc41837eeacb84d80aee06eaba51f6941a0c7a26cb1bf5d090000015e9de408000000000ea2e40800000000049b2e88b8ce43a677c1ca90f62134800b7e510c0c353dad719cad82faf6136cfb0d0e0000010ea2e40800000000bea6e408000000000000082c315b655c7144df4bfdc41837eeacb84d80aee06eaba51f6941a0c7a26cb1bf5d0900000103000000000000005e9de40800000000b00400000000000014daaaaab6a6e574099e24ae9bb75b543610edef9d374fa85a378edb573b47615f010000000000000074fa7381a7a74b316afb6793a00387eed9d95d46a69866cbb316b5d9c918af0e0100000000000000e0f0a776ecc9fa5e1f22e2fa001fe3fba5aea52b9444bc894b45589d4213247501000000000000006c4dd88b43e2011cf9a6a73d53446336ac9e04cdd4ca23587df63187ac455e4901000000000000003c944c704cae203619b9e7a5a4b6742736da6a8e76c762291bebdc7652cfec2f01000000000000002ff7c765405e9c11a59525a08173bb1fa59e5e281090f8e4d1824469f513d0cc01000000000000000400000000000000029b2e88b8ce43a677c1ca90f62134800b7e510c0c353dad719cad82faf6136cfb0d0e00000104000000000000000ea2e40800000000b00400000000000014daaaaab6a6e574099e24ae9bb75b543610edef9d374fa85a378edb573b47615f010000000000000074fa7381a7a74b316afb6793a00387eed9d95d46a69866cbb316b5d9c918af0e0100000000000000e0f0a776ecc9fa5e1f22e2fa001fe3fba5aea52b9444bc894b45589d4213247501000000000000006c4dd88b43e2011cf9a6a73d53446336ac9e04cdd4ca23587df63187ac455e4901000000000000003c944c704cae203619b9e7a5a4b6742736da6a8e76c762291bebdc7652cfec2f0100000000000000dea465ff8bf16938cc9ead89bf9fdb5d9c495fad61fd952b2067ebdc51c4e1c10100000000000000040000000000000002", - "babeFinalizedBlockWeight": 1039, - "finalizedBlockHeader": "0x6ed6c2debc2e5fd4e7db0d14426d1b904e6c101f2c62a71b8e891c217ba79079f9433280c0257a860f793833bd6bed6b9f86094400aa8e5ea24a81752f0f046ff5ed560f7231760c622ae35d3968fbab033ed34d950b57a7937ae45c411d1d384748080642414245b50103000000004fa0e40800000000a2c7d71b4e1f94c0208c9730ab93955b08c8d7ac86d2fc6ec5f62cc3272a5f1ec0e28c798461372109ee3e12f03ff66ea5ad9cac5b1c37d7280d2333d0226f0865ea77a801bad79785a8ca83c87f0dbf1e36936a6f65cd7b125a41c52f38f80105424142450101caaa6fe5f9df57930e2ac08aef43b75d5477d410c73c3a8e6dea9d5503b9fd25eae1d12b6604194389e22f3f4265d581eb162d1dc5a9034247fe33765dddbe8c", - "grandpaAuthoritySet": "0x1455446f9a7aa99ced06b317c80ce90d56b84e56526775683af2525969e8da0b640100000000000000236d2fa03f4ed8cb65de7e514d7540159b328f1c170dd402b094ad7fbf5472180100000000000000b63c5a0cf342b9b04931bc8ed74d7d0165ab99ab5f8a4514797d4b299a4501fe01000000000000008f9ea20bf4a807a8e710f7559dece86e94672b5b361de157bdaa5c1f37849f8d01000000000000000aa3a88f6b777c95c3dfe7e997b76798413f16aa325f34824cae0c9102b281d5010000000000000000000000000000000001fe1000000000" - }, - "name": "Casper Staging Testnet", - "properties": { - "ss58Format": 1996, - "tokenDecimals": 18, - "tokenSymbol": "CSPR" - }, - "protocolId": "cspr", - "telemetryEndpoints": [ - [ - "/dns/telemetry.ghostchain.io/tcp/443/x-parity-wss/%2Fsubmit%2F", - 0 - ] - ] -}`; +import { casperChainSpec } from "./chainSpecs"; const GHOST_CONNECT_EXTENSION = "GHOST Connect Browser Extension" const MAX_BLOCK_TIMEOUT = 15000 @@ -71,7 +34,7 @@ export const UnstableProviderProvider = ({ children }) => { try { const smoldot = startFromWorker(new SmWorker()); provider = createClientOther( - getSmProvider(() => smoldot.addChain({ chainSpec })), + getSmProvider(() => smoldot.addChain({ chainSpec: casperChainSpec })), ) if (isMounted) { diff --git a/src/hooks/ghost/chainSpecs.js b/src/hooks/ghost/chainSpecs.js new file mode 100644 index 0000000..62b3023 --- /dev/null +++ b/src/hooks/ghost/chainSpecs.js @@ -0,0 +1,36 @@ +export const casperChainSpec = `{ + "badBlocks": null, + "bootNodes": [ + "/dns/bootnode007.ghostchain.io/tcp/30335/p2p/12D3KooWF9SWxz9dmy6vfndQhoxqCa7PESaoFWEiF8Jkqh4xKDRf", + "/dns/bootnode007.ghostchain.io/tcp/30336/ws/p2p/12D3KooWF9SWxz9dmy6vfndQhoxqCa7PESaoFWEiF8Jkqh4xKDRf", + "/dns/bootnode007.ghostchain.io/tcp/443/wss/p2p/12D3KooWF9SWxz9dmy6vfndQhoxqCa7PESaoFWEiF8Jkqh4xKDRf", + "/dns/bootnode70.chain.ghostchain.io/tcp/30334/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW", + "/dns/bootnode70.chain.ghostchain.io/tcp/30335/ws/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW", + "/dns/bootnode70.chain.ghostchain.io/tcp/30336/wss/p2p/12D3KooWP3h5dSdqcpvsCr7fp1jyfqDj291QDZ68a4gY6VQ8T1nW" + ], + "chainType": "Live", + "forkBlocks": null, + "genesis": { + "stateRootHash": "0xb4784a3c8f2f29ba3f191d563f1166f08f4e0337e114679c0f24ef77d06e10e4" + }, + "id": "casper_staging_testnet", + "lightSyncState": { + "babeEpochChanges": "0x042c315b655c7144df4bfdc41837eeacb84d80aee06eaba51f6941a0c7a26cb1bf5d090000015e9de408000000000ea2e40800000000049b2e88b8ce43a677c1ca90f62134800b7e510c0c353dad719cad82faf6136cfb0d0e0000010ea2e40800000000bea6e408000000000000082c315b655c7144df4bfdc41837eeacb84d80aee06eaba51f6941a0c7a26cb1bf5d0900000103000000000000005e9de40800000000b00400000000000014daaaaab6a6e574099e24ae9bb75b543610edef9d374fa85a378edb573b47615f010000000000000074fa7381a7a74b316afb6793a00387eed9d95d46a69866cbb316b5d9c918af0e0100000000000000e0f0a776ecc9fa5e1f22e2fa001fe3fba5aea52b9444bc894b45589d4213247501000000000000006c4dd88b43e2011cf9a6a73d53446336ac9e04cdd4ca23587df63187ac455e4901000000000000003c944c704cae203619b9e7a5a4b6742736da6a8e76c762291bebdc7652cfec2f01000000000000002ff7c765405e9c11a59525a08173bb1fa59e5e281090f8e4d1824469f513d0cc01000000000000000400000000000000029b2e88b8ce43a677c1ca90f62134800b7e510c0c353dad719cad82faf6136cfb0d0e00000104000000000000000ea2e40800000000b00400000000000014daaaaab6a6e574099e24ae9bb75b543610edef9d374fa85a378edb573b47615f010000000000000074fa7381a7a74b316afb6793a00387eed9d95d46a69866cbb316b5d9c918af0e0100000000000000e0f0a776ecc9fa5e1f22e2fa001fe3fba5aea52b9444bc894b45589d4213247501000000000000006c4dd88b43e2011cf9a6a73d53446336ac9e04cdd4ca23587df63187ac455e4901000000000000003c944c704cae203619b9e7a5a4b6742736da6a8e76c762291bebdc7652cfec2f0100000000000000dea465ff8bf16938cc9ead89bf9fdb5d9c495fad61fd952b2067ebdc51c4e1c10100000000000000040000000000000002", + "babeFinalizedBlockWeight": 1039, + "finalizedBlockHeader": "0x6ed6c2debc2e5fd4e7db0d14426d1b904e6c101f2c62a71b8e891c217ba79079f9433280c0257a860f793833bd6bed6b9f86094400aa8e5ea24a81752f0f046ff5ed560f7231760c622ae35d3968fbab033ed34d950b57a7937ae45c411d1d384748080642414245b50103000000004fa0e40800000000a2c7d71b4e1f94c0208c9730ab93955b08c8d7ac86d2fc6ec5f62cc3272a5f1ec0e28c798461372109ee3e12f03ff66ea5ad9cac5b1c37d7280d2333d0226f0865ea77a801bad79785a8ca83c87f0dbf1e36936a6f65cd7b125a41c52f38f80105424142450101caaa6fe5f9df57930e2ac08aef43b75d5477d410c73c3a8e6dea9d5503b9fd25eae1d12b6604194389e22f3f4265d581eb162d1dc5a9034247fe33765dddbe8c", + "grandpaAuthoritySet": "0x1455446f9a7aa99ced06b317c80ce90d56b84e56526775683af2525969e8da0b640100000000000000236d2fa03f4ed8cb65de7e514d7540159b328f1c170dd402b094ad7fbf5472180100000000000000b63c5a0cf342b9b04931bc8ed74d7d0165ab99ab5f8a4514797d4b299a4501fe01000000000000008f9ea20bf4a807a8e710f7559dece86e94672b5b361de157bdaa5c1f37849f8d01000000000000000aa3a88f6b777c95c3dfe7e997b76798413f16aa325f34824cae0c9102b281d5010000000000000000000000000000000001fe1000000000" + }, + "name": "Casper Staging Testnet", + "properties": { + "ss58Format": 1996, + "tokenDecimals": 18, + "tokenSymbol": "CSPR" + }, + "protocolId": "cspr", + "telemetryEndpoints": [ + [ + "/dns/telemetry.ghostchain.io/tcp/443/x-parity-wss/%2Fsubmit%2F", + 0 + ] + ] +}`; diff --git a/src/hooks/helpers.js b/src/hooks/helpers.js index d1cabaa..6f8dd36 100644 --- a/src/hooks/helpers.js +++ b/src/hooks/helpers.js @@ -259,3 +259,40 @@ export const executeOnChainTransaction = async ({ return undefined; } } + +export const executePapiBareTransaction = async ({ + client, + bareTx, + messages = { + loadingMsg: "Broadcasting transaction to available peers...", + inBlockMsg: "Transaction included in block, waiting for finality...", + successMsg: "Transaction successfully finalized!", + errorMsg: "Transaction failed.", + }, +}) => { + try { + return await new Promise((resolve, reject) => { + const subscription = client.submitAndWatch(bareTx).subscribe({ + next: (event) => { + if (event.type === "broadcasted") { + toast.success(messages.loadingMsg); + } else if (event.type === "inBestBlock") { + toast.success(messages.inBlockMsg); + } else if (event.type === "finalized") { + toast.success(messages.successMsg); + subscription.unsubscribe(); + resolve(event); + } + }, + error: (err) => { + toast.error(`${messages.errorMsg} ${err.message || err}`); + subscription.unsubscribe(); + reject(err); + }, + }); + }); + } catch (err) { + console.error("Bare PAPI Transaction failed:", err); + return undefined; + } +}; diff --git a/src/hooks/staking/index.js b/src/hooks/staking/index.js index e5ae28b..538eb24 100644 --- a/src/hooks/staking/index.js +++ b/src/hooks/staking/index.js @@ -17,38 +17,68 @@ import { DecimalBigNumber } from "../../helpers/DecimalBigNumber"; import { executeOnChainTransaction, getTokenDecimals } from "../helpers"; import { useCirculatingSupply } from "../tokens"; -export const useWeaverState = (chainId, blockNumber, context) => { - const { session, index } = context; +export const useWeaverMaximumSlots = (chainId) => { const { gatekeeperAddress } = useGatekeeperAddress(chainId); - const { data: merkleRoot } = useReadContract({ + const { data } = useReadContract({ abi: GatekeeperAbi, address: gatekeeperAddress, - functionName: "getRoot", - args: [index, blockNumber], - scopeKey: `getRoot-${chainId}-${session}-${gatekeeperAddress}`, + functionName: "SLOTS", + scopeKey: `SLOTS-${chainId}-${gatekeeperAddress}`, chainId: chainId, }); - const { data: merkleProof } = useReadContract({ + return { maximumSlots: data ? data : 1n }; +} + +export const useWeaverRoot = (chainId, blockNumber, context) => { + const session = context?.session; + const { gatekeeperAddress } = useGatekeeperAddress(chainId); + + const { data } = useReadContract({ + abi: GatekeeperAbi, + address: gatekeeperAddress, + functionName: "getRoot", + args: [session, blockNumber], + scopeKey: `getRoot-${chainId}-${session}-${blockNumber}-${gatekeeperAddress}`, + chainId: chainId, + }); + + return { merkleRoot: data?.at(0), nextSession: data?.at(1) } +} + +export const useWeaverProof = (chainId, blockNumber, context) => { + const index = context?.index; + const session = context?.session; + const { gatekeeperAddress } = useGatekeeperAddress(chainId); + + const { data: merkleProofInner } = useReadContract({ abi: GatekeeperAbi, address: gatekeeperAddress, functionName: "getProof", args: [index, session, blockNumber], - scopeKey: `getProof-${chainId}-${session}-${gatekeeperAddress}`, + scopeKey: `getProof-${chainId}-${session}-${index}-${blockNumber}-${gatekeeperAddress}`, chainId: chainId, }); - const { data: slotValues } = useReadContract({ + return { merkleProof: merkleProofInner } +} + +export const useWeaverSlotValues = (chainId, blockNumber, context) => { + const index = context?.index; + const session = context?.session; + const { gatekeeperAddress } = useGatekeeperAddress(chainId); + + const { data: slotValuesInner } = useReadContract({ abi: GatekeeperAbi, address: gatekeeperAddress, functionName: "getSlotValues", args: [index, session, blockNumber], - scopeKey: `getSlotValues-${chainId}-${session}-${gatekeeperAddress}`, + scopeKey: `getSlotValues-${chainId}-${session}-${index}-${blockNumber}-${gatekeeperAddress}`, chainId: chainId, }); - return null + return { slotValues: slotValuesInner } } export const useGatekeeperMinimum = (chainId) => {