Skip to content

Commit ab57e16

Browse files
Add calldata and tx decoder tabs (#148)
* Add calldata and tx decoder tabs * Add alert
1 parent 877c7c3 commit ab57e16

File tree

30 files changed

+601
-42
lines changed

30 files changed

+601
-42
lines changed

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"postcss": "8.4.29",
4141
"react": "18.2.0",
4242
"react-dom": "18.2.0",
43+
"react-hook-form": "^7.53.1",
4344
"tailwind-merge": "^1.14.0",
4445
"tailwindcss": "3.3.3",
4546
"tailwindcss-animate": "^1.0.7",
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react'
2+
import DecodingForm from '@/app/calldata/form'
3+
import { decodeCalldata, getRawCalldata } from '@/lib/decode'
4+
5+
export default async function CalldataPage({ params }: { params: { hash: string; chainID: number } }) {
6+
const res = await getRawCalldata(params.hash, params.chainID)
7+
8+
if (!res) {
9+
return <DecodingForm data={''} chainID={params.chainID} />
10+
}
11+
12+
const { data, contractAddress } = res
13+
14+
try {
15+
const decoded = data
16+
? await decodeCalldata({
17+
data,
18+
chainID: params.chainID,
19+
contractAddress,
20+
})
21+
: undefined
22+
23+
return <DecodingForm decoded={decoded} data={data} contractAddress={contractAddress} chainID={params.chainID} />
24+
} catch (error) {
25+
console.error('Error decoding calldata:', error)
26+
return <DecodingForm data={data} />
27+
}
28+
}
File renamed without changes.

apps/web/src/app/calldata/form.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use client'
2+
import * as React from 'react'
3+
import { Label } from '@/components/ui/label'
4+
import { Textarea } from '@/components/ui/textarea'
5+
import { Button } from '@/components/ui/button'
6+
import CodeBlock from '@/components/ui/code-block'
7+
import { NetworkSelect } from '@/components/ui/network-select'
8+
import { Input } from '@/components/ui/input'
9+
import { ExampleTransactions } from '../../components/ui/examples'
10+
import { useCalldataForm } from './useCalldataForm'
11+
import { CalldataFormProps } from './types'
12+
13+
const PATH = 'calldata'
14+
15+
export default function DecodingForm({ decoded, contractAddress, chainID, data, isLoading }: CalldataFormProps) {
16+
const { form, onSubmit } = useCalldataForm({
17+
data,
18+
chainID,
19+
contractAddress,
20+
})
21+
22+
return (
23+
<div className="grid h-full items-stretch gap-6 grid-cols-1 lg:grid-cols-[1fr_200px]">
24+
<div className="md:order-1 flex flex-col space-y-4">
25+
<form onSubmit={form.handleSubmit(onSubmit)}>
26+
<div className="flex w-full gap-2 flex-col">
27+
<Textarea
28+
className="min-h-[100px] flex-1"
29+
placeholder="Paste transaction calldata or click on examples"
30+
{...form.register('data', { required: true })}
31+
/>
32+
<div className="flex w-full lg:items-center gap-2 flex-col lg:flex-row">
33+
<div>
34+
<NetworkSelect
35+
defaultValue={form.watch('chainID')}
36+
onValueChange={(value) => {
37+
form.setValue('chainID', value)
38+
}}
39+
/>
40+
</div>
41+
<Input
42+
className="flex-1"
43+
placeholder="Optional: contract address"
44+
defaultValue={contractAddress}
45+
{...form.register('contractAddress')}
46+
/>
47+
<div className="flex gap-2">
48+
<Button type="submit" disabled={isLoading}>
49+
{isLoading ? 'Decoding...' : 'Decode'}
50+
</Button>
51+
</div>
52+
</div>
53+
</div>
54+
</form>
55+
56+
{decoded && (
57+
<div className="grid gap-6 h-full">
58+
<div className="flex flex-col gap-2 min-h-[40vh] lg:min-h-[initial]">
59+
<Label>Decoded calldata:</Label>
60+
<CodeBlock language="json" value={JSON.stringify(decoded, null, 2)} readonly lineNumbers={false} />
61+
</div>
62+
</div>
63+
)}
64+
</div>
65+
66+
<div className="md:order-2">
67+
<ExampleTransactions path={PATH} />
68+
</div>
69+
</div>
70+
)
71+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Label } from '@/components/ui/label'
2+
import { ExampleTransactions } from '@/components/ui/examples'
3+
import { Input } from '@/components/ui/input'
4+
import { Button } from '@/components/ui/button'
5+
import { NetworkSelect } from '@/components/ui/network-select'
6+
7+
export default function Loading() {
8+
return (
9+
<div className="grid h-full items-stretch gap-6 grid-cols-1 lg:grid-cols-[1fr_200px] animate-pulse">
10+
<div className="md:order-1 flex flex-col space-y-4">
11+
<form>
12+
<div className="flex w-full gap-2 flex-col">
13+
<div className="min-h-[100px] flex-1 bg-muted rounded-md" />
14+
<div className="flex w-full lg:items-center gap-2 flex-col lg:flex-row">
15+
<div>
16+
<NetworkSelect disabled={true} />
17+
</div>
18+
<Input className="flex-1" placeholder="Optional: contract address" disabled={true} />
19+
<div className="flex gap-2">
20+
<Button type="submit" disabled={true}>
21+
Decode
22+
</Button>
23+
</div>
24+
</div>
25+
</div>
26+
</form>
27+
28+
<div className="grid gap-6 h-full">
29+
<div className="flex flex-col gap-2 min-h-[40vh] lg:min-h-[initial]">
30+
<Label>Decoded calldata:</Label>
31+
<div className="flex flex-1 bg-muted rounded-md"></div>
32+
</div>
33+
</div>
34+
</div>
35+
36+
<div className="md:order-2">
37+
<ExampleTransactions path="calldata" />
38+
</div>
39+
</div>
40+
)
41+
}

apps/web/src/app/calldata/page.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as React from 'react'
2+
import DecodingForm from './form'
3+
import { decodeCalldata } from '@/lib/decode'
4+
import { CalldataParams } from './types'
5+
6+
export default async function CalldataPage({ searchParams }: { searchParams: CalldataParams }) {
7+
const chainID = Number(searchParams.chainID)
8+
try {
9+
const decoded = searchParams.data
10+
? await decodeCalldata({
11+
data: searchParams.data,
12+
chainID,
13+
contractAddress: searchParams.contractAddress,
14+
})
15+
: undefined
16+
17+
return (
18+
<DecodingForm
19+
decoded={decoded}
20+
chainID={chainID}
21+
data={searchParams.data}
22+
contractAddress={searchParams.contractAddress}
23+
/>
24+
)
25+
} catch (error) {
26+
console.error('Error decoding calldata:', error)
27+
return <DecodingForm chainID={chainID} data={searchParams.data} contractAddress={searchParams.contractAddress} />
28+
}
29+
}

apps/web/src/app/calldata/types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { DecodeResult } from '@3loop/transaction-decoder'
2+
3+
export interface CalldataParams {
4+
data: string
5+
chainID: string
6+
contractAddress?: string
7+
}
8+
9+
export interface CalldataFormProps {
10+
data?: string
11+
chainID?: number
12+
contractAddress?: string
13+
decoded?: DecodeResult
14+
isLoading?: boolean
15+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useForm } from 'react-hook-form'
2+
import { useRouter } from 'next/navigation'
3+
import { CalldataParams } from './types'
4+
import { DEFAULT_CHAIN_ID } from '@/app/data'
5+
6+
export function useCalldataForm(initialData?: { data?: string; chainID?: number; contractAddress?: string }) {
7+
const router = useRouter()
8+
const form = useForm<CalldataParams>({
9+
defaultValues: {
10+
data: initialData?.data || '',
11+
chainID: (initialData?.chainID || DEFAULT_CHAIN_ID).toString(),
12+
contractAddress: initialData?.contractAddress || '',
13+
},
14+
})
15+
16+
const onSubmit = (formData: CalldataParams) => {
17+
let path = `/calldata?data=${formData.data}`
18+
19+
if (formData.chainID && formData.contractAddress) {
20+
path += `&chainID=${formData.chainID}&contractAddress=${formData.contractAddress}`
21+
}
22+
23+
router.push(path)
24+
}
25+
26+
return {
27+
form,
28+
onSubmit,
29+
}
30+
}

apps/web/src/app/data.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,14 @@ export const defaultTrasaction = EXAMPLE_TXS['AAVE V2'][0]
179179
export const DEFAULT_CONTRACT = aaveV2
180180
export const DEFAULT_CHAIN_ID = 1
181181

182-
const generateNavItems = (transactions: any) => {
182+
const generateNavItems = (transactions: any, path: string) => {
183183
return transactions.map((tx: any) => ({
184-
href: `/tx/${tx.chainID}/${tx.hash}`,
184+
href: `/${path}/${tx.chainID}/${tx.hash}`,
185185
title: `${tx.name}`,
186186
}))
187187
}
188188

189-
export const sidebarNavItems = Object.fromEntries(
190-
Object.entries(EXAMPLE_TXS).map(([key, value]) => [key, generateNavItems(value)]),
191-
)
189+
export const geSidebarNavItems = (path: string) =>
190+
Object.fromEntries(Object.entries(EXAMPLE_TXS).map(([key, value]) => [key, generateNavItems(value, path)]))
192191

193192
export const INTERPRETER_REPO = 'https://github.com/3loop/loop-decoder/tree/main/packages/transaction-interpreter'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use client'
2+
3+
export default function Custom500() {
4+
return <h1>Opps, something went wrong.</h1>
5+
}

0 commit comments

Comments
 (0)