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 +167 -0
- package/dist/index.cjs +321 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +295 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
- package/src/contracts/BaseStorage.sol +221 -0
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
|
+
// =============================================================
|