From 3f82d79da2eb44d830787242e9524992ab18d22a Mon Sep 17 00:00:00 2001 From: Dark-Brain07 <85172976+Dark-Brain07@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:13:33 +0600 Subject: [PATCH] feat: add post-deployment success panel with explorer link Closes #1610 Added a new DeploymentSuccessPanel component that renders automatically upon successful contract deployment. It provides: - The deployed contract address (with quick-copy) - The deployment transaction hash (with quick-copy) - A View on Explorer button that deep-links to the transaction on the connected network - An Interact Now button that smoothly scrolls to the Read/Write methods - Additionally, the contract address is now displayed under the file name in the sidebar for deployed contracts. --- .../src/components/Simulator/ContractItem.vue | 67 ++++++++++------ .../Simulator/DeploymentSuccessPanel.vue | 76 +++++++++++++++++++ frontend/src/hooks/useContractListener.ts | 1 + frontend/src/types/store.ts | 1 + frontend/src/views/Simulator/RunDebugView.vue | 38 ++++++++++ 5 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 frontend/src/components/Simulator/DeploymentSuccessPanel.vue diff --git a/frontend/src/components/Simulator/ContractItem.vue b/frontend/src/components/Simulator/ContractItem.vue index c7b4b91a9..83a5a92e7 100644 --- a/frontend/src/components/Simulator/ContractItem.vue +++ b/frontend/src/components/Simulator/ContractItem.vue @@ -10,6 +10,10 @@ import { } from '@heroicons/vue/16/solid'; import { ref, onMounted, nextTick } from 'vue'; import { notify } from '@kyvg/vue3-notification'; +import { computed } from 'vue'; +import { useShortAddress } from '@/hooks'; + +const { shorten } = useShortAddress(); const store = useContractsStore(); const defaultContractName = 'New Contract.py'; @@ -22,6 +26,11 @@ const props = defineProps<{ const emit = defineEmits(['save', 'cancel']); +const deployedContract = computed(() => { + if (!props.contract) return null; + return store.deployedContracts.find((c) => c.contractId === props.contract?.id); +}); + const isEditing = ref(false); const editInput = ref(null); const editingFileName = ref(''); @@ -137,33 +146,41 @@ const handleDownloadFile = (e: Event) => {
-
- {{ contract.name }} +
+
+ {{ contract.name }} +
+ +
- - diff --git a/frontend/src/components/Simulator/DeploymentSuccessPanel.vue b/frontend/src/components/Simulator/DeploymentSuccessPanel.vue new file mode 100644 index 000000000..1cbaad93e --- /dev/null +++ b/frontend/src/components/Simulator/DeploymentSuccessPanel.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/src/hooks/useContractListener.ts b/frontend/src/hooks/useContractListener.ts index 6a7803612..247f08099 100644 --- a/frontend/src/hooks/useContractListener.ts +++ b/frontend/src/hooks/useContractListener.ts @@ -23,6 +23,7 @@ export function useContractListener() { contractId: localDeployTx.localContractId, address: eventData.data.id, defaultState: eventData.data.data.state, + deployTxHash: localDeployTx.hash, }); } } diff --git a/frontend/src/types/store.ts b/frontend/src/types/store.ts index 3aaad25c6..9b962ab7b 100644 --- a/frontend/src/types/store.ts +++ b/frontend/src/types/store.ts @@ -17,6 +17,7 @@ export interface DeployedContract { contractId: string; address: Address; defaultState: string; + deployTxHash?: `0x${string}`; } export interface NodeLog { diff --git a/frontend/src/views/Simulator/RunDebugView.vue b/frontend/src/views/Simulator/RunDebugView.vue index a75d32c9c..f5adf9da5 100644 --- a/frontend/src/views/Simulator/RunDebugView.vue +++ b/frontend/src/views/Simulator/RunDebugView.vue @@ -3,6 +3,7 @@ import ConstructorParameters from '@/components/Simulator/ConstructorParameters. import ContractReadMethods from '@/components/Simulator/ContractReadMethods.vue'; import ContractWriteMethods from '@/components/Simulator/ContractWriteMethods.vue'; import TransactionsList from '@/components/Simulator/TransactionsList.vue'; +import DeploymentSuccessPanel from '@/components/Simulator/DeploymentSuccessPanel.vue'; import { useContractQueries } from '@/hooks'; import MainTitle from '@/components/Simulator/MainTitle.vue'; import { ref, watch, computed } from 'vue'; @@ -59,6 +60,33 @@ function isFinalityWindowValid(value: number) { } const consensusMaxRotations = computed(() => consensusStore.maxRotations); + +const showDeploymentSuccessPanel = ref(false); +const newlyDeployedTxHash = ref(''); + +watch( + [() => isDeployed.value, () => contract.value?.id], + ([newIsDeployed, newId], [oldIsDeployed, oldId]) => { + if (newIsDeployed && !oldIsDeployed && newId === oldId) { + showDeploymentSuccessPanel.value = true; + const deployedContract = contractsStore.deployedContracts.find( + (c) => c.contractId === newId, + ); + newlyDeployedTxHash.value = deployedContract?.deployTxHash || ''; + } else if (newId !== oldId) { + showDeploymentSuccessPanel.value = false; + } + }, +); + +const scrollToInteract = () => { + const el = + document.getElementById('tutorial-read-methods') || + document.getElementById('tutorial-write-methods'); + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } +};