erc4337-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # erc4337-kit
2
+
3
+ ERC-4337 Account Abstraction for React apps — gasless transactions, social login, smart accounts. Plug in, don't plumb.
4
+
5
+ Built on: Privy · Pimlico · Permissionless · Polygon Amoy
6
+
7
+ ---
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install erc4337-kit
13
+ ```
14
+
15
+ Also install peer dependencies if you haven't already:
16
+
17
+ ```bash
18
+ npm install @privy-io/react-auth @privy-io/wagmi viem wagmi @tanstack/react-query permissionless
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Vite setup (required)
24
+
25
+ Add this to `vite.config.js` — viem needs these polyfills in the browser:
26
+
27
+ ```js
28
+ export default defineConfig({
29
+ define: { global: 'globalThis' },
30
+ resolve: {
31
+ alias: { '@noble/curves/nist.js': '@noble/curves/nist' },
32
+ },
33
+ })
34
+ ```
35
+
36
+ Add this to your `index.html` `<head>` before your app script:
37
+
38
+ ```html
39
+ <script type="module">
40
+ import { Buffer } from 'buffer'
41
+ import process from 'process'
42
+ window.Buffer = Buffer
43
+ window.process = process
44
+ </script>
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Quick start
50
+
51
+ ### 1. Wrap your app
52
+
53
+ ```jsx
54
+ import { ChainProvider } from 'erc4337-kit'
55
+ import { polygonAmoy } from 'erc4337-kit'
56
+
57
+ function main() {
58
+ return (
59
+ <ChainProvider
60
+ privyAppId={import.meta.env.VITE_PRIVY_APP_ID}
61
+ chain={polygonAmoy}
62
+ rpcUrl={import.meta.env.VITE_RPC_URL}
63
+ >
64
+ <App />
65
+ </ChainProvider>
66
+ )
67
+ }
68
+ ```
69
+
70
+ ### 2. Initialize the smart account
71
+
72
+ ```jsx
73
+ import { useSmartAccount } from 'erc4337-kit'
74
+ import { polygonAmoy } from 'erc4337-kit'
75
+
76
+ function App() {
77
+ const {
78
+ login, logout, authenticated,
79
+ smartAccountClient, smartAccountAddress,
80
+ isReady, isLoading, error
81
+ } = useSmartAccount({
82
+ pimlicoApiKey: import.meta.env.VITE_PIMLICO_API_KEY,
83
+ rpcUrl: import.meta.env.VITE_RPC_URL,
84
+ chain: polygonAmoy,
85
+ })
86
+
87
+ if (!authenticated) return <button onClick={login}>Login with Google</button>
88
+ if (isLoading) return <p>Setting up your wallet...</p>
89
+ if (error) return <p>Error: {error}</p>
90
+
91
+ return <Dashboard smartAccountClient={smartAccountClient} />
92
+ }
93
+ ```
94
+
95
+ ### 3. Store data on-chain (gasless)
96
+
97
+ ```jsx
98
+ import { useStoreOnChain, sha256Hash } from 'erc4337-kit'
99
+
100
+ const MY_CONTRACT_ABI = [
101
+ {
102
+ name: 'storeRecord',
103
+ type: 'function',
104
+ inputs: [{ name: 'dataHash', type: 'bytes32' }],
105
+ },
106
+ ]
107
+
108
+ function ReportForm({ smartAccountClient }) {
109
+ const { submit, txHash, isLoading, error } = useStoreOnChain({
110
+ smartAccountClient,
111
+ contractAddress: import.meta.env.VITE_CONTRACT_ADDRESS,
112
+ abi: MY_CONTRACT_ABI,
113
+ functionName: 'storeRecord',
114
+ })
115
+
116
+ const handleSubmit = async (reportText) => {
117
+ const hash = await sha256Hash(reportText) // hashed locally
118
+ await submit([hash])
119
+ }
120
+
121
+ return (
122
+ <div>
123
+ <button onClick={() => handleSubmit('incident details')} disabled={isLoading}>
124
+ {isLoading ? 'Storing...' : 'Submit Report'}
125
+ </button>
126
+ {txHash && <p>Stored! Tx: {txHash}</p>}
127
+ {error && <p>Error: {error}</p>}
128
+ </div>
129
+ )
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Contract template
136
+
137
+ Copy `src/contracts/BaseStorage.sol` from this package as a starting point. It is pre-commented with all ERC-4337 compatibility rules. Add fields to the struct and parameters to `storeRecord()` as needed for your use case.
138
+
139
+ Deploy it with Hardhat or Remix to your chain, then pass the address to `useStoreOnChain()`.
140
+
141
+ ---
142
+
143
+ ## Environment variables
144
+
145
+ ```env
146
+ VITE_PRIVY_APP_ID= # dashboard.privy.io
147
+ VITE_PIMLICO_API_KEY= # dashboard.pimlico.io
148
+ VITE_RPC_URL= # Alchemy or Infura RPC for your chain
149
+ VITE_CONTRACT_ADDRESS= # deployed BaseStorage.sol address
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Supported chains
155
+
156
+ Any EVM chain supported by Pimlico and Privy. Chains exported from this package for convenience:
157
+
158
+ - `polygonAmoy` — Polygon testnet (recommended for dev)
159
+ - `polygon` — Polygon mainnet
160
+ - `sepolia` — Ethereum testnet
161
+ - `baseSepolia` — Base testnet
162
+
163
+ ---
164
+
165
+ ## License
166
+
167
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,321 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var reactAuth = require('@privy-io/react-auth');
5
+ var wagmi = require('@privy-io/wagmi');
6
+ var reactQuery = require('@tanstack/react-query');
7
+ var viem = require('viem');
8
+ var permissionless = require('permissionless');
9
+ var accounts = require('permissionless/accounts');
10
+ var pimlico = require('permissionless/clients/pimlico');
11
+ var accountAbstraction = require('viem/account-abstraction');
12
+ var chains = require('viem/chains');
13
+
14
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
+
16
+ var React__default = /*#__PURE__*/_interopDefault(React);
17
+
18
+ // src/providers/ChainProvider.jsx
19
+ var queryClient = new reactQuery.QueryClient({
20
+ defaultOptions: {
21
+ queries: {
22
+ retry: 2,
23
+ staleTime: 3e4
24
+ }
25
+ }
26
+ });
27
+ function ChainProvider({
28
+ privyAppId,
29
+ chain,
30
+ rpcUrl,
31
+ loginMethods = ["google", "email"],
32
+ appearance = {},
33
+ children
34
+ }) {
35
+ const wagmiConfig = wagmi.createConfig({
36
+ chains: [chain],
37
+ transports: {
38
+ [chain.id]: viem.http(rpcUrl)
39
+ }
40
+ });
41
+ return /* @__PURE__ */ React__default.default.createElement(
42
+ reactAuth.PrivyProvider,
43
+ {
44
+ appId: privyAppId,
45
+ config: {
46
+ loginMethods,
47
+ embeddedWallets: {
48
+ // CRITICAL: this tells Privy to create a wallet for EVERY user
49
+ // automatically on login. Without this, you'd have to call
50
+ // createWallet() manually and handle the timing yourself.
51
+ createOnLogin: "all-users"
52
+ },
53
+ defaultChain: chain,
54
+ supportedChains: [chain],
55
+ appearance: {
56
+ theme: "light",
57
+ accentColor: "#7c3aed",
58
+ ...appearance
59
+ }
60
+ }
61
+ },
62
+ /* @__PURE__ */ React__default.default.createElement(reactQuery.QueryClientProvider, { client: queryClient }, /* @__PURE__ */ React__default.default.createElement(wagmi.WagmiProvider, { config: wagmiConfig }, children))
63
+ );
64
+ }
65
+ function buildPimlicoUrl(chainId, apiKey) {
66
+ return `https://api.pimlico.io/v2/${chainId}/rpc?apikey=${apiKey}`;
67
+ }
68
+ function useSmartAccount({ pimlicoApiKey, rpcUrl, chain }) {
69
+ const { login, logout, authenticated, user, ready } = reactAuth.usePrivy();
70
+ const { wallets } = reactAuth.useWallets();
71
+ const { createWallet } = reactAuth.useCreateWallet();
72
+ const [smartAccountAddress, setSmartAccountAddress] = React.useState(null);
73
+ const [smartAccountClient, setSmartAccountClient] = React.useState(null);
74
+ const [pimlicoClient, setPimlicoClient] = React.useState(null);
75
+ const [isLoading, setIsLoading] = React.useState(false);
76
+ const [error, setError] = React.useState(null);
77
+ const initCalledRef = React.useRef(false);
78
+ const walletCreationAttempted = React.useRef(false);
79
+ const pimlicoUrl = buildPimlicoUrl(chain.id, pimlicoApiKey);
80
+ const initSmartAccount = React.useCallback(async () => {
81
+ var _a;
82
+ if (!authenticated || !ready) return;
83
+ if (!wallets || wallets.length === 0) {
84
+ if (!walletCreationAttempted.current) {
85
+ walletCreationAttempted.current = true;
86
+ try {
87
+ await createWallet();
88
+ return;
89
+ } catch (err) {
90
+ if (!((_a = err.message) == null ? void 0 : _a.includes("already has"))) {
91
+ setError("Failed to create embedded wallet: " + err.message);
92
+ }
93
+ return;
94
+ }
95
+ }
96
+ return;
97
+ }
98
+ if (initCalledRef.current) return;
99
+ initCalledRef.current = true;
100
+ setIsLoading(true);
101
+ setError(null);
102
+ try {
103
+ const wallet = wallets[0];
104
+ await wallet.switchChain(chain.id);
105
+ const provider = await wallet.getEthereumProvider();
106
+ const walletClient = viem.createWalletClient({
107
+ account: wallet.address,
108
+ chain,
109
+ transport: viem.custom(provider)
110
+ });
111
+ const publicClient = viem.createPublicClient({
112
+ chain,
113
+ transport: viem.http(rpcUrl)
114
+ });
115
+ const pimlico$1 = pimlico.createPimlicoClient({
116
+ transport: viem.http(pimlicoUrl),
117
+ entryPoint: {
118
+ address: accountAbstraction.entryPoint07Address,
119
+ version: "0.7"
120
+ }
121
+ });
122
+ const smartAccount = await accounts.toSimpleSmartAccount({
123
+ client: publicClient,
124
+ owner: walletClient,
125
+ entryPoint: {
126
+ address: accountAbstraction.entryPoint07Address,
127
+ version: "0.7"
128
+ }
129
+ });
130
+ const client = permissionless.createSmartAccountClient({
131
+ account: smartAccount,
132
+ chain,
133
+ bundlerTransport: viem.http(pimlicoUrl),
134
+ paymaster: pimlico$1,
135
+ // userOperation config: tell Pimlico to sponsor everything
136
+ userOperation: {
137
+ estimateFeesPerGas: async () => {
138
+ const fees = await pimlico$1.getUserOperationGasPrice();
139
+ return fees.fast;
140
+ }
141
+ }
142
+ });
143
+ setPimlicoClient(pimlico$1);
144
+ setSmartAccountClient(client);
145
+ setSmartAccountAddress(smartAccount.address);
146
+ } catch (err) {
147
+ console.error("[erc4337-kit] Smart account init failed:", err);
148
+ setError(err.message || "Failed to initialize smart account");
149
+ initCalledRef.current = false;
150
+ } finally {
151
+ setIsLoading(false);
152
+ }
153
+ }, [authenticated, wallets, ready, createWallet, chain, rpcUrl, pimlicoUrl]);
154
+ React.useEffect(() => {
155
+ initSmartAccount();
156
+ }, [initSmartAccount]);
157
+ const handleLogout = React.useCallback(async () => {
158
+ await logout();
159
+ initCalledRef.current = false;
160
+ walletCreationAttempted.current = false;
161
+ setSmartAccountAddress(null);
162
+ setSmartAccountClient(null);
163
+ setPimlicoClient(null);
164
+ setError(null);
165
+ }, [logout]);
166
+ return {
167
+ login,
168
+ logout: handleLogout,
169
+ authenticated,
170
+ user,
171
+ smartAccountAddress,
172
+ smartAccountClient,
173
+ pimlicoClient,
174
+ isReady: !!smartAccountClient && !!smartAccountAddress,
175
+ isLoading,
176
+ error
177
+ };
178
+ }
179
+ function useStoreOnChain({
180
+ smartAccountClient,
181
+ contractAddress,
182
+ abi,
183
+ functionName
184
+ }) {
185
+ const [txHash, setTxHash] = React.useState(null);
186
+ const [recordId, setRecordId] = React.useState(null);
187
+ const [isLoading, setIsLoading] = React.useState(false);
188
+ const [isSuccess, setIsSuccess] = React.useState(false);
189
+ const [error, setError] = React.useState(null);
190
+ const submit = React.useCallback(
191
+ async (args = []) => {
192
+ var _a, _b;
193
+ if (!smartAccountClient) {
194
+ setError("Smart account not initialized. Make sure user is logged in.");
195
+ return null;
196
+ }
197
+ setIsLoading(true);
198
+ setIsSuccess(false);
199
+ setError(null);
200
+ setTxHash(null);
201
+ setRecordId(null);
202
+ try {
203
+ const calldata = viem.encodeFunctionData({
204
+ abi,
205
+ functionName,
206
+ args
207
+ });
208
+ const hash = await smartAccountClient.sendTransaction({
209
+ to: contractAddress,
210
+ data: calldata,
211
+ value: 0n
212
+ // no ETH/MATIC sent — this is just a contract call
213
+ });
214
+ setTxHash(hash);
215
+ setIsSuccess(true);
216
+ try {
217
+ const receipt = await smartAccountClient.waitForTransactionReceipt({ hash });
218
+ const firstLog = (_a = receipt.logs) == null ? void 0 : _a[0];
219
+ if ((_b = firstLog == null ? void 0 : firstLog.topics) == null ? void 0 : _b[1]) {
220
+ setRecordId(firstLog.topics[1]);
221
+ }
222
+ } catch {
223
+ }
224
+ return hash;
225
+ } catch (err) {
226
+ const message = parseError(err);
227
+ setError(message);
228
+ console.error("[erc4337-kit] Transaction failed:", err);
229
+ return null;
230
+ } finally {
231
+ setIsLoading(false);
232
+ }
233
+ },
234
+ [smartAccountClient, contractAddress, abi, functionName]
235
+ );
236
+ const reset = React.useCallback(() => {
237
+ setTxHash(null);
238
+ setRecordId(null);
239
+ setIsLoading(false);
240
+ setIsSuccess(false);
241
+ setError(null);
242
+ }, []);
243
+ return {
244
+ submit,
245
+ txHash,
246
+ recordId,
247
+ isLoading,
248
+ isSuccess,
249
+ error,
250
+ reset
251
+ };
252
+ }
253
+ function parseError(err) {
254
+ const msg = (err == null ? void 0 : err.message) || (err == null ? void 0 : err.toString()) || "Unknown error";
255
+ if (msg.includes("AA21")) {
256
+ return "Paymaster rejected: your Pimlico API key may be invalid or the policy does not cover this chain.";
257
+ }
258
+ if (msg.includes("AA31")) {
259
+ return "Paymaster out of funds. Check your Pimlico dashboard deposit balance.";
260
+ }
261
+ if (msg.includes("AA23") || msg.includes("invalid signature")) {
262
+ return "Wallet signature failed. Try logging out and back in.";
263
+ }
264
+ if (msg.includes("gas") && msg.includes("too low")) {
265
+ return "Gas estimate too low. The contract function may be too expensive for the paymaster policy.";
266
+ }
267
+ if (msg.includes("nonce")) {
268
+ return "Nonce error. A previous transaction may still be pending \u2014 wait a moment and retry.";
269
+ }
270
+ if (msg.includes("user rejected") || msg.includes("User rejected")) {
271
+ return "Transaction was cancelled.";
272
+ }
273
+ if (msg.includes("fetch") || msg.includes("network")) {
274
+ return "Network error. Check your RPC URL and Pimlico API key.";
275
+ }
276
+ return msg;
277
+ }
278
+
279
+ // src/utils/hash.js
280
+ async function sha256Hash(text) {
281
+ if (typeof text !== "string") {
282
+ throw new Error("sha256Hash: input must be a string");
283
+ }
284
+ const encoded = new TextEncoder().encode(text);
285
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
286
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
287
+ return "0x" + hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
288
+ }
289
+ async function sha256HashFile(file) {
290
+ if (!(file instanceof File)) {
291
+ throw new Error("sha256HashFile: input must be a File object");
292
+ }
293
+ const arrayBuffer = await file.arrayBuffer();
294
+ const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
295
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
296
+ return "0x" + hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
297
+ }
298
+
299
+ Object.defineProperty(exports, "baseSepolia", {
300
+ enumerable: true,
301
+ get: function () { return chains.baseSepolia; }
302
+ });
303
+ Object.defineProperty(exports, "polygon", {
304
+ enumerable: true,
305
+ get: function () { return chains.polygon; }
306
+ });
307
+ Object.defineProperty(exports, "polygonAmoy", {
308
+ enumerable: true,
309
+ get: function () { return chains.polygonAmoy; }
310
+ });
311
+ Object.defineProperty(exports, "sepolia", {
312
+ enumerable: true,
313
+ get: function () { return chains.sepolia; }
314
+ });
315
+ exports.ChainProvider = ChainProvider;
316
+ exports.sha256Hash = sha256Hash;
317
+ exports.sha256HashFile = sha256HashFile;
318
+ exports.useSmartAccount = useSmartAccount;
319
+ exports.useStoreOnChain = useStoreOnChain;
320
+ //# sourceMappingURL=index.cjs.map
321
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/ChainProvider.jsx","../src/hooks/useSmartAccount.js","../src/hooks/useStoreOnChain.js","../src/utils/hash.js"],"names":["QueryClient","createConfig","http","React","PrivyProvider","QueryClientProvider","WagmiProvider","usePrivy","useWallets","useCreateWallet","useState","useRef","useCallback","createWalletClient","custom","createPublicClient","pimlico","createPimlicoClient","entryPoint07Address","toSimpleSmartAccount","createSmartAccountClient","useEffect","encodeFunctionData"],"mappings":"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","file":"index.cjs","sourcesContent":["import React from 'react'\nimport { PrivyProvider } from '@privy-io/react-auth'\nimport { WagmiProvider, createConfig } from '@privy-io/wagmi'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport { http } from 'viem'\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n retry: 2,\n staleTime: 30_000,\n },\n },\n})\n\n/**\n * ChainProvider\n *\n * Wraps your app with all providers required for ERC-4337:\n * Privy (auth + embedded wallets) → QueryClient → Wagmi\n *\n * Put this at the ROOT of your app, outside your router.\n *\n * @param {object} props\n * @param {string} props.privyAppId — from dashboard.privy.io\n * @param {object} props.chain — viem chain (e.g. polygonAmoy)\n * @param {string} props.rpcUrl — your Alchemy/Infura RPC URL\n * @param {string[]} [props.loginMethods] — default: ['google', 'email']\n * @param {object} [props.appearance] — Privy modal theme config\n * @param {node} props.children\n *\n * @example\n * import { ChainProvider } from '@atharva/erc4337-kit'\n * import { polygonAmoy } from 'viem/chains'\n *\n * <ChainProvider\n * privyAppId={import.meta.env.VITE_PRIVY_APP_ID}\n * chain={polygonAmoy}\n * rpcUrl={import.meta.env.VITE_RPC_URL}\n * >\n * <App />\n * </ChainProvider>\n */\nexport function ChainProvider({\n privyAppId,\n chain,\n rpcUrl,\n loginMethods = ['google', 'email'],\n appearance = {},\n children,\n}) {\n const wagmiConfig = createConfig({\n chains: [chain],\n transports: {\n [chain.id]: http(rpcUrl),\n },\n })\n\n return (\n <PrivyProvider\n appId={privyAppId}\n config={{\n loginMethods,\n embeddedWallets: {\n // CRITICAL: this tells Privy to create a wallet for EVERY user\n // automatically on login. Without this, you'd have to call\n // createWallet() manually and handle the timing yourself.\n createOnLogin: 'all-users',\n },\n defaultChain: chain,\n supportedChains: [chain],\n appearance: {\n theme: 'light',\n accentColor: '#7c3aed',\n ...appearance,\n },\n }}\n >\n <QueryClientProvider client={queryClient}>\n <WagmiProvider config={wagmiConfig}>\n {children}\n </WagmiProvider>\n </QueryClientProvider>\n </PrivyProvider>\n )\n}\n","import { useState, useCallback, useRef, useEffect } from 'react'\nimport { usePrivy, useWallets, useCreateWallet } from '@privy-io/react-auth'\nimport { createPublicClient, createWalletClient, http, custom } from 'viem'\nimport { createSmartAccountClient } from 'permissionless'\nimport { toSimpleSmartAccount } from 'permissionless/accounts'\nimport { createPimlicoClient } from 'permissionless/clients/pimlico'\nimport { entryPoint07Address } from 'viem/account-abstraction'\n\n// Internal helper — builds the Pimlico endpoint URL from chain ID\nfunction buildPimlicoUrl(chainId, apiKey) {\n return `https://api.pimlico.io/v2/${chainId}/rpc?apikey=${apiKey}`\n}\n\n/**\n * useSmartAccount\n *\n * Manages ERC-4337 Smart Account creation and lifecycle.\n * Handles Privy auth, embedded wallet creation, and Pimlico setup.\n *\n * @param {object} config\n * @param {string} config.pimlicoApiKey — from dashboard.pimlico.io\n * @param {string} config.rpcUrl — Alchemy/Infura RPC for your chain\n * @param {object} config.chain — viem chain object (e.g. polygonAmoy)\n *\n * @returns {object} {\n * login, logout, authenticated, user,\n * smartAccountAddress,\n * smartAccountClient, ← use this to send transactions\n * pimlicoClient,\n * isReady, ← true when SA is initialized and ready\n * isLoading,\n * error\n * }\n */\nexport function useSmartAccount({ pimlicoApiKey, rpcUrl, chain }) {\n const { login, logout, authenticated, user, ready } = usePrivy()\n const { wallets } = useWallets()\n const { createWallet } = useCreateWallet()\n\n const [smartAccountAddress, setSmartAccountAddress] = useState(null)\n const [smartAccountClient, setSmartAccountClient] = useState(null)\n const [pimlicoClient, setPimlicoClient] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState(null)\n\n // Refs to prevent duplicate initialization — same pattern as your ProofChain\n const initCalledRef = useRef(false)\n const walletCreationAttempted = useRef(false)\n\n const pimlicoUrl = buildPimlicoUrl(chain.id, pimlicoApiKey)\n\n const initSmartAccount = useCallback(async () => {\n // Guard: only proceed when Privy is fully ready and user is logged in\n if (!authenticated || !ready) return\n\n // If no wallet yet, try to create one (Privy sometimes needs a nudge)\n if (!wallets || wallets.length === 0) {\n if (!walletCreationAttempted.current) {\n walletCreationAttempted.current = true\n try {\n await createWallet()\n // Don't continue here — wait for next effect run after wallet appears\n return\n } catch (err) {\n // 'already has' means the wallet exists but wasn't in state yet — safe to ignore\n if (!err.message?.includes('already has')) {\n setError('Failed to create embedded wallet: ' + err.message)\n }\n return\n }\n }\n return\n }\n\n // Guard: don't initialize twice\n if (initCalledRef.current) return\n initCalledRef.current = true\n\n setIsLoading(true)\n setError(null)\n\n try {\n const wallet = wallets[0]\n\n // Switch to the configured chain before doing anything\n await wallet.switchChain(chain.id)\n\n const provider = await wallet.getEthereumProvider()\n\n // Wallet client signs UserOperations using the embedded wallet\n const walletClient = createWalletClient({\n account: wallet.address,\n chain,\n transport: custom(provider),\n })\n\n // Public client reads from chain (balance, contract state, etc.)\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n })\n\n // Pimlico client handles bundling + gas sponsorship\n const pimlico = createPimlicoClient({\n transport: http(pimlicoUrl),\n entryPoint: {\n address: entryPoint07Address,\n version: '0.7',\n },\n })\n\n // SimpleSmartAccount: the simplest ERC-4337 account type\n // deterministic address — same owner always gets same SA address\n const smartAccount = await toSimpleSmartAccount({\n client: publicClient,\n owner: walletClient,\n entryPoint: {\n address: entryPoint07Address,\n version: '0.7',\n },\n })\n\n // SmartAccountClient: the object you use to send transactions\n // It automatically builds UserOperations, gets gas estimates,\n // requests paymaster sponsorship, and submits to the bundler\n const client = createSmartAccountClient({\n account: smartAccount,\n chain,\n bundlerTransport: http(pimlicoUrl),\n paymaster: pimlico,\n // userOperation config: tell Pimlico to sponsor everything\n userOperation: {\n estimateFeesPerGas: async () => {\n const fees = await pimlico.getUserOperationGasPrice()\n return fees.fast\n },\n },\n })\n\n setPimlicoClient(pimlico)\n setSmartAccountClient(client)\n setSmartAccountAddress(smartAccount.address)\n\n } catch (err) {\n console.error('[erc4337-kit] Smart account init failed:', err)\n setError(err.message || 'Failed to initialize smart account')\n // Reset so the user can retry\n initCalledRef.current = false\n } finally {\n setIsLoading(false)\n }\n }, [authenticated, wallets, ready, createWallet, chain, rpcUrl, pimlicoUrl])\n\n useEffect(() => {\n initSmartAccount()\n }, [initSmartAccount])\n\n const handleLogout = useCallback(async () => {\n await logout()\n // Full reset so next login starts fresh\n initCalledRef.current = false\n walletCreationAttempted.current = false\n setSmartAccountAddress(null)\n setSmartAccountClient(null)\n setPimlicoClient(null)\n setError(null)\n }, [logout])\n\n return {\n login,\n logout: handleLogout,\n authenticated,\n user,\n smartAccountAddress,\n smartAccountClient,\n pimlicoClient,\n isReady: !!smartAccountClient && !!smartAccountAddress,\n isLoading,\n error,\n }\n}\n","import { useState, useCallback } from 'react'\nimport { encodeFunctionData } from 'viem'\n\n/**\n * useStoreOnChain\n *\n * Generic hook to call any write function on any contract\n * via ERC-4337 gasless UserOperation.\n *\n * @param {object} params\n * @param {object} params.smartAccountClient — from useSmartAccount()\n * @param {string} params.contractAddress — deployed contract address\n * @param {array} params.abi — contract ABI (just the functions you need)\n * @param {string} params.functionName — which function to call\n *\n * @returns {object} {\n * submit(args), ← call this with your function arguments as an array\n * txHash,\n * recordId, ← decoded from logs if contract returns bytes32\n * isLoading,\n * isSuccess,\n * error,\n * reset\n * }\n *\n * @example\n * const { submit, txHash, isLoading } = useStoreOnChain({\n * smartAccountClient,\n * contractAddress: '0x...',\n * abi: incidentABI,\n * functionName: 'storeRecord',\n * })\n *\n * // In your handler:\n * await submit([dataHash])\n */\nexport function useStoreOnChain({\n smartAccountClient,\n contractAddress,\n abi,\n functionName,\n}) {\n const [txHash, setTxHash] = useState(null)\n const [recordId, setRecordId] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const [isSuccess, setIsSuccess] = useState(false)\n const [error, setError] = useState(null)\n\n const submit = useCallback(\n async (args = []) => {\n // Guard: smartAccountClient must exist (user must be logged in)\n if (!smartAccountClient) {\n setError('Smart account not initialized. Make sure user is logged in.')\n return null\n }\n\n setIsLoading(true)\n setIsSuccess(false)\n setError(null)\n setTxHash(null)\n setRecordId(null)\n\n try {\n // encodeFunctionData turns your ABI + args into the raw calldata bytes\n // that the smart account will call on the target contract\n const calldata = encodeFunctionData({\n abi,\n functionName,\n args,\n })\n\n // sendTransaction on a SmartAccountClient works differently than a normal\n // wallet tx. Under the hood it:\n // 1. Builds a UserOperation\n // 2. Estimates gas (callGasLimit, verificationGasLimit, preVerificationGas)\n // 3. Calls your paymaster (Pimlico) for sponsorship\n // 4. Signs the UserOperation with the embedded wallet\n // 5. Sends it to the Pimlico bundler\n // 6. Returns the tx hash once the bundler accepts it\n //\n // The tx hash here is the ACTUAL on-chain tx hash, not the UserOp hash.\n const hash = await smartAccountClient.sendTransaction({\n to: contractAddress,\n data: calldata,\n value: 0n, // no ETH/MATIC sent — this is just a contract call\n })\n\n setTxHash(hash)\n setIsSuccess(true)\n\n // Try to extract the returned bytes32 record ID from the receipt logs\n // This is specific to BaseStorage.sol which emits RecordStored(id, ...)\n try {\n const receipt = await smartAccountClient.waitForTransactionReceipt({ hash })\n const firstLog = receipt.logs?.[0]\n if (firstLog?.topics?.[1]) {\n setRecordId(firstLog.topics[1])\n }\n } catch {\n // Log parsing failing is not a fatal error — tx already succeeded\n }\n\n return hash\n\n } catch (err) {\n const message = parseError(err)\n setError(message)\n console.error('[erc4337-kit] Transaction failed:', err)\n return null\n } finally {\n setIsLoading(false)\n }\n },\n [smartAccountClient, contractAddress, abi, functionName]\n )\n\n const reset = useCallback(() => {\n setTxHash(null)\n setRecordId(null)\n setIsLoading(false)\n setIsSuccess(false)\n setError(null)\n }, [])\n\n return {\n submit,\n txHash,\n recordId,\n isLoading,\n isSuccess,\n error,\n reset,\n }\n}\n\n// -----------------------------------------------------------------\n// Internal: parse common ERC-4337 / Pimlico errors into human messages\n// These are the exact errors you hit during ProofChain development\n// -----------------------------------------------------------------\nfunction parseError(err) {\n const msg = err?.message || err?.toString() || 'Unknown error'\n\n if (msg.includes('AA21')) {\n return 'Paymaster rejected: your Pimlico API key may be invalid or the policy does not cover this chain.'\n }\n if (msg.includes('AA31')) {\n return 'Paymaster out of funds. Check your Pimlico dashboard deposit balance.'\n }\n if (msg.includes('AA23') || msg.includes('invalid signature')) {\n return 'Wallet signature failed. Try logging out and back in.'\n }\n if (msg.includes('gas') && msg.includes('too low')) {\n return 'Gas estimate too low. The contract function may be too expensive for the paymaster policy.'\n }\n if (msg.includes('nonce')) {\n return 'Nonce error. A previous transaction may still be pending — wait a moment and retry.'\n }\n if (msg.includes('user rejected') || msg.includes('User rejected')) {\n return 'Transaction was cancelled.'\n }\n if (msg.includes('fetch') || msg.includes('network')) {\n return 'Network error. Check your RPC URL and Pimlico API key.'\n }\n\n return msg\n}\n","/**\r\n * SHA-256 hashing utilities for client-side data hashing\r\n * \r\n * These functions hash data BEFORE sending to blockchain,\r\n * preserving privacy while creating tamper-proof proofs.\r\n */\r\n\r\n/**\r\n * Hash a string using SHA-256\r\n * \r\n * @param {string} text - The text to hash\r\n * @returns {Promise<string>} - Hex-encoded hash with 0x prefix\r\n * \r\n * @example\r\n * const hash = await sha256Hash(\"my secret data\")\r\n * // Returns: \"0x2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae\"\r\n */\r\nexport async function sha256Hash(text) {\r\n if (typeof text !== 'string') {\r\n throw new Error('sha256Hash: input must be a string')\r\n }\r\n\r\n const encoded = new TextEncoder().encode(text)\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', encoded)\r\n const hashArray = Array.from(new Uint8Array(hashBuffer))\r\n return '0x' + hashArray.map(b => b.toString(16).padStart(2, '0')).join('')\r\n}\r\n\r\n/**\r\n * Hash a File object using SHA-256\r\n * \r\n * @param {File} file - The file to hash\r\n * @returns {Promise<string>} - Hex-encoded hash with 0x prefix\r\n * \r\n * @example\r\n * const fileInput = document.querySelector('input[type=\"file\"]')\r\n * const file = fileInput.files[0]\r\n * const hash = await sha256HashFile(file)\r\n */\r\nexport async function sha256HashFile(file) {\r\n if (!(file instanceof File)) {\r\n throw new Error('sha256HashFile: input must be a File object')\r\n }\r\n\r\n const arrayBuffer = await file.arrayBuffer()\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer)\r\n const hashArray = Array.from(new Uint8Array(hashBuffer))\r\n return '0x' + hashArray.map(b => b.toString(16).padStart(2, '0')).join('')\r\n}\r\n"]}
package/dist/index.js ADDED
@@ -0,0 +1,295 @@
1
+ import React, { useState, useRef, useCallback, useEffect } from 'react';
2
+ import { PrivyProvider, usePrivy, useWallets, useCreateWallet } from '@privy-io/react-auth';
3
+ import { createConfig, WagmiProvider } from '@privy-io/wagmi';
4
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
+ import { http, createWalletClient, custom, createPublicClient, encodeFunctionData } from 'viem';
6
+ import { createSmartAccountClient } from 'permissionless';
7
+ import { toSimpleSmartAccount } from 'permissionless/accounts';
8
+ import { createPimlicoClient } from 'permissionless/clients/pimlico';
9
+ import { entryPoint07Address } from 'viem/account-abstraction';
10
+ export { baseSepolia, polygon, polygonAmoy, sepolia } from 'viem/chains';
11
+
12
+ // src/providers/ChainProvider.jsx
13
+ var queryClient = new QueryClient({
14
+ defaultOptions: {
15
+ queries: {
16
+ retry: 2,
17
+ staleTime: 3e4
18
+ }
19
+ }
20
+ });
21
+ function ChainProvider({
22
+ privyAppId,
23
+ chain,
24
+ rpcUrl,
25
+ loginMethods = ["google", "email"],
26
+ appearance = {},
27
+ children
28
+ }) {
29
+ const wagmiConfig = createConfig({
30
+ chains: [chain],
31
+ transports: {
32
+ [chain.id]: http(rpcUrl)
33
+ }
34
+ });
35
+ return /* @__PURE__ */ React.createElement(
36
+ PrivyProvider,
37
+ {
38
+ appId: privyAppId,
39
+ config: {
40
+ loginMethods,
41
+ embeddedWallets: {
42
+ // CRITICAL: this tells Privy to create a wallet for EVERY user
43
+ // automatically on login. Without this, you'd have to call
44
+ // createWallet() manually and handle the timing yourself.
45
+ createOnLogin: "all-users"
46
+ },
47
+ defaultChain: chain,
48
+ supportedChains: [chain],
49
+ appearance: {
50
+ theme: "light",
51
+ accentColor: "#7c3aed",
52
+ ...appearance
53
+ }
54
+ }
55
+ },
56
+ /* @__PURE__ */ React.createElement(QueryClientProvider, { client: queryClient }, /* @__PURE__ */ React.createElement(WagmiProvider, { config: wagmiConfig }, children))
57
+ );
58
+ }
59
+ function buildPimlicoUrl(chainId, apiKey) {
60
+ return `https://api.pimlico.io/v2/${chainId}/rpc?apikey=${apiKey}`;
61
+ }
62
+ function useSmartAccount({ pimlicoApiKey, rpcUrl, chain }) {
63
+ const { login, logout, authenticated, user, ready } = usePrivy();
64
+ const { wallets } = useWallets();
65
+ const { createWallet } = useCreateWallet();
66
+ const [smartAccountAddress, setSmartAccountAddress] = useState(null);
67
+ const [smartAccountClient, setSmartAccountClient] = useState(null);
68
+ const [pimlicoClient, setPimlicoClient] = useState(null);
69
+ const [isLoading, setIsLoading] = useState(false);
70
+ const [error, setError] = useState(null);
71
+ const initCalledRef = useRef(false);
72
+ const walletCreationAttempted = useRef(false);
73
+ const pimlicoUrl = buildPimlicoUrl(chain.id, pimlicoApiKey);
74
+ const initSmartAccount = useCallback(async () => {
75
+ var _a;
76
+ if (!authenticated || !ready) return;
77
+ if (!wallets || wallets.length === 0) {
78
+ if (!walletCreationAttempted.current) {
79
+ walletCreationAttempted.current = true;
80
+ try {
81
+ await createWallet();
82
+ return;
83
+ } catch (err) {
84
+ if (!((_a = err.message) == null ? void 0 : _a.includes("already has"))) {
85
+ setError("Failed to create embedded wallet: " + err.message);
86
+ }
87
+ return;
88
+ }
89
+ }
90
+ return;
91
+ }
92
+ if (initCalledRef.current) return;
93
+ initCalledRef.current = true;
94
+ setIsLoading(true);
95
+ setError(null);
96
+ try {
97
+ const wallet = wallets[0];
98
+ await wallet.switchChain(chain.id);
99
+ const provider = await wallet.getEthereumProvider();
100
+ const walletClient = createWalletClient({
101
+ account: wallet.address,
102
+ chain,
103
+ transport: custom(provider)
104
+ });
105
+ const publicClient = createPublicClient({
106
+ chain,
107
+ transport: http(rpcUrl)
108
+ });
109
+ const pimlico = createPimlicoClient({
110
+ transport: http(pimlicoUrl),
111
+ entryPoint: {
112
+ address: entryPoint07Address,
113
+ version: "0.7"
114
+ }
115
+ });
116
+ const smartAccount = await toSimpleSmartAccount({
117
+ client: publicClient,
118
+ owner: walletClient,
119
+ entryPoint: {
120
+ address: entryPoint07Address,
121
+ version: "0.7"
122
+ }
123
+ });
124
+ const client = createSmartAccountClient({
125
+ account: smartAccount,
126
+ chain,
127
+ bundlerTransport: http(pimlicoUrl),
128
+ paymaster: pimlico,
129
+ // userOperation config: tell Pimlico to sponsor everything
130
+ userOperation: {
131
+ estimateFeesPerGas: async () => {
132
+ const fees = await pimlico.getUserOperationGasPrice();
133
+ return fees.fast;
134
+ }
135
+ }
136
+ });
137
+ setPimlicoClient(pimlico);
138
+ setSmartAccountClient(client);
139
+ setSmartAccountAddress(smartAccount.address);
140
+ } catch (err) {
141
+ console.error("[erc4337-kit] Smart account init failed:", err);
142
+ setError(err.message || "Failed to initialize smart account");
143
+ initCalledRef.current = false;
144
+ } finally {
145
+ setIsLoading(false);
146
+ }
147
+ }, [authenticated, wallets, ready, createWallet, chain, rpcUrl, pimlicoUrl]);
148
+ useEffect(() => {
149
+ initSmartAccount();
150
+ }, [initSmartAccount]);
151
+ const handleLogout = useCallback(async () => {
152
+ await logout();
153
+ initCalledRef.current = false;
154
+ walletCreationAttempted.current = false;
155
+ setSmartAccountAddress(null);
156
+ setSmartAccountClient(null);
157
+ setPimlicoClient(null);
158
+ setError(null);
159
+ }, [logout]);
160
+ return {
161
+ login,
162
+ logout: handleLogout,
163
+ authenticated,
164
+ user,
165
+ smartAccountAddress,
166
+ smartAccountClient,
167
+ pimlicoClient,
168
+ isReady: !!smartAccountClient && !!smartAccountAddress,
169
+ isLoading,
170
+ error
171
+ };
172
+ }
173
+ function useStoreOnChain({
174
+ smartAccountClient,
175
+ contractAddress,
176
+ abi,
177
+ functionName
178
+ }) {
179
+ const [txHash, setTxHash] = useState(null);
180
+ const [recordId, setRecordId] = useState(null);
181
+ const [isLoading, setIsLoading] = useState(false);
182
+ const [isSuccess, setIsSuccess] = useState(false);
183
+ const [error, setError] = useState(null);
184
+ const submit = useCallback(
185
+ async (args = []) => {
186
+ var _a, _b;
187
+ if (!smartAccountClient) {
188
+ setError("Smart account not initialized. Make sure user is logged in.");
189
+ return null;
190
+ }
191
+ setIsLoading(true);
192
+ setIsSuccess(false);
193
+ setError(null);
194
+ setTxHash(null);
195
+ setRecordId(null);
196
+ try {
197
+ const calldata = encodeFunctionData({
198
+ abi,
199
+ functionName,
200
+ args
201
+ });
202
+ const hash = await smartAccountClient.sendTransaction({
203
+ to: contractAddress,
204
+ data: calldata,
205
+ value: 0n
206
+ // no ETH/MATIC sent — this is just a contract call
207
+ });
208
+ setTxHash(hash);
209
+ setIsSuccess(true);
210
+ try {
211
+ const receipt = await smartAccountClient.waitForTransactionReceipt({ hash });
212
+ const firstLog = (_a = receipt.logs) == null ? void 0 : _a[0];
213
+ if ((_b = firstLog == null ? void 0 : firstLog.topics) == null ? void 0 : _b[1]) {
214
+ setRecordId(firstLog.topics[1]);
215
+ }
216
+ } catch {
217
+ }
218
+ return hash;
219
+ } catch (err) {
220
+ const message = parseError(err);
221
+ setError(message);
222
+ console.error("[erc4337-kit] Transaction failed:", err);
223
+ return null;
224
+ } finally {
225
+ setIsLoading(false);
226
+ }
227
+ },
228
+ [smartAccountClient, contractAddress, abi, functionName]
229
+ );
230
+ const reset = useCallback(() => {
231
+ setTxHash(null);
232
+ setRecordId(null);
233
+ setIsLoading(false);
234
+ setIsSuccess(false);
235
+ setError(null);
236
+ }, []);
237
+ return {
238
+ submit,
239
+ txHash,
240
+ recordId,
241
+ isLoading,
242
+ isSuccess,
243
+ error,
244
+ reset
245
+ };
246
+ }
247
+ function parseError(err) {
248
+ const msg = (err == null ? void 0 : err.message) || (err == null ? void 0 : err.toString()) || "Unknown error";
249
+ if (msg.includes("AA21")) {
250
+ return "Paymaster rejected: your Pimlico API key may be invalid or the policy does not cover this chain.";
251
+ }
252
+ if (msg.includes("AA31")) {
253
+ return "Paymaster out of funds. Check your Pimlico dashboard deposit balance.";
254
+ }
255
+ if (msg.includes("AA23") || msg.includes("invalid signature")) {
256
+ return "Wallet signature failed. Try logging out and back in.";
257
+ }
258
+ if (msg.includes("gas") && msg.includes("too low")) {
259
+ return "Gas estimate too low. The contract function may be too expensive for the paymaster policy.";
260
+ }
261
+ if (msg.includes("nonce")) {
262
+ return "Nonce error. A previous transaction may still be pending \u2014 wait a moment and retry.";
263
+ }
264
+ if (msg.includes("user rejected") || msg.includes("User rejected")) {
265
+ return "Transaction was cancelled.";
266
+ }
267
+ if (msg.includes("fetch") || msg.includes("network")) {
268
+ return "Network error. Check your RPC URL and Pimlico API key.";
269
+ }
270
+ return msg;
271
+ }
272
+
273
+ // src/utils/hash.js
274
+ async function sha256Hash(text) {
275
+ if (typeof text !== "string") {
276
+ throw new Error("sha256Hash: input must be a string");
277
+ }
278
+ const encoded = new TextEncoder().encode(text);
279
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
280
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
281
+ return "0x" + hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
282
+ }
283
+ async function sha256HashFile(file) {
284
+ if (!(file instanceof File)) {
285
+ throw new Error("sha256HashFile: input must be a File object");
286
+ }
287
+ const arrayBuffer = await file.arrayBuffer();
288
+ const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
289
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
290
+ return "0x" + hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
291
+ }
292
+
293
+ export { ChainProvider, sha256Hash, sha256HashFile, useSmartAccount, useStoreOnChain };
294
+ //# sourceMappingURL=index.js.map
295
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/ChainProvider.jsx","../src/hooks/useSmartAccount.js","../src/hooks/useStoreOnChain.js","../src/utils/hash.js"],"names":["http","useState","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import React from 'react'\nimport { PrivyProvider } from '@privy-io/react-auth'\nimport { WagmiProvider, createConfig } from '@privy-io/wagmi'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\nimport { http } from 'viem'\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n retry: 2,\n staleTime: 30_000,\n },\n },\n})\n\n/**\n * ChainProvider\n *\n * Wraps your app with all providers required for ERC-4337:\n * Privy (auth + embedded wallets) → QueryClient → Wagmi\n *\n * Put this at the ROOT of your app, outside your router.\n *\n * @param {object} props\n * @param {string} props.privyAppId — from dashboard.privy.io\n * @param {object} props.chain — viem chain (e.g. polygonAmoy)\n * @param {string} props.rpcUrl — your Alchemy/Infura RPC URL\n * @param {string[]} [props.loginMethods] — default: ['google', 'email']\n * @param {object} [props.appearance] — Privy modal theme config\n * @param {node} props.children\n *\n * @example\n * import { ChainProvider } from '@atharva/erc4337-kit'\n * import { polygonAmoy } from 'viem/chains'\n *\n * <ChainProvider\n * privyAppId={import.meta.env.VITE_PRIVY_APP_ID}\n * chain={polygonAmoy}\n * rpcUrl={import.meta.env.VITE_RPC_URL}\n * >\n * <App />\n * </ChainProvider>\n */\nexport function ChainProvider({\n privyAppId,\n chain,\n rpcUrl,\n loginMethods = ['google', 'email'],\n appearance = {},\n children,\n}) {\n const wagmiConfig = createConfig({\n chains: [chain],\n transports: {\n [chain.id]: http(rpcUrl),\n },\n })\n\n return (\n <PrivyProvider\n appId={privyAppId}\n config={{\n loginMethods,\n embeddedWallets: {\n // CRITICAL: this tells Privy to create a wallet for EVERY user\n // automatically on login. Without this, you'd have to call\n // createWallet() manually and handle the timing yourself.\n createOnLogin: 'all-users',\n },\n defaultChain: chain,\n supportedChains: [chain],\n appearance: {\n theme: 'light',\n accentColor: '#7c3aed',\n ...appearance,\n },\n }}\n >\n <QueryClientProvider client={queryClient}>\n <WagmiProvider config={wagmiConfig}>\n {children}\n </WagmiProvider>\n </QueryClientProvider>\n </PrivyProvider>\n )\n}\n","import { useState, useCallback, useRef, useEffect } from 'react'\nimport { usePrivy, useWallets, useCreateWallet } from '@privy-io/react-auth'\nimport { createPublicClient, createWalletClient, http, custom } from 'viem'\nimport { createSmartAccountClient } from 'permissionless'\nimport { toSimpleSmartAccount } from 'permissionless/accounts'\nimport { createPimlicoClient } from 'permissionless/clients/pimlico'\nimport { entryPoint07Address } from 'viem/account-abstraction'\n\n// Internal helper — builds the Pimlico endpoint URL from chain ID\nfunction buildPimlicoUrl(chainId, apiKey) {\n return `https://api.pimlico.io/v2/${chainId}/rpc?apikey=${apiKey}`\n}\n\n/**\n * useSmartAccount\n *\n * Manages ERC-4337 Smart Account creation and lifecycle.\n * Handles Privy auth, embedded wallet creation, and Pimlico setup.\n *\n * @param {object} config\n * @param {string} config.pimlicoApiKey — from dashboard.pimlico.io\n * @param {string} config.rpcUrl — Alchemy/Infura RPC for your chain\n * @param {object} config.chain — viem chain object (e.g. polygonAmoy)\n *\n * @returns {object} {\n * login, logout, authenticated, user,\n * smartAccountAddress,\n * smartAccountClient, ← use this to send transactions\n * pimlicoClient,\n * isReady, ← true when SA is initialized and ready\n * isLoading,\n * error\n * }\n */\nexport function useSmartAccount({ pimlicoApiKey, rpcUrl, chain }) {\n const { login, logout, authenticated, user, ready } = usePrivy()\n const { wallets } = useWallets()\n const { createWallet } = useCreateWallet()\n\n const [smartAccountAddress, setSmartAccountAddress] = useState(null)\n const [smartAccountClient, setSmartAccountClient] = useState(null)\n const [pimlicoClient, setPimlicoClient] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState(null)\n\n // Refs to prevent duplicate initialization — same pattern as your ProofChain\n const initCalledRef = useRef(false)\n const walletCreationAttempted = useRef(false)\n\n const pimlicoUrl = buildPimlicoUrl(chain.id, pimlicoApiKey)\n\n const initSmartAccount = useCallback(async () => {\n // Guard: only proceed when Privy is fully ready and user is logged in\n if (!authenticated || !ready) return\n\n // If no wallet yet, try to create one (Privy sometimes needs a nudge)\n if (!wallets || wallets.length === 0) {\n if (!walletCreationAttempted.current) {\n walletCreationAttempted.current = true\n try {\n await createWallet()\n // Don't continue here — wait for next effect run after wallet appears\n return\n } catch (err) {\n // 'already has' means the wallet exists but wasn't in state yet — safe to ignore\n if (!err.message?.includes('already has')) {\n setError('Failed to create embedded wallet: ' + err.message)\n }\n return\n }\n }\n return\n }\n\n // Guard: don't initialize twice\n if (initCalledRef.current) return\n initCalledRef.current = true\n\n setIsLoading(true)\n setError(null)\n\n try {\n const wallet = wallets[0]\n\n // Switch to the configured chain before doing anything\n await wallet.switchChain(chain.id)\n\n const provider = await wallet.getEthereumProvider()\n\n // Wallet client signs UserOperations using the embedded wallet\n const walletClient = createWalletClient({\n account: wallet.address,\n chain,\n transport: custom(provider),\n })\n\n // Public client reads from chain (balance, contract state, etc.)\n const publicClient = createPublicClient({\n chain,\n transport: http(rpcUrl),\n })\n\n // Pimlico client handles bundling + gas sponsorship\n const pimlico = createPimlicoClient({\n transport: http(pimlicoUrl),\n entryPoint: {\n address: entryPoint07Address,\n version: '0.7',\n },\n })\n\n // SimpleSmartAccount: the simplest ERC-4337 account type\n // deterministic address — same owner always gets same SA address\n const smartAccount = await toSimpleSmartAccount({\n client: publicClient,\n owner: walletClient,\n entryPoint: {\n address: entryPoint07Address,\n version: '0.7',\n },\n })\n\n // SmartAccountClient: the object you use to send transactions\n // It automatically builds UserOperations, gets gas estimates,\n // requests paymaster sponsorship, and submits to the bundler\n const client = createSmartAccountClient({\n account: smartAccount,\n chain,\n bundlerTransport: http(pimlicoUrl),\n paymaster: pimlico,\n // userOperation config: tell Pimlico to sponsor everything\n userOperation: {\n estimateFeesPerGas: async () => {\n const fees = await pimlico.getUserOperationGasPrice()\n return fees.fast\n },\n },\n })\n\n setPimlicoClient(pimlico)\n setSmartAccountClient(client)\n setSmartAccountAddress(smartAccount.address)\n\n } catch (err) {\n console.error('[erc4337-kit] Smart account init failed:', err)\n setError(err.message || 'Failed to initialize smart account')\n // Reset so the user can retry\n initCalledRef.current = false\n } finally {\n setIsLoading(false)\n }\n }, [authenticated, wallets, ready, createWallet, chain, rpcUrl, pimlicoUrl])\n\n useEffect(() => {\n initSmartAccount()\n }, [initSmartAccount])\n\n const handleLogout = useCallback(async () => {\n await logout()\n // Full reset so next login starts fresh\n initCalledRef.current = false\n walletCreationAttempted.current = false\n setSmartAccountAddress(null)\n setSmartAccountClient(null)\n setPimlicoClient(null)\n setError(null)\n }, [logout])\n\n return {\n login,\n logout: handleLogout,\n authenticated,\n user,\n smartAccountAddress,\n smartAccountClient,\n pimlicoClient,\n isReady: !!smartAccountClient && !!smartAccountAddress,\n isLoading,\n error,\n }\n}\n","import { useState, useCallback } from 'react'\nimport { encodeFunctionData } from 'viem'\n\n/**\n * useStoreOnChain\n *\n * Generic hook to call any write function on any contract\n * via ERC-4337 gasless UserOperation.\n *\n * @param {object} params\n * @param {object} params.smartAccountClient — from useSmartAccount()\n * @param {string} params.contractAddress — deployed contract address\n * @param {array} params.abi — contract ABI (just the functions you need)\n * @param {string} params.functionName — which function to call\n *\n * @returns {object} {\n * submit(args), ← call this with your function arguments as an array\n * txHash,\n * recordId, ← decoded from logs if contract returns bytes32\n * isLoading,\n * isSuccess,\n * error,\n * reset\n * }\n *\n * @example\n * const { submit, txHash, isLoading } = useStoreOnChain({\n * smartAccountClient,\n * contractAddress: '0x...',\n * abi: incidentABI,\n * functionName: 'storeRecord',\n * })\n *\n * // In your handler:\n * await submit([dataHash])\n */\nexport function useStoreOnChain({\n smartAccountClient,\n contractAddress,\n abi,\n functionName,\n}) {\n const [txHash, setTxHash] = useState(null)\n const [recordId, setRecordId] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const [isSuccess, setIsSuccess] = useState(false)\n const [error, setError] = useState(null)\n\n const submit = useCallback(\n async (args = []) => {\n // Guard: smartAccountClient must exist (user must be logged in)\n if (!smartAccountClient) {\n setError('Smart account not initialized. Make sure user is logged in.')\n return null\n }\n\n setIsLoading(true)\n setIsSuccess(false)\n setError(null)\n setTxHash(null)\n setRecordId(null)\n\n try {\n // encodeFunctionData turns your ABI + args into the raw calldata bytes\n // that the smart account will call on the target contract\n const calldata = encodeFunctionData({\n abi,\n functionName,\n args,\n })\n\n // sendTransaction on a SmartAccountClient works differently than a normal\n // wallet tx. Under the hood it:\n // 1. Builds a UserOperation\n // 2. Estimates gas (callGasLimit, verificationGasLimit, preVerificationGas)\n // 3. Calls your paymaster (Pimlico) for sponsorship\n // 4. Signs the UserOperation with the embedded wallet\n // 5. Sends it to the Pimlico bundler\n // 6. Returns the tx hash once the bundler accepts it\n //\n // The tx hash here is the ACTUAL on-chain tx hash, not the UserOp hash.\n const hash = await smartAccountClient.sendTransaction({\n to: contractAddress,\n data: calldata,\n value: 0n, // no ETH/MATIC sent — this is just a contract call\n })\n\n setTxHash(hash)\n setIsSuccess(true)\n\n // Try to extract the returned bytes32 record ID from the receipt logs\n // This is specific to BaseStorage.sol which emits RecordStored(id, ...)\n try {\n const receipt = await smartAccountClient.waitForTransactionReceipt({ hash })\n const firstLog = receipt.logs?.[0]\n if (firstLog?.topics?.[1]) {\n setRecordId(firstLog.topics[1])\n }\n } catch {\n // Log parsing failing is not a fatal error — tx already succeeded\n }\n\n return hash\n\n } catch (err) {\n const message = parseError(err)\n setError(message)\n console.error('[erc4337-kit] Transaction failed:', err)\n return null\n } finally {\n setIsLoading(false)\n }\n },\n [smartAccountClient, contractAddress, abi, functionName]\n )\n\n const reset = useCallback(() => {\n setTxHash(null)\n setRecordId(null)\n setIsLoading(false)\n setIsSuccess(false)\n setError(null)\n }, [])\n\n return {\n submit,\n txHash,\n recordId,\n isLoading,\n isSuccess,\n error,\n reset,\n }\n}\n\n// -----------------------------------------------------------------\n// Internal: parse common ERC-4337 / Pimlico errors into human messages\n// These are the exact errors you hit during ProofChain development\n// -----------------------------------------------------------------\nfunction parseError(err) {\n const msg = err?.message || err?.toString() || 'Unknown error'\n\n if (msg.includes('AA21')) {\n return 'Paymaster rejected: your Pimlico API key may be invalid or the policy does not cover this chain.'\n }\n if (msg.includes('AA31')) {\n return 'Paymaster out of funds. Check your Pimlico dashboard deposit balance.'\n }\n if (msg.includes('AA23') || msg.includes('invalid signature')) {\n return 'Wallet signature failed. Try logging out and back in.'\n }\n if (msg.includes('gas') && msg.includes('too low')) {\n return 'Gas estimate too low. The contract function may be too expensive for the paymaster policy.'\n }\n if (msg.includes('nonce')) {\n return 'Nonce error. A previous transaction may still be pending — wait a moment and retry.'\n }\n if (msg.includes('user rejected') || msg.includes('User rejected')) {\n return 'Transaction was cancelled.'\n }\n if (msg.includes('fetch') || msg.includes('network')) {\n return 'Network error. Check your RPC URL and Pimlico API key.'\n }\n\n return msg\n}\n","/**\r\n * SHA-256 hashing utilities for client-side data hashing\r\n * \r\n * These functions hash data BEFORE sending to blockchain,\r\n * preserving privacy while creating tamper-proof proofs.\r\n */\r\n\r\n/**\r\n * Hash a string using SHA-256\r\n * \r\n * @param {string} text - The text to hash\r\n * @returns {Promise<string>} - Hex-encoded hash with 0x prefix\r\n * \r\n * @example\r\n * const hash = await sha256Hash(\"my secret data\")\r\n * // Returns: \"0x2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae\"\r\n */\r\nexport async function sha256Hash(text) {\r\n if (typeof text !== 'string') {\r\n throw new Error('sha256Hash: input must be a string')\r\n }\r\n\r\n const encoded = new TextEncoder().encode(text)\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', encoded)\r\n const hashArray = Array.from(new Uint8Array(hashBuffer))\r\n return '0x' + hashArray.map(b => b.toString(16).padStart(2, '0')).join('')\r\n}\r\n\r\n/**\r\n * Hash a File object using SHA-256\r\n * \r\n * @param {File} file - The file to hash\r\n * @returns {Promise<string>} - Hex-encoded hash with 0x prefix\r\n * \r\n * @example\r\n * const fileInput = document.querySelector('input[type=\"file\"]')\r\n * const file = fileInput.files[0]\r\n * const hash = await sha256HashFile(file)\r\n */\r\nexport async function sha256HashFile(file) {\r\n if (!(file instanceof File)) {\r\n throw new Error('sha256HashFile: input must be a File object')\r\n }\r\n\r\n const arrayBuffer = await file.arrayBuffer()\r\n const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer)\r\n const hashArray = Array.from(new Uint8Array(hashBuffer))\r\n return '0x' + hashArray.map(b => b.toString(16).padStart(2, '0')).join('')\r\n}\r\n"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "erc4337-kit",
3
+ "version": "0.1.0",
4
+ "description": "Plug-and-play ERC-4337 Account Abstraction for React apps. Gasless txs, social login, smart accounts — without the complexity.",
5
+ "author": "Atharva Baodhankar",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/op_athu_17/erc4337-kit"
10
+ },
11
+ "keywords": [
12
+ "erc4337",
13
+ "account-abstraction",
14
+ "gasless",
15
+ "privy",
16
+ "pimlico",
17
+ "polygon",
18
+ "web3",
19
+ "react",
20
+ "smart-account",
21
+ "userops"
22
+ ],
23
+
24
+ "type": "module",
25
+
26
+ "main": "./dist/index.cjs",
27
+ "module": "./dist/index.js",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.cjs"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src/contracts/BaseStorage.sol",
38
+ "README.md"
39
+ ],
40
+
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "dev": "tsup --watch",
44
+ "prepublishOnly": "npm run build"
45
+ },
46
+
47
+ "peerDependencies": {
48
+ "react": ">=18.0.0",
49
+ "react-dom": ">=18.0.0",
50
+ "@privy-io/react-auth":">=3.0.0",
51
+ "@privy-io/wagmi": ">=4.0.0",
52
+ "viem": ">=2.0.0",
53
+ "wagmi": ">=3.0.0",
54
+ "@tanstack/react-query":">=5.0.0"
55
+ },
56
+
57
+ "dependencies": {
58
+ "permissionless": "^0.3.4"
59
+ },
60
+
61
+ "devDependencies": {
62
+ "tsup": "^8.0.0",
63
+ "react": "^18.2.0",
64
+ "react-dom": "^18.2.0",
65
+ "typescript": "^5.0.0"
66
+ }
67
+ }
@@ -0,0 +1,221 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ // =============================================================
5
+ // ERC-4337 BASE STORAGE CONTRACT — erc4337-kit
6
+ // Copy this file, rename the contract, change the struct.
7
+ // DO NOT remove or change anything marked [ERC-4337 RULE].
8
+ // =============================================================
9
+ //
10
+ // WHAT THIS CONTRACT DOES:
11
+ // Stores a SHA-256 hash of your data permanently on-chain.
12
+ // The actual data never touches the blockchain — only the hash.
13
+ // This gives you tamper-proof proof that data existed at a
14
+ // specific time, without exposing any private content.
15
+ //
16
+ // HOW ERC-4337 CALLS THIS CONTRACT:
17
+ // Normal flow: User wallet → calls your contract
18
+ // ERC-4337 flow: User wallet → Smart Account → EntryPoint → your contract
19
+ //
20
+ // The key difference: msg.sender will NEVER be the user's real wallet
21
+ // address. It will be their Smart Account address (a contract).
22
+ // Keep this in mind when you design identity or access control.
23
+ //
24
+ // =============================================================
25
+
26
+ contract BaseStorage {
27
+
28
+ // ---------------------------------------------------------
29
+ // DATA STRUCTURES
30
+ // Customize the Record struct for your use case.
31
+ // Examples: add locationHash, severity, category, etc.
32
+ // ---------------------------------------------------------
33
+
34
+ struct Record {
35
+ bytes32 dataHash; // SHA-256 hash of your actual data (computed in the frontend)
36
+ uint256 timestamp; // block.timestamp at submission — fine for ordering, not for security
37
+ address submitter; // [ERC-4337 RULE] This is the Smart Account address, NOT the user's EOA
38
+ bool exists; // guard for duplicate-check pattern
39
+ }
40
+
41
+ // ---------------------------------------------------------
42
+ // STATE
43
+ // ---------------------------------------------------------
44
+
45
+ // Primary lookup: unique ID → Record
46
+ mapping(bytes32 => Record) private _records;
47
+
48
+ // Reverse lookup: submitter address → all their record IDs
49
+ // Lets you fetch "all records by this user" efficiently
50
+ mapping(address => bytes32[]) private _submitterRecords;
51
+
52
+ // Total count — useful for off-chain indexing
53
+ uint256 public totalRecords;
54
+
55
+ // ---------------------------------------------------------
56
+ // EVENTS
57
+ // Always emit events. Off-chain apps (your frontend, indexers)
58
+ // listen to these to know something happened without polling.
59
+ // ---------------------------------------------------------
60
+
61
+ event RecordStored(
62
+ bytes32 indexed id,
63
+ bytes32 indexed dataHash,
64
+ address indexed submitter,
65
+ uint256 timestamp
66
+ );
67
+
68
+ event RecordUpdated(
69
+ bytes32 indexed id,
70
+ bytes32 newDataHash,
71
+ uint256 timestamp
72
+ );
73
+
74
+ // ---------------------------------------------------------
75
+ // ERRORS
76
+ // Custom errors use less gas than require strings.
77
+ // Use these instead of require("...") in production.
78
+ // ---------------------------------------------------------
79
+
80
+ error RecordAlreadyExists(bytes32 id);
81
+ error RecordNotFound(bytes32 id);
82
+ error NotSubmitter(address caller, address expected);
83
+
84
+ // ---------------------------------------------------------
85
+ // WRITE FUNCTIONS
86
+ // ---------------------------------------------------------
87
+
88
+ /**
89
+ * @notice Store a new record on-chain.
90
+ *
91
+ * @dev [ERC-4337 RULE] msg.sender here is the user's Smart Account,
92
+ * not their original EOA (e.g. Google-login wallet). If you need
93
+ * to track the original user, pass an identifier in calldata
94
+ * (like a hash of their email) and store it in the struct.
95
+ *
96
+ * @dev [ERC-4337 RULE] Do NOT do heavy computation here.
97
+ * Paymasters cap gas. If this function is too expensive,
98
+ * the UserOp will be rejected before it even reaches chain.
99
+ * Keep storage writes minimal. One SSTORE = ~20,000 gas.
100
+ *
101
+ * @param dataHash SHA-256 hash computed in the frontend. Never send
102
+ * raw data — only the hash belongs on-chain.
103
+ *
104
+ * @return id Unique identifier for this record (use this to verify later)
105
+ */
106
+ function storeRecord(bytes32 dataHash) external returns (bytes32 id) {
107
+ // Generate a deterministic ID from hash + block + sender
108
+ // This makes IDs reproducible for the same input in the same block
109
+ id = keccak256(abi.encodePacked(dataHash, block.timestamp, msg.sender));
110
+
111
+ // Revert if this exact ID was already stored
112
+ // (prevents accidental double-submit)
113
+ if (_records[id].exists) revert RecordAlreadyExists(id);
114
+
115
+ // Write to storage
116
+ _records[id] = Record({
117
+ dataHash: dataHash,
118
+ timestamp: block.timestamp,
119
+ submitter: msg.sender, // Smart Account address
120
+ exists: true
121
+ });
122
+
123
+ // Update reverse index
124
+ _submitterRecords[msg.sender].push(id);
125
+ totalRecords++;
126
+
127
+ emit RecordStored(id, dataHash, msg.sender, block.timestamp);
128
+ }
129
+
130
+ // ---------------------------------------------------------
131
+ // READ FUNCTIONS
132
+ // These are free (no gas) — call them as often as you want.
133
+ // ---------------------------------------------------------
134
+
135
+ /**
136
+ * @notice Get a record by ID.
137
+ * @dev Returns all fields. Your frontend can use dataHash to
138
+ * verify against the original data the user still has.
139
+ */
140
+ function getRecord(bytes32 id)
141
+ external
142
+ view
143
+ returns (
144
+ bytes32 dataHash,
145
+ uint256 timestamp,
146
+ address submitter
147
+ )
148
+ {
149
+ if (!_records[id].exists) revert RecordNotFound(id);
150
+ Record storage r = _records[id];
151
+ return (r.dataHash, r.timestamp, r.submitter);
152
+ }
153
+
154
+ /**
155
+ * @notice Verify: does this record exist AND match the given hash?
156
+ * @dev This is your tamper-proof check. If someone gives you
157
+ * the original data + a record ID, you hash the data and
158
+ * call this. If it returns true, the data is authentic.
159
+ *
160
+ * @param id Record ID returned from storeRecord()
161
+ * @param dataHash SHA-256 hash you computed of the original data
162
+ * @return bool true = record exists and hash matches
163
+ */
164
+ function verifyRecord(bytes32 id, bytes32 dataHash)
165
+ external
166
+ view
167
+ returns (bool)
168
+ {
169
+ if (!_records[id].exists) return false;
170
+ return _records[id].dataHash == dataHash;
171
+ }
172
+
173
+ /**
174
+ * @notice Get all record IDs submitted by a specific Smart Account.
175
+ * @dev Pass the Smart Account address (not the user's EOA).
176
+ * Your frontend gets this from useSmartAccount().smartAccountAddress
177
+ */
178
+ function getRecordsBySubmitter(address submitter)
179
+ external
180
+ view
181
+ returns (bytes32[] memory)
182
+ {
183
+ return _submitterRecords[submitter];
184
+ }
185
+
186
+ /**
187
+ * @notice Check if a record exists without reverting.
188
+ * @dev Useful for frontend validation before showing a verify button.
189
+ */
190
+ function recordExists(bytes32 id) external view returns (bool) {
191
+ return _records[id].exists;
192
+ }
193
+ }
194
+
195
+ // =============================================================
196
+ // HOW TO CUSTOMIZE THIS CONTRACT
197
+ //
198
+ // 1. RENAME IT:
199
+ // contract IncidentRegistry { ... }
200
+ // contract DrugVerification { ... }
201
+ // contract DocumentProof { ... }
202
+ //
203
+ // 2. ADD FIELDS TO THE STRUCT:
204
+ // struct Record {
205
+ // bytes32 dataHash;
206
+ // uint256 timestamp;
207
+ // address submitter;
208
+ // bool exists;
209
+ // // ADD YOUR FIELDS:
210
+ // string locationHash; // hashed GPS coords
211
+ // uint8 severity; // 1-5 scale
212
+ // bytes32 category; // incident type, drug type, etc.
213
+ // }
214
+ //
215
+ // 3. UPDATE storeRecord() PARAMS:
216
+ // function storeRecord(bytes32 dataHash, string calldata locationHash, uint8 severity)
217
+ //
218
+ // 4. THAT'S IT. The ERC-4337 rules above still apply.
219
+ // You do NOT need to change anything about how UserOps work.
220
+ // The SDK handles all of that.
221
+ // =============================================================