-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
902 additions
and
329 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import '../styles.css'; // Import the global CSS here | ||
import { AppProps } from 'next/app'; | ||
import "../styles.css"; // Import the global CSS here | ||
import { AppProps } from "next/app"; | ||
|
||
function MyApp({ Component, pageProps }: AppProps) { | ||
return <Component {...pageProps} />; | ||
} | ||
|
||
export default MyApp; | ||
export default MyApp; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
import React, { useState } from "react"; | ||
import { | ||
simulate, | ||
BundlerUserOperationData, | ||
PackedUserOperation, | ||
unwrap, | ||
wrap, | ||
SimulateType, | ||
getUserOpHash, | ||
INFRA_ADDRESS, | ||
} from "@consenlabs/imaccount-sdk"; | ||
import { Address } from "viem"; | ||
import Header from "./header"; | ||
|
||
const Debugger = () => { | ||
// Tenderly Config | ||
const [TENDERLY_ACCESS_KEY, setTENDERLY_ACCESS_KEY] = useState(process.env.NEXT_PUBLIC_TENDERLY_ACCESS_KEY || ""); | ||
const [projectOwner, setProjectOwner] = useState("ChiHaoLu"); | ||
const [projectName, setProjectName] = useState("aatest"); | ||
const [richBundlerEOA, setRichBundlerEOA] = useState<Address>( | ||
"0x4d7f573039fddc84fdb28515ba20d75ef6b987ff" | ||
); | ||
const [network, setNetwork] = useState("11155111"); | ||
const [blockNumber, setBlockNumber] = useState("latest"); | ||
const [simulateType, setSimulateType] = useState<SimulateType>( | ||
SimulateType.Send | ||
); | ||
|
||
// userOp input | ||
const [input, setInput] = useState(`{ | ||
sender: "0x8b8aF275602891ddDf4d60783A80A01bf56f8F64", | ||
nonce: "0x31", | ||
callData: "0xe9ae5c53010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001842b67b570000000000000000000000000f7728cf90ad4cb9a14342827641056fd2e88a82e0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c72380000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000066f0e1be000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc450000000000000000000000000000000000000000000000000000000066f0e1be000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000557a61f7aa49341fb0ef67b1376f84b72bf1063a678417b73b0a2bc06100a7b3d880f51c8837b46a14fa235ac1441ad436681fb0d73e39e869f79f52f46212fc9575f2bd6cb9c522f32be0e1ba49f0e2a3987c018e1c000000000000000000000000000000000000000000000000000000000000000000000000000000", | ||
callGasLimit: "0x0", | ||
verificationGasLimit: "0x0", | ||
preVerificationGas: "0x0", | ||
maxFeePerGas: "0x1", | ||
maxPriorityFeePerGas: "0x1", | ||
paymasterVerificationGasLimit: "0x0", | ||
paymasterPostOpGasLimit: "0x0", | ||
signature:"0x7a61f7aa49341fb0ef67b1376f84b72bf1063a67fffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c" | ||
}`); | ||
const [parsedInput, setParsedInput] = useState<{ | ||
unwrapped: BundlerUserOperationData; | ||
wrapped: PackedUserOperation; | ||
serialized: string; | ||
} | null>(null); | ||
const [parseError, setParseError] = useState<string | null>(null); | ||
|
||
// Result | ||
const [loading, setLoading] = useState(false); | ||
const [result, setResult] = useState(""); | ||
|
||
const isSimulateDisabled = | ||
!TENDERLY_ACCESS_KEY || !projectOwner || !projectName || !richBundlerEOA; | ||
|
||
const handleSimulate = async () => { | ||
if ( | ||
!TENDERLY_ACCESS_KEY || | ||
!projectOwner || | ||
!projectName || | ||
!richBundlerEOA || | ||
!parsedInput | ||
) { | ||
alert("Please fill in all required fields."); | ||
return; | ||
} | ||
|
||
setLoading(true); | ||
try { | ||
const simulationResult = await simulate( | ||
simulateType, | ||
TENDERLY_ACCESS_KEY, | ||
richBundlerEOA, | ||
parsedInput.wrapped, | ||
projectOwner, | ||
projectName, | ||
network, | ||
blockNumber | ||
); | ||
|
||
setResult(JSON.stringify(simulationResult, null, 2)); | ||
} catch (error) { | ||
console.error(error); | ||
alert(`Simulation failed.\n${error}`); | ||
setResult(""); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
function serialize(parsed: any): Record<string, any> { | ||
return Object.entries(parsed).reduce( | ||
(acc, [key, value]) => { | ||
if ( | ||
typeof value === "string" && | ||
/^0x[a-fA-F0-9]+$/.test(value) && | ||
(key.includes("gas") || key.includes("Gas")) && | ||
key !== "accountGasLimits" && | ||
key !== "gasFees" | ||
) { | ||
acc[key] = String(BigInt(value)); | ||
} else if (typeof value === "bigint") { | ||
acc[key] = String(value); | ||
} else { | ||
acc[key] = value; | ||
} | ||
return acc; | ||
}, | ||
{} as Record<string, any> | ||
); | ||
} | ||
|
||
function parse(parsed: { | ||
unwrapped: BundlerUserOperationData; | ||
wrapped: PackedUserOperation; | ||
}): string { | ||
return JSON.stringify( | ||
{ | ||
wrapped: serialize(parsed.wrapped), | ||
unwrapped: serialize(parsed.unwrapped), | ||
}, | ||
null, | ||
2 | ||
); | ||
} | ||
|
||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
setResult(""); | ||
const text = e.target.value; | ||
setInput(text); | ||
try { | ||
const parsed = JSON.parse( | ||
text | ||
.replace(/([a-zA-Z0-9]+):/g, '"$1":') // Add quotes around keys | ||
.replace(/'/g, '"') // Replace single quotes with double quotes | ||
); | ||
|
||
// Check and transform BigInt values where necessary | ||
if (parsed.accountGasLimits !== undefined) { | ||
const serialized = parse({ | ||
wrapped: parsed as PackedUserOperation, | ||
unwrapped: unwrap(parsed as PackedUserOperation), | ||
}); | ||
setParsedInput({ | ||
wrapped: parsed as PackedUserOperation, | ||
unwrapped: unwrap(parsed as PackedUserOperation), | ||
serialized: serialized, | ||
}); | ||
} else { | ||
const serialized = parse({ | ||
wrapped: wrap(parsed as BundlerUserOperationData), | ||
unwrapped: parsed as BundlerUserOperationData, | ||
}); | ||
setParsedInput({ | ||
wrapped: wrap(parsed as BundlerUserOperationData), | ||
unwrapped: parsed as BundlerUserOperationData, | ||
serialized: serialized, | ||
}); | ||
} | ||
setParseError(null); | ||
} catch (error) { | ||
setParseError(`Invalid JSON format.\n${error}`); | ||
setParsedInput(null); | ||
} | ||
}; | ||
|
||
const handleResultClick = () => { | ||
const url = result.startsWith('"') ? JSON.parse(result) : result; | ||
window.open(url, "_blank"); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<Header /> | ||
|
||
<h2>AA Simulator</h2> | ||
<h3>1. Fill Up the Tenderly Config</h3> | ||
<div className="warning-banner"> | ||
<p> | ||
This default configuration currently only supports the Sepolia | ||
Testnet. | ||
<br /> If you wish to use a different network, please create a | ||
Tenderly project on your own. | ||
</p> | ||
</div> | ||
<div> | ||
<label> | ||
TENDERLY_ACCESS_KEY: | ||
<input | ||
type="text" | ||
value={TENDERLY_ACCESS_KEY} | ||
onChange={(e) => setTENDERLY_ACCESS_KEY(e.target.value)} | ||
required | ||
/> | ||
</label> | ||
</div> | ||
<div> | ||
<label> | ||
Project Owner: | ||
<input | ||
type="text" | ||
value={projectOwner} | ||
onChange={(e) => setProjectOwner(e.target.value)} | ||
required | ||
/> | ||
</label> | ||
</div> | ||
<div> | ||
<label> | ||
Project Name: | ||
<input | ||
type="text" | ||
value={projectName} | ||
onChange={(e) => setProjectName(e.target.value)} | ||
required | ||
/> | ||
</label> | ||
</div> | ||
<div> | ||
<label> | ||
Rich Bundler EOA: | ||
<input | ||
type="text" | ||
value={richBundlerEOA} | ||
onChange={(e) => setRichBundlerEOA(e.target.value as Address)} | ||
required | ||
/> | ||
</label> | ||
</div> | ||
<div> | ||
<label> | ||
Chain ID: | ||
<input | ||
type="text" | ||
value={network} | ||
onChange={(e) => setNetwork(e.target.value)} | ||
required | ||
/> | ||
</label> | ||
</div> | ||
|
||
<div> | ||
<label> | ||
Block Number: | ||
<input | ||
type="text" | ||
value={blockNumber} | ||
onChange={(e) => setBlockNumber(e.target.value)} | ||
required | ||
/> | ||
</label> | ||
</div> | ||
|
||
<h3>2. Give your userOp</h3> | ||
<h4>Simulate Usage</h4> | ||
<div> | ||
<label> | ||
Simulate Type (where you meet the problem?):{" "} | ||
<select | ||
value={simulateType} | ||
onChange={(e) => setSimulateType(e.target.value as SimulateType)} | ||
> | ||
{Object.values(SimulateType).map((type) => ( | ||
<option key={type} value={type}> | ||
{type} | ||
</option> | ||
))} | ||
</select> | ||
</label> | ||
</div> | ||
<h4>UserOp Input</h4> | ||
<div> | ||
<label> | ||
<textarea | ||
rows={30} | ||
cols={100} | ||
value={input} | ||
onChange={handleInputChange} | ||
placeholder="Paste your User Operation in JSON here" | ||
required | ||
/> | ||
</label> | ||
{parseError && <p style={{ color: "red" }}>{parseError}</p>} | ||
{parsedInput && ( | ||
<div> | ||
<h5>UserOp Hash:</h5> | ||
<pre> | ||
{getUserOpHash( | ||
parsedInput.wrapped, | ||
INFRA_ADDRESS.ENTRY_POINT, | ||
Number(network) | ||
)} | ||
</pre> | ||
<h5>Parsed Input Data:</h5> | ||
<pre>{parsedInput.serialized}</pre> | ||
</div> | ||
)} | ||
</div> | ||
|
||
<h3>3. Simulate Time!</h3> | ||
<button onClick={handleSimulate} disabled={loading || isSimulateDisabled}> | ||
{loading ? "Simulating..." : "Run Simulation"} | ||
</button> | ||
{result !== "" && ( | ||
<div className="simulation-result"> | ||
<h4>Simulation Result:</h4> | ||
<button onClick={handleResultClick}> | ||
Go to Dashboard! (required login Tenderly) | ||
</button> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default Debugger; |
Oops, something went wrong.