bridge-in flow finished based on new weaver logic

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-11-06 17:23:41 +03:00
parent 132bd0986b
commit 42d5f82040
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
8 changed files with 362 additions and 149 deletions

View File

@ -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",

View File

@ -447,7 +447,7 @@ const BridgeIn = ({ chainId, address, config, connect }) => {
disabled={actionDisabled}
onClick={startGhostBridge}
>
Bond
{isPending ? "Pending..." : "Bridge-In"}
</PrimaryButton>
</Box>
</Paper>

View File

@ -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 (
<Modal
maxWidth="420px"
minHeight="200px"
open={isOpen}
headerText={
<>
{bridgeInInfo && "Bridge-In"}
{!bridgeInInfo && "Parsing Transaction Hash"}
</>
headerContent={
<Box display="flex" flexDirection="row">
{bridgeInInfo
? <Box display="flex" gap="10px">
<Typography variant="h5">Bridge-In</Typography>
<Link
underline="always"
target="_blank"
rel="noopener noreferrer"
href={`${chainInfo.url}/tx/${historyRecord.txHash}`}
>
<Typography variant="h5">
{bridgeInInfo ? shorten(historyRecord.txHash, 7, -5) : ""}
</Typography>
</Link>
</Box>
: <Typography variant="h5">Parsing Transaction Hash</Typography>
}
</Box>
}
onClose={() => close()}
onClose={() => isPending ? {} : close()}
>
<Box display="flex" flexDirection="column">
<Box width="100%" gap="5px" display="flex" flexDirection="column" justifyContent="space-between" alignItems="center">
<BridgeInProgress
submissionBlock={receipt ? receipt.blockNumber : 0}
blockNumber={blockNumber}
chainId={chainId}
casperApi={casperApi}
finalization={finalization}
loomState={loomState}
rootIncluded={rootIncluded}
claimed={threadPulled === null}
/>
<Box
@ -191,15 +308,15 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => {
/>
<DataRow
title="Receiver [-]"
title="Receiver"
tooltip="some description to be added."
balance={
<Link
style={{ display: "flex", flexDirection: "row", justifyContent: "center", alignItems: "center" }}
onClick={() => copyToClipboard(receiver, 0)}
onClick={() => functionArgs?.receiver ? copyToClipboard(functionArgs.receiver, 0) : {}}
>
<Typography variant="body1">
{bridgeInInfo ? shorten(receiver, 11, -8) : <Skeleton width="120px" />}
{functionArgs?.receiver ? shorten(functionArgs.receiver, 11, -8) : <Skeleton width="120px" />}
</Typography>
<GhostStyledIcon
sx={{ marginLeft: "5px", width: "12px", height: "12px" }}
@ -218,19 +335,6 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => {
: <Skeleton width="120px" />
}
/>
<DataRow
title="Transaction Hash [+]"
tooltip="some description to be added."
balance={historyRecord
? <Link href={`${chainInfo.url}/tx/${historyRecord.txHash}`} target="_blank" rel="noopener noreferrer">
<Typography variant="body1">
{bridgeInInfo ? shorten(historyRecord.txHash, 11, -8) : <Skeleton width="120px" />}
</Typography>
</Link>
: <Skeleton width="120px" />
}
/>
</Box>
<hr style={{ width: "100%" }} />
@ -250,9 +354,14 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => {
}}
>
<DataRow
title="Gross Bridged [-]"
title="Gross Bridged"
tooltip="some description to be added."
balance={<Typography variant="body1">{formatCurrency(bridgedAmount, 5, chainProperties?.properties.tokenSymbol ?? "")}</Typography>}
balance={bridgedAmount
? <Typography variant="body1">
{formatCurrency(bridgedAmount.toString(), 5, chainProperties?.properties.tokenSymbol ?? "")}
</Typography>
: <Skeleton width="120px" />
}
/>
<DataRow
@ -264,18 +373,32 @@ const BridgeInModal = ({ chainId, isOpen, close, config, historyRecord }) => {
<DataRow
title="Net Received"
tooltip="some description to be added."
balance={<Typography variant="body1">{formatCurrency(estimatedReceived.toString(), 5, chainProperties?.properties.tokenSymbol ?? "")}</Typography>}
balance={estimatedReceived
? <Typography variant="body1">
{formatCurrency(estimatedReceived.toString(), 5, chainProperties?.properties.tokenSymbol ?? "")}
</Typography>
: <Skeleton width="120px" />
}
/>
</Box>
<PrimaryButton fullWidth>Claim Bridge-In</PrimaryButton>
<PrimaryButton
fullWidth
disabled={!rootIncluded || isPending || threadPulled === null}
onClick={pullCurrentThread}
>
{isPending
? "Pending..."
: threadPulled === null ? "Transaction Claimed" : "Claim Bridge-In"
}
</PrimaryButton>
</Box>
</Box>
</Modal>
)
}
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 (
<Box p="10px 0" width="100%" display="flex" justifyContent="space-between" alignItems="center">
{finalization === null || finalization > 0
? <BlockFinalization theme={theme} finalization={finalization} />
: <SyncTarget
theme={theme}
weaverAuthorities={weaverAuthorities}
networkAttestations={networkAttestations}
rootIncluded={rootIncluded}
blockNumber={blockNumber}
submissionBlock={submissionBlock}
loomState={loomState}
tapestryDraft={tapestryDraft}
/>
}
<RootVerification
theme={theme}
finalization={finalization}
rootIncluded={rootIncluded}
tapestryDraft={tapestryDraft}
tapestryStrand={tapestryStrand}
totalAuthorities={weaverAuthorities?.length ?? 0}
/>
<ProofSubmission theme={theme} rootIncluded={rootIncluded} />
<ProofSubmission claimed={claimed} theme={theme} rootIncluded={rootIncluded} />
</Box>
)
}
@ -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 }) => {
<Typography variant="caption">
{rootIncluded
? claimed ? "Success" : "Claim Ready"
: "Pending..."
: "Claim Pending..."
}
</Typography>
</Box>
)
}
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 (
<Box
sx={{
transition: "all 0.8s ease",
transform: !rootIncluded && tapestryDraft && "scale(1.2)",
color: (rootIncluded || tapestryDraft) && theme.colors.primary[300]
transform: !rootIncluded && !finalizationPending && tapestryDraft && "scale(1.2)",
color: (rootIncluded || tapestryDraft) && !finalizationPending && theme.colors.primary[300]
}}
maxWidth="120px"
display="flex"
@ -449,14 +570,22 @@ const RootVerification = ({ theme, tapestryDraft, rootIncluded, totalAuthorities
)
}
const SyncTarget = ({ tapestryDraft, loomState, rootIncluded, blockNumber, theme }) => {
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 (
<Box

View File

@ -18,6 +18,7 @@ import {
import { useLocation } from "react-router-dom";
import { useEffect, useState, useMemo, useCallback } from "react";
import useSWR from 'swr';
import ReactGA from "react-ga4";
import SettingsIcon from '@mui/icons-material/Settings';
@ -33,7 +34,9 @@ import { Tab, Tabs } from "../../components/Tabs/Tabs";
import { useTokenSymbol } from "../../hooks/tokens";
import { useLocalStorage } from "../../hooks/localstorage";
import { useUnstableProvider } from "../../hooks/ghost";
import { shorten } from "../../helpers";
import { networkAvgBlockSpeed } from "../../constants";
import EraseModal from "./EraseModal";
import SettingsModal from "./SettingsModal";
@ -41,7 +44,7 @@ import DexViewModal from "./DexViewModal";
import StakingViewModal from "./StakingViewModal";
import BridgeInModal from "./BridgeInModal";
const HistoryItem = ({ item, chainId, config, currentTime, eraseRecord, setViewModalOpen, isSmallScreen }) => {
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 <Chip sx={{ width: width }} label={isVerySmallScreen ? "" : "Pending"} template={"gray"} />
}
// darkGray for final
return <Chip sx={{ width: width }} label={isVerySmallScreen ? "" : "Executed"} template={"success"} />
}, [item, currentTime, isVerySmallScreen])
// TODO: put correct values into TokenStack based on type
return <Chip sx={{ width: width }} label={isVerySmallScreen ? "" : "Ready"} template={"success"} />
}, [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}

View File

@ -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) {

View File

@ -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
]
]
}`;

View File

@ -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;
}
};

View File

@ -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) => {