22
33import { useState } from "react" ;
44import type { NextPage } from "next" ;
5- import { concat , createPublicClient , hexToString , http , isAddress , keccak256 , pad , toHex } from "viem" ;
6- import { RocketLaunchIcon } from "@heroicons/react/24/outline" ;
75import { Interpretations , NetworkSelector } from "~~/components/storagoor" ;
6+ import { StorageForm } from "~~/components/storagoor/StorageForm" ;
87import { useTargetNetwork } from "~~/hooks/scaffold-eth" ;
9-
10- interface StorageValueFormats {
11- hex : string ;
12- decimal : string ;
13- string : string ;
14- address : string ;
15- }
8+ import { useStorageSlot } from "~~/hooks/storagoor/useStorageSlot" ;
169
1710const Home : NextPage = ( ) => {
1811 const [ contractAddress , setContractAddress ] = useState ( "" ) ;
@@ -21,102 +14,19 @@ const Home: NextPage = () => {
2114 const [ isDoubleMapping , setIsDoubleMapping ] = useState ( false ) ;
2215 const [ mappingKey , setMappingKey ] = useState ( "" ) ;
2316 const [ secondMappingKey , setSecondMappingKey ] = useState ( "" ) ;
24- const [ storageValue , setStorageValue ] = useState < StorageValueFormats | null > ( null ) ;
25- const [ isLoading , setIsLoading ] = useState ( false ) ;
26- const { targetNetwork } = useTargetNetwork ( ) ;
27-
28- const formatStorageValue = ( value : `0x${string } `) : StorageValueFormats => {
29- // Convert to BigInt for decimal representation
30- const bigIntValue = BigInt ( value ) ;
31-
32- // Try to interpret as string (skip if not valid UTF-8)
33- let stringValue = "" ;
34- try {
35- stringValue = hexToString ( value , { size : 32 } ) ;
36- // Filter out non-printable characters
37- stringValue = stringValue . replace ( / [ ^ \x20 - \x7E ] / g, "" ) ;
38- if ( ! stringValue . length ) stringValue = "Not a valid UTF-8 string" ;
39- } catch {
40- stringValue = "Not a valid UTF-8 string" ;
41- }
42-
43- return {
44- hex : value ,
45- decimal : bigIntValue . toString ( ) ,
46- string : stringValue ,
47- address : isAddress ( value . slice ( 0 , 42 ) ) ? value . slice ( 0 , 42 ) : "Not a valid address" ,
48- } ;
49- } ;
50-
51- const fetchStorageSlot = async ( ) => {
52- try {
53- setIsLoading ( true ) ;
54- if ( ! contractAddress || ! slot || ! isAddress ( contractAddress ) ) return ;
55- if ( isMapping && ! mappingKey ) return ;
56-
57- const publicClient = createPublicClient ( {
58- chain : targetNetwork ,
59- transport : http ( targetNetwork . rpcUrls . default . http [ 0 ] ) ,
60- } ) ;
61-
62- let finalSlot : `0x${string } `;
63- if ( isMapping ) {
64- const mappingSlot = pad ( toHex ( BigInt ( slot ) ) , { size : 32 } ) ;
65- let paddedKey : `0x${string } `;
66- let paddedSecondKey : `0x${string } `;
67-
68- // Handle first key
69- if ( isAddress ( mappingKey ) ) {
70- paddedKey = pad ( mappingKey as `0x${string } `, { size : 32 } ) ;
71- } else {
72- try {
73- const keyBigInt = BigInt ( mappingKey ) ;
74- paddedKey = pad ( toHex ( keyBigInt ) , { size : 32 } ) ;
75- } catch {
76- paddedKey = pad ( mappingKey as `0x${string } `, { size : 32 } ) ;
77- }
78- }
7917
80- if ( isDoubleMapping && secondMappingKey ) {
81- // Handle second key
82- if ( isAddress ( secondMappingKey ) ) {
83- paddedSecondKey = pad ( secondMappingKey as `0x${string } `, { size : 32 } ) ;
84- } else {
85- try {
86- const keyBigInt = BigInt ( secondMappingKey ) ;
87- paddedSecondKey = pad ( toHex ( keyBigInt ) , { size : 32 } ) ;
88- } catch {
89- paddedSecondKey = pad ( secondMappingKey as `0x${string } `, { size : 32 } ) ;
90- }
91- }
92- // For double mapping: keccak256(key2 + keccak256(key1 + slot))
93- const firstHash = keccak256 ( concat ( [ paddedKey , mappingSlot ] ) ) ;
94- finalSlot = keccak256 ( concat ( [ paddedSecondKey , firstHash ] ) ) ;
95- } else {
96- // Single mapping: keccak256(key + slot)
97- finalSlot = keccak256 ( concat ( [ paddedKey , mappingSlot ] ) ) ;
98- }
99- } else {
100- finalSlot = toHex ( BigInt ( slot ) , { size : 32 } ) ;
101- }
102-
103- const value = await publicClient . getStorageAt ( {
104- address : contractAddress as `0x${string } `,
105- slot : finalSlot ,
106- } ) ;
107-
108- if ( ! value ) {
109- setStorageValue ( null ) ;
110- return ;
111- }
112-
113- setStorageValue ( formatStorageValue ( value ) ) ;
114- } catch ( error ) {
115- console . error ( "Error fetching storage slot:" , error ) ;
116- setStorageValue ( null ) ;
117- } finally {
118- setIsLoading ( false ) ;
119- }
18+ const { targetNetwork } = useTargetNetwork ( ) ;
19+ const { readStorageSlot, storageValue, isLoading, error } = useStorageSlot ( targetNetwork ) ;
20+
21+ const handleSubmit = ( ) => {
22+ readStorageSlot ( {
23+ contractAddress,
24+ slot,
25+ isMapping,
26+ isDoubleMapping,
27+ mappingKey,
28+ secondMappingKey,
29+ } ) ;
12030 } ;
12131
12232 return (
@@ -131,120 +41,27 @@ const Home: NextPage = () => {
13141
13242 { /* Main Card */ }
13343 < div className = "bg-base-100 rounded-2xl shadow-xl border border-base-300" >
134- { /* Network Selector */ }
13544 < NetworkSelector />
136- { /* Input Form */ }
137- < div className = "p-6 space-y-6" >
138- < div >
139- < label className = "label" >
140- < span className = "label-text font-semibold" > Contract Address</ span >
141- </ label >
142- < input
143- type = "text"
144- placeholder = "0x..."
145- className = "input input-bordered w-full font-mono"
146- value = { contractAddress }
147- onChange = { e => setContractAddress ( e . target . value ) }
148- />
149- </ div >
150-
151- < div >
152- < label className = "label" >
153- < span className = "label-text font-semibold" > Storage Slot</ span >
154- </ label >
155- < input
156- type = "text"
157- placeholder = "0x0"
158- className = "input input-bordered w-full font-mono"
159- value = { slot }
160- onChange = { e => setSlot ( e . target . value ) }
161- />
162- </ div >
163-
164- < div className = "flex items-center justify-between p-2 rounded-lg bg-base-200" >
165- < span className = "font-semibold px-2" > Mapping Mode</ span >
166- < input
167- type = "checkbox"
168- className = "toggle toggle-primary"
169- checked = { isMapping }
170- onChange = { e => {
171- setIsMapping ( e . target . checked ) ;
172- if ( ! e . target . checked ) {
173- setIsDoubleMapping ( false ) ;
174- }
175- } }
176- />
177- </ div >
178-
179- { isMapping && (
180- < >
181- < div className = "flex items-center justify-between p-2 rounded-lg bg-base-200" >
182- < span className = "font-semibold px-2" > Double Mapping</ span >
183- < input
184- type = "checkbox"
185- className = "toggle toggle-primary"
186- checked = { isDoubleMapping }
187- onChange = { e => setIsDoubleMapping ( e . target . checked ) }
188- />
189- </ div >
190-
191- < div className = "animate-fadeIn" >
192- < label className = "label" >
193- < span className = "label-text font-semibold" > First Mapping Key</ span >
194- < span className = "label-text-alt" > address, number, or string</ span >
195- </ label >
196- < input
197- type = "text"
198- placeholder = "Key value..."
199- className = "input input-bordered w-full font-mono"
200- value = { mappingKey }
201- onChange = { e => setMappingKey ( e . target . value ) }
202- />
203- </ div >
204-
205- { isDoubleMapping && (
206- < div className = "animate-fadeIn" >
207- < label className = "label" >
208- < span className = "label-text font-semibold" > Second Mapping Key</ span >
209- < span className = "label-text-alt" > address, number, or string</ span >
210- </ label >
211- < input
212- type = "text"
213- placeholder = "Second key value..."
214- className = "input input-bordered w-full font-mono"
215- value = { secondMappingKey }
216- onChange = { e => setSecondMappingKey ( e . target . value ) }
217- />
218- </ div >
219- ) }
220- </ >
221- ) }
22245
223- < button
224- className = "btn btn-primary w-full"
225- onClick = { fetchStorageSlot }
226- disabled = {
227- ! isAddress ( contractAddress ) ||
228- ( isMapping && ! mappingKey ) ||
229- ( isDoubleMapping && ! secondMappingKey ) ||
230- isLoading
231- }
232- >
233- { isLoading ? (
234- < >
235- < span className = "loading loading-spinner" > </ span >
236- Reading Storage...
237- </ >
238- ) : (
239- < >
240- < RocketLaunchIcon className = "h-5 w-5" />
241- Read Storage Slot
242- </ >
243- ) }
244- </ button >
245- </ div >
46+ < StorageForm
47+ contractAddress = { contractAddress }
48+ setContractAddress = { setContractAddress }
49+ slot = { slot }
50+ setSlot = { setSlot }
51+ isMapping = { isMapping }
52+ setIsMapping = { setIsMapping }
53+ isDoubleMapping = { isDoubleMapping }
54+ setIsDoubleMapping = { setIsDoubleMapping }
55+ mappingKey = { mappingKey }
56+ setMappingKey = { setMappingKey }
57+ secondMappingKey = { secondMappingKey }
58+ setSecondMappingKey = { setSecondMappingKey }
59+ onSubmit = { handleSubmit }
60+ isLoading = { isLoading }
61+ />
62+
63+ { error && < div className = "p-4 mx-6 mb-6 text-error bg-error/10 rounded-lg" > { error } </ div > }
24664
247- { /* Results Section */ }
24865 { storageValue && < Interpretations storageValue = { storageValue } /> }
24966 </ div >
25067 </ div >
0 commit comments