Compare commits

..

1 Commits
master ... dev

Author SHA1 Message Date
471fa450b8 Show vote extensions and Bundle transaction details (#6)
All checks were successful
Publish cosmos-explorer docker image on release / Run docker build and publish (release) Successful in 2m19s
Part of https://plan.wireit.in/deepstack/browse/VUL-296/

Co-authored-by: Pranav <jadhavpranav89@gmail.com>
Reviewed-on: #6
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2025-12-03 11:57:22 +00:00
3 changed files with 124 additions and 8 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "ping.pub", "name": "ping.pub",
"version": "3.0.1-zenith-0.1.2", "version": "3.0.1-zenith-0.2.0",
"private": true, "private": true,
"target": "", "target": "",
"scripts": { "scripts": {

View File

@ -46,7 +46,12 @@ const chain = useBlockchain();
<tr v-for="item in txs"> <tr v-for="item in txs">
<td>{{ item.injected }}</td> <td>{{ item.injected }}</td>
<td> <td>
<span v-if="item.injected === 'Injected'">{{ item.hash }}</span> <RouterLink
v-if="item.injected === 'Injected'"
:to="`/${chain.chainName}/tx/${item.hash}?type=injected`"
class="text-primary dark:invert">
{{ item.hash }}
</RouterLink>
<RouterLink v-else :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{ <RouterLink v-else :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{
item.hash item.hash
}}</RouterLink> }}</RouterLink>

View File

@ -1,27 +1,78 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { JsonViewer } from 'vue3-json-viewer';
import { useBaseStore, useBlockchain, useFormatter } from '@/stores'; import { useBaseStore, useBlockchain, useFormatter } from '@/stores';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { computed, ref } from '@vue/reactivity'; import { computed, ref } from '@vue/reactivity';
import type { Tx, TxResponse } from '@/types'; import type { Tx, TxResponse } from '@/types';
import { JsonViewer } from 'vue3-json-viewer';
// if you used v1.0.5 or latster ,you should add import "vue3-json-viewer/dist/index.css" // if you used v1.0.5 or latster ,you should add import "vue3-json-viewer/dist/index.css"
import 'vue3-json-viewer/dist/index.css'; import 'vue3-json-viewer/dist/index.css';
const props = defineProps(['hash', 'chain']); const props = defineProps(['hash', 'chain']);
const route = useRoute();
const blockchain = useBlockchain(); const blockchain = useBlockchain();
const baseStore = useBaseStore(); const baseStore = useBaseStore();
const chainStore = useBlockchain();
const format = useFormatter(); const format = useFormatter();
const rpcList = ref(chainStore.current?.endpoints?.rpc || [{ address: '', provider: '' }]);
let rpc = ref('');
const tx = ref( const tx = ref(
{} as { {} as {
tx: Tx; tx: Tx;
tx_response: TxResponse; tx_response: TxResponse;
} }
); );
if (props.hash) { const voteExtension = ref(null as any);
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x)); const bundleTx = ref(null as any);
const isInjected = computed(() => route.query.type === 'injected');
onMounted(async () => {
rpc.value = rpcList.value[0].address;
if (props.hash) {
if (isInjected.value) {
getTxFromRPC(props.hash).then((data) => {
if (data.result) {
const txBytes = new Uint8Array(atob(data.result.tx).split('').map((c: string) => c.charCodeAt(0)));
const decoded = new TextDecoder().decode(txBytes);
// Check if it's a bundle tx
if (decoded.startsWith('INCLUDE_BUNDLE_TX:')) {
const bundleJson = decoded.replace('INCLUDE_BUNDLE_TX:', '');
bundleTx.value = {
...data.result,
decoded: JSON.parse(bundleJson)
};
} else {
// It's a vote extension
voteExtension.value = {
...data.result,
decoded: JSON.parse(decoded)
};
}
}
});
} else {
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
}
}
})
async function getTxFromRPC(hash: string) {
const response = await fetch(rpc.value + `/tx?hash=0x${hash}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
} }
const messages = computed(() => { const messages = computed(() => {
return ( return (
tx.value.tx?.body?.messages.map((x) => { tx.value.tx?.body?.messages.map((x) => {
@ -33,6 +84,22 @@ const messages = computed(() => {
}) || [] }) || []
); );
}); });
const jsonData = computed(() => {
if (tx.value.tx_response) {
return tx.value;
}
if (voteExtension.value) {
return voteExtension.value.decoded
}
if (bundleTx.value) {
return bundleTx.value.decoded
}
return undefined;
});
</script> </script>
<template> <template>
<div> <div>
@ -44,6 +111,50 @@ const messages = computed(() => {
<a class="tab text-gray-400 uppercase tab-active">Transaction</a> <a class="tab text-gray-400 uppercase tab-active">Transaction</a>
</div> </div>
<div v-if="bundleTx" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">Bundle Transaction</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>Tx Hash</td>
<td class="overflow-hidden">{{ bundleTx.hash }}</td>
</tr>
<tr>
<td>Height</td>
<td>
<RouterLink :to="`/${props.chain}/block/${bundleTx.height}`" class="text-primary dark:invert"
>{{ bundleTx.height }}
</RouterLink>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-if="voteExtension" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">Vote Extension</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>Tx Hash</td>
<td class="overflow-hidden">{{ voteExtension.hash }}</td>
</tr>
<tr>
<td>Height</td>
<td>
<RouterLink :to="`/${props.chain}/block/${voteExtension.height}`" class="text-primary dark:invert"
>{{ voteExtension.height }}
</RouterLink>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"> <div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">{{ $t('tx.title') }}</h2> <h2 class="card-title truncate mb-2">{{ $t('tx.title') }}</h2>
<div class="overflow-hidden"> <div class="overflow-hidden">
@ -116,16 +227,16 @@ const messages = computed(() => {
<div v-if="messages.length === 0">{{ $t('tx.no_messages') }}</div> <div v-if="messages.length === 0">{{ $t('tx.no_messages') }}</div>
</div> </div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow"> <div v-if="jsonData" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title truncate mb-2">JSON</h2> <h2 class="card-title truncate mb-2">JSON</h2>
<JsonViewer <JsonViewer
:value="tx" :value="jsonData"
:theme="baseStore.theme" :theme="baseStore.theme"
style="background: transparent" style="background: transparent"
copyable copyable
boxed boxed
sort sort
expand-depth="5" :expand-depth="5"
/> />
</div> </div>
</div> </div>