Skip to content

Commit

Permalink
Decode call data addresses and functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Destiner committed Apr 29, 2024
1 parent bafb738 commit 7c41d3c
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 46 deletions.
187 changes: 159 additions & 28 deletions src/components/ViewCallData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
}}
</span>
</div>
</div>
Expand All @@ -34,7 +43,15 @@
<div class="action-details">
<div class="action-detail">
<div class="action-detail-label">Address</div>
<div class="action-detail-value">{{ call.to }}</div>
<div class="action-detail-value">
{{ getAddressLabel(call.to, false) }}
<a
:href="getAddressExplorerUrl(chain, call.to)"
target="_blank"
>
<IconArrowTopRight class="icon" />
</a>
</div>
</div>
<div class="action-detail">
<div class="action-detail-label">Value</div>
Expand Down Expand Up @@ -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<Call[]>(() => {
return decodeCallData(props.value);
});
const addresses = computed<Address[]>(() => {
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<Record<Hex, string | null>>({});
watch(
functions,
() => {
fetchFunctions();
},
{
immediate: true,
},
);
async function fetchFunctions(): Promise<void> {
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}`;
}
</script>

<style scoped>
Expand All @@ -99,6 +208,46 @@ const calls = computed<Call[]>(() => {
background: var(--color-background-secondary);
}
.action-details {
display: flex;
flex-direction: column;
margin: 16px 0 4px;
gap: 4px;
}
.action-detail {
display: flex;
align-items: baseline;
}
.action-detail-label {
width: 120px;
color: var(--color-text-secondary);
font-size: 14px;
}
.action-detail-value {
display: flex;
align-items: center;
gap: 4px;
width: calc(100% - 120px);
overflow-wrap: break-word;
a {
color: inherit;
}
.icon {
width: 16px;
height: 16px;
opacity: 0.5;
&:hover {
opacity: 1;
}
}
}
.action-trigger {
display: flex;
align-items: center;
Expand Down Expand Up @@ -141,29 +290,6 @@ const calls = computed<Call[]>(() => {
}
}
.action-details {
display: flex;
flex-direction: column;
margin: 16px 0 4px;
gap: 4px;
}
.action-detail {
display: flex;
align-items: baseline;
}
.action-detail-label {
width: 120px;
color: var(--color-text-secondary);
font-size: 14px;
}
.action-detail-value {
width: calc(100% - 120px);
overflow-wrap: break-word;
}
@keyframes slide-down {
from {
height: 0;
Expand Down Expand Up @@ -202,6 +328,11 @@ const calls = computed<Call[]>(() => {
flex-wrap: wrap;
}
.action-part.highlight {
color: var(--color-accent);
font-family: var(--font-mono);
}
.call-data {
--font-size: 14px;
--line-height: 1.2;
Expand Down
42 changes: 24 additions & 18 deletions src/utils/entryPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ interface Call {
}

interface ActionPart {
label: string;
type: 'text' | 'address';
value: string;
type: 'text' | 'value' | 'address';
}

type Action = ActionPart[];
Expand Down Expand Up @@ -619,25 +618,32 @@ function toAction(chain: Chain, call: Call): Action {
return `${slice(address, 0, 6)}...${slice(address, -4)}`;
}

if (call.value > 0n && size(call.data) === 0) {
// Native asset transfer
return [
{ label: 'Transfer', type: 'text', value: '' },
{
label: formatNative(chain, call.value),
type: 'text',
value: '',
},
{ label: 'to', type: 'text', value: '' },
{ label: formatAddress(call.to), type: 'address', value: '' },
];
if (size(call.data) === 0) {
if (call.value === 0n) {
return [
{ value: 'Call', type: 'text' },
{ value: formatAddress(call.to), type: 'address' },
{ value: 'contract', type: 'text' },
];
} else {
// Native asset transfer
return [
{ value: 'Transfer', type: 'text' },
{
value: formatNative(chain, call.value),
type: 'value',
},
{ value: 'to', type: 'text' },
{ value: formatAddress(call.to), type: 'address' },
];
}
}

const parts: ActionPart[] = [
{ label: 'Call function', type: 'text', value: '' },
{ label: slice(call.data, 0, 4), type: 'text', value: '' },
{ label: 'in contract', type: 'text', value: '' },
{ label: formatAddress(call.to), type: 'address', value: '' },
{ value: 'Call function', type: 'text' },
{ value: slice(call.data, 0, 4), type: 'value' },
{ value: 'in contract', type: 'text' },
{ value: formatAddress(call.to), type: 'address' },
];
return parts;
}
Expand Down

0 comments on commit 7c41d3c

Please sign in to comment.