diff --git a/src/components/ViewCallData.vue b/src/components/ViewCallData.vue index 4dc077c..5a2eb9d 100644 --- a/src/components/ViewCallData.vue +++ b/src/components/ViewCallData.vue @@ -21,8 +21,17 @@ v-for="(part, partIndex) in toAction(chain, call)" :key="partIndex" class="action-part" + :class="{ + highlight: part.type === 'value' || part.type === 'address', + }" > - {{ part.label }} + {{ + part.type === 'address' + ? getAddressLabel(part.value as Address, true) + : part.type === 'value' + ? getValueLabel(part.value) + : part.value + }} @@ -34,7 +43,15 @@
Address
-
{{ call.to }}
+
+ {{ getAddressLabel(call.to, false) }} + + + +
Value
@@ -66,21 +83,113 @@ import { AccordionTrigger, AccordionContent, } from 'radix-vue'; -import { Hex } from 'viem'; -import { computed } from 'vue'; +import { Address, Hex } from 'viem'; +import { computed, ref, watch } from 'vue'; import IconChevronDown from '@/components/IconChevronDown.vue'; -import { Chain } from '@/utils/chains'; +import useLabels from '@/composables/useLabels'; +import FourByteService from '@/services/4byte'; +import { Chain, getExplorerUrl } from '@/utils/chains'; import { decodeCallData, toAction, Call } from '@/utils/entryPoint'; +import IconArrowTopRight from './IconArrowTopRight.vue'; + const props = defineProps<{ chain: Chain; value: Hex; }>(); +const { requestLabels, getLabelText } = useLabels(); + +const fourByteService = new FourByteService(); + const calls = computed(() => { return decodeCallData(props.value); }); + +const addresses = computed(() => { + return calls.value.map((call) => call.to.toLowerCase() as Address); +}); + +watch( + addresses, + () => { + requestLabels(props.chain, addresses.value); + }, + { + immediate: true, + }, +); + +function isFunctionSelector(value: string): value is Hex { + const selectorRegex = /^0x[a-fA-F0-9]{8}$/; + return selectorRegex.test(value); +} + +const functions = computed(() => { + return calls.value + .map((call) => { + const action = toAction(props.chain, call); + return action + .map((part) => part.value) + .filter((value) => isFunctionSelector(value)); + }) + .flat(); +}); +const functionSignatures = ref>({}); + +watch( + functions, + () => { + fetchFunctions(); + }, + { + immediate: true, + }, +); + +async function fetchFunctions(): Promise { + for (const func of functions.value) { + const signature = await fourByteService.getSignature(func as Hex); + functionSignatures.value[func as Hex] = signature; + } +} + +function getAddressLabel(address: Address, format: boolean): string { + const labelText = getLabelText(props.chain, address.toLowerCase() as Address); + if (labelText) { + return labelText; + } + if (!format) { + return address; + } + return formatAddress(address); +} + +function getValueLabel(value: string): string { + if (!isFunctionSelector(value)) { + return value; + } + const signature = functionSignatures.value[value]; + if (signature) { + const parts = signature.split('('); + const firstPart = parts[0]; + if (!firstPart) { + return value; + } + return firstPart; + } + return value; +} + +function formatAddress(address: Address): string { + return address.slice(0, 8) + '…' + address.slice(-6); +} + +function getAddressExplorerUrl(chain: Chain, address: Address): string { + const explorerUrl = getExplorerUrl(chain); + return `${explorerUrl}/address/${address}`; +}