diff --git a/examples/with-web3js-playground/src/components/ButtonWithResult.tsx b/examples/with-web3js-playground/src/components/ButtonWithResult.tsx new file mode 100644 index 00000000..a5e4ee55 --- /dev/null +++ b/examples/with-web3js-playground/src/components/ButtonWithResult.tsx @@ -0,0 +1,47 @@ +import { FC, ReactNode } from 'react'; +import { isEmpty } from '../utils'; +interface ButtonWithResultProps { + action: () => void; + result?: any; + label: string; + resultIsJson?: boolean; + children?: ReactNode; +} + +const ButtonWithResult: FC = ({ + action, + result = '', + label, + resultIsJson = false, + children, +}) => { + function replacer(key: string, value: any) { + if (typeof value === 'bigint') { + return { + type: 'bigint', + value: value.toString(), + }; + } else { + return value; + } + } + const resultString = (result: any) => JSON.stringify(result, replacer); + + return ( + <> +
+ + {children} +
+ {!isEmpty(result) && ( + + {label}: {resultIsJson ? resultString(result) : result} + + )} + + ); +}; + +export default ButtonWithResult; diff --git a/examples/with-web3js-playground/src/pages/index.tsx b/examples/with-web3js-playground/src/pages/index.tsx index 9494e685..d978930e 100644 --- a/examples/with-web3js-playground/src/pages/index.tsx +++ b/examples/with-web3js-playground/src/pages/index.tsx @@ -1,38 +1,57 @@ -import Head from "next/head"; -import { useState } from "react"; -import { web3, bloctoSDK } from "../services/ethereum"; - +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import Head from 'next/head'; +import { useState } from 'react'; +import { web3, bloctoSDK } from '../services/ethereum'; +import ButtonWithResult from '../components/ButtonWithResult'; const userOp = { - sender: "0x9fd042a18e90ce326073fa70f111dc9d798d9a52", - nonce: "123", - init_code: "0x68656c6c6f", - call_data: "0x776F726C64", - call_gas_limit: "1000", - verification_gas_limit: "2300", - pre_verification_gas: "3100", - max_fee_per_gas: "8500", - max_priority_fee_per_gas: "1", - paymaster_and_data: "0x626c6f63746f", - signature: "0x636c656d656e74", + sender: '0x9fd042a18e90ce326073fa70f111dc9d798d9a52', + nonce: '123', + initCode: '0x68656c6c6f', + callData: '0x776F726C64', + callGasLimit: '1000', + verificationGasLimit: '2300', + preVerificationGas: '3100', + maxFeePerGas: '8500', + maxPriorityFeePerGas: '1', + paymasterAndData: '0x626c6f63746f', + signature: '0x636c656d656e74', }; +const callData = + 'b61d27f600000000000000000000000000005ea00ac477b1030ce78506496e8c2de24bf5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084161ac21f000000000000000000000000fd8ec18d48ac1f46b600e231da07d1da8209ceef0000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000'; + export default function Home() { const [address, setAddress] = useState(undefined); - const [userOpHash, setUserOpHash] = useState(""); - const [estimateUserOpHash, setEstimateUserOpHash] = useState(""); - const [userOpByHash, setUserOpByHash] = useState(""); + const [userOpHash, setUserOpHash] = useState(''); + const [estimateUserOpHash, setEstimateUserOpHash] = useState(''); + const [userOpByHash, setUserOpByHash] = useState(''); const [entryPoint, setEntryPoint] = useState([]); - const [userOpReceipt, setUserOpReceipt] = useState(""); + const [chainId, setChainId] = useState(); + const [userOpReceipt, setUserOpReceipt] = useState(''); + const [generateUserOpHash, setGenerateUserOpHash] = useState(''); + const [userOpHashInput, setUserOpHashInput] = useState(''); + const [userOperationReceiptInput, setUserOperationReceiptInput] = + useState(''); const connectHandler = async () => { const accounts = await bloctoSDK?.ethereum?.enable(); const [addr] = accounts || []; setAddress(addr); }; + const disconnectHandler = async () => { - await bloctoSDK?.ethereum?.request({ method: "wallet_disconnect" }); + await bloctoSDK?.ethereum?.request({ method: 'wallet_disconnect' }); setAddress(undefined); - setUserOpHash(""); + setUserOpHash(''); + }; + + const getChainId = async () => { + try { + const result = await web3.eth.getChainId(); + setChainId(result.toString()); + } catch (error) { + console.error('error: ', error); + } }; const getEntryPoint = async () => { @@ -40,7 +59,7 @@ export default function Home() { const result = await web3.eth.supportedEntryPoints(); setEntryPoint(result); } catch (error) { - console.error("error: ", error); + console.error('error: ', error); } }; @@ -49,18 +68,27 @@ export default function Home() { if (entryPoint.length === 0) { await getEntryPoint(); } - const result = await web3.eth.sendUserOperation(userOp, entryPoint[0]); + const result = await web3.eth.sendUserOperation( + // @ts-ignore + { + callData, + }, + entryPoint[0] + ); setUserOpHash(result); } catch (error) { - console.error("sendUserOp error", error); + console.error('sendUserOp error', error); } }; + const getUserOperationReceipt = async () => { try { - const result = await web3.eth.getUserOperationReceipt(userOpHash); + const result = await web3.eth.getUserOperationReceipt( + userOperationReceiptInput + ); setUserOpReceipt(result); } catch (error) { - console.error("error: ", error); + console.error('error: ', error); } }; @@ -70,22 +98,47 @@ export default function Home() { await getEntryPoint(); } const result = await web3.eth.estimateUserOperationGas( - userOp, + { ...userOp, sender: address, callData }, entryPoint[0] ); + console.log('result: ', result); setEstimateUserOpHash(result); } catch (error) { - console.error("estimateUserOpGas error: ", error); + console.error('estimateUserOpGas error: ', error); } }; const getUserOpByHash = async () => { + if (!userOpHashInput) { + return console.error('userOpHashInput is not defined'); + } + try { + const result = await web3.eth.getUserOperationByHash(userOpHashInput); + if (result) { + console.log('result: ', result); + setUserOpByHash(result); + } + } catch (error) { + console.error('getUserOpByHash error: ', error); + } + }; + + const generateUserOpHashHandler = async () => { try { - const result = await web3.eth.getUserOperationByHash(userOpHash); - setUserOpByHash(result); - console.log("result: ", result); + if (!chainId) { + throw new Error('chainId is not defined'); + } + if (!entryPoint[0]) { + throw new Error('entryPoint is not defined'); + } + const result = await web3.eth.generateUserOpHash( + userOp, + entryPoint[0], + chainId + ); + setGenerateUserOpHash(result); } catch (error) { - console.error("getUserOpByHash error: ", error); + console.error('generateUserOpHashHandler error: ', error); } }; @@ -99,45 +152,67 @@ export default function Home() {
{address ? ( - <> - - - {entryPoint.length > 0 && ( - entryPoint: {JSON.stringify(entryPoint)} - )} - - {userOpHash && ( - userOpHash: {JSON.stringify(userOpHash)} - )} - - {estimateUserOpHash && ( - userOpHash: {JSON.stringify(estimateUserOpHash)} - )} - - {userOpByHash && ( - getUserOpByHash: {JSON.stringify(userOpByHash)} - )} - - {userOpReceipt && ( - UserOpReceipt: {JSON.stringify(userOpReceipt)} - )} - +
+
+ +
+
+ + + + + + setUserOpHashInput(e.target.value)} + /> + + + setUserOperationReceiptInput(e.target.value)} + /> + + +
+
) : ( - +
+ +
)}
diff --git a/examples/with-web3js-playground/src/styles/globals.css b/examples/with-web3js-playground/src/styles/globals.css index 3b320fab..20f14bd3 100644 --- a/examples/with-web3js-playground/src/styles/globals.css +++ b/examples/with-web3js-playground/src/styles/globals.css @@ -197,3 +197,25 @@ button span:hover:before { button span:hover:after { width: 100%; } + +.connect_btn { + position: fixed; + top: 20px; + right: 20px; +} + +code { + padding: 0 20px; +} + +input { + width: 400px; + height: 25px; +} + +.grid { + display: grid; + grid-auto-flow: row; + grid-gap: 1rem; + margin-top: 1rem; +} \ No newline at end of file diff --git a/examples/with-web3js-playground/src/utils/generateUserOpHash.ts b/examples/with-web3js-playground/src/utils/generateUserOpHash.ts deleted file mode 100644 index 41f97654..00000000 --- a/examples/with-web3js-playground/src/utils/generateUserOpHash.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { encodeParameters } from "web3-eth-abi"; -import { Keccak as SHA3 } from "sha3"; - -const sha3Hash = (msg: string | Buffer) => { - const sha = new SHA3(256); - sha.update(typeof msg === "string" ? Buffer.from(msg, "hex") : msg); - const hashed = sha.digest("hex"); - return hashed; -}; - -const strip0x = (str: string) => str.replace(/^0x/, ""); -interface UserOperation { - sender: string; - nonce: string; - init_code: string; - call_data: string; - call_gas_limit: string; - verification_gas_limit: string; - pre_verification_gas: string; - max_fee_per_gas: string; - max_priority_fee_per_gas: string; - paymaster_and_data: string; - signature: string; -} - -const generateUserOpHash = ( - userOp: UserOperation, - entryPoint: string, - chainId: string -) => { - try { - const packed = encodeParameters( - [ - "address", - "uint256", - "bytes32", - "bytes32", - "uint256", - "uint256", - "uint256", - "uint256", - "uint256", - "bytes32", - ], - [ - userOp.sender, - userOp.nonce, - `0x${sha3Hash(strip0x(userOp.init_code))}`, - `0x${sha3Hash(strip0x(userOp.call_data))}`, - userOp.call_gas_limit, - userOp.verification_gas_limit, - userOp.pre_verification_gas, - userOp.max_fee_per_gas, - userOp.max_priority_fee_per_gas, - `0x${sha3Hash(strip0x(userOp.paymaster_and_data))}`, - ] - ); - - const enc = encodeParameters( - ["bytes32", "address", "uint256"], - [`0x${sha3Hash(strip0x(packed))}`, entryPoint, chainId] - ); - - return sha3Hash(strip0x(enc)); - } catch (error) { - console.error(error); - return error; - } -}; -export default generateUserOpHash; diff --git a/examples/with-web3js-playground/src/utils/index.ts b/examples/with-web3js-playground/src/utils/index.ts index 2a1c7df0..45161189 100644 --- a/examples/with-web3js-playground/src/utils/index.ts +++ b/examples/with-web3js-playground/src/utils/index.ts @@ -1,5 +1,11 @@ -import generateUserOpHash from "./generateUserOpHash"; -import web3GenerateUserOpHash from "./web3GenerateUserOpHash"; -import sendUserOperation from "./sendUserOperation"; +function isEmpty(value: any): boolean { + if (value == null) return true; + if (typeof value === 'string' || Array.isArray(value)) + return value.length === 0; + if (value instanceof Set || value instanceof Map) return value.size === 0; + if (typeof value === 'object' && value.constructor === Object) + return Object.keys(value).length === 0; + return false; +} -export { generateUserOpHash, web3GenerateUserOpHash, sendUserOperation }; +export { isEmpty }; diff --git a/examples/with-web3js-playground/src/utils/web3GenerateUserOpHash.ts b/examples/with-web3js-playground/src/utils/web3GenerateUserOpHash.ts deleted file mode 100644 index 0d443f16..00000000 --- a/examples/with-web3js-playground/src/utils/web3GenerateUserOpHash.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { encodeParameters } from "web3-eth-abi"; -import { AbiInput } from "web3-types"; -import { sha3 } from "web3-utils"; - -interface UserOperation { - sender: string; - nonce: string; - init_code: string; - call_data: string; - call_gas_limit: string; - verification_gas_limit: string; - pre_verification_gas: string; - max_fee_per_gas: string; - max_priority_fee_per_gas: string; - paymaster_and_data: string; - signature: string; -} - -const sha3Checked = (data: string): string => { - const result = sha3(data); - if (result === undefined) { - throw new Error(`sha3 returned undefined for data: ${data}`); - } - return result; -}; - -const generateUserOpHash = ( - userOp: UserOperation, - entryPoint: string, - chainId: string -) => { - try { - const types: AbiInput[] = [ - "address", - "uint256", - "bytes32", - "bytes32", - "uint256", - "uint256", - "uint256", - "uint256", - "uint256", - "bytes32", - ]; - - const values: (string | number)[] = [ - userOp.sender, - userOp.nonce, - sha3Checked(userOp.init_code), - sha3Checked(userOp.call_data), - userOp.call_gas_limit, - userOp.verification_gas_limit, - userOp.pre_verification_gas, - userOp.max_fee_per_gas, - userOp.max_priority_fee_per_gas, - sha3Checked(userOp.paymaster_and_data), - ]; - - const packed: string = encodeParameters(types, values); - - const enctype: AbiInput[] = ["bytes32", "address", "uint256"]; - const encValues: string[] = [sha3Checked(packed), entryPoint, chainId]; - const enc = encodeParameters(enctype, encValues); - - return sha3Checked(enc); - } catch (error) { - console.error(error); - return error; - } -}; - -export default generateUserOpHash;