├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── tsconfig.json ├── tutorial-1.ts ├── tutorial-2.ts └── tutorial-3.ts /.env.example: -------------------------------------------------------------------------------- 1 | PIMLICO_API_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Pimlico 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pimlico Tutorials 2 | 3 | This repository contains the full code for [Pimlico tutorials](https://docs.pimlico.io/tutorial) in the Pimlico documentation. 4 | 5 | To set up the repository, clone it, copy the .env.example file to .env and fill in your Pimlico API key (use the [quick start guide](https://docs.pimlico.io/how-to/quick-start) to generate one), install the dependencies, and run `npm run tutorial-1`! 6 | 7 | If you are looking to run the tutorial code for [tutorial 2](https://docs.pimlico.io/tutorial/tutorial-2), in addition to filling the Pimlico API key, you will also need to generate a private key and replace the `privateKey` variable at the start of `tutorial-2.ts` with it before running `npm run tutorial-2`. 8 | 9 | ```bash 10 | npm install 11 | cp .env.example .env 12 | # fill in your Pimlico API key in .env 13 | npm run tutorial-1 14 | ``` 15 | 16 | If everything works correctly, you should deploy a User Operation as per the flow of the tutorial. 17 | 18 | Good luck! 19 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pimlico-tutorials", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "pimlico-tutorials", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "dotenv": "^16.3.1", 13 | "permissionless": "0.2.0-rc-5", 14 | "viem": "2.20.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20.11.0", 18 | "tsx": "^3.13.0" 19 | } 20 | }, 21 | "node_modules/@adraffy/ens-normalize": { 22 | "version": "1.10.0", 23 | "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", 24 | "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" 25 | }, 26 | "node_modules/@esbuild/android-arm": { 27 | "version": "0.18.20", 28 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 29 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 30 | "cpu": [ 31 | "arm" 32 | ], 33 | "dev": true, 34 | "optional": true, 35 | "os": [ 36 | "android" 37 | ], 38 | "engines": { 39 | "node": ">=12" 40 | } 41 | }, 42 | "node_modules/@esbuild/android-arm64": { 43 | "version": "0.18.20", 44 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 45 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 46 | "cpu": [ 47 | "arm64" 48 | ], 49 | "dev": true, 50 | "optional": true, 51 | "os": [ 52 | "android" 53 | ], 54 | "engines": { 55 | "node": ">=12" 56 | } 57 | }, 58 | "node_modules/@esbuild/android-x64": { 59 | "version": "0.18.20", 60 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 61 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 62 | "cpu": [ 63 | "x64" 64 | ], 65 | "dev": true, 66 | "optional": true, 67 | "os": [ 68 | "android" 69 | ], 70 | "engines": { 71 | "node": ">=12" 72 | } 73 | }, 74 | "node_modules/@esbuild/darwin-arm64": { 75 | "version": "0.18.20", 76 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 77 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 78 | "cpu": [ 79 | "arm64" 80 | ], 81 | "dev": true, 82 | "optional": true, 83 | "os": [ 84 | "darwin" 85 | ], 86 | "engines": { 87 | "node": ">=12" 88 | } 89 | }, 90 | "node_modules/@esbuild/darwin-x64": { 91 | "version": "0.18.20", 92 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 93 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 94 | "cpu": [ 95 | "x64" 96 | ], 97 | "dev": true, 98 | "optional": true, 99 | "os": [ 100 | "darwin" 101 | ], 102 | "engines": { 103 | "node": ">=12" 104 | } 105 | }, 106 | "node_modules/@esbuild/freebsd-arm64": { 107 | "version": "0.18.20", 108 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 109 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 110 | "cpu": [ 111 | "arm64" 112 | ], 113 | "dev": true, 114 | "optional": true, 115 | "os": [ 116 | "freebsd" 117 | ], 118 | "engines": { 119 | "node": ">=12" 120 | } 121 | }, 122 | "node_modules/@esbuild/freebsd-x64": { 123 | "version": "0.18.20", 124 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 125 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 126 | "cpu": [ 127 | "x64" 128 | ], 129 | "dev": true, 130 | "optional": true, 131 | "os": [ 132 | "freebsd" 133 | ], 134 | "engines": { 135 | "node": ">=12" 136 | } 137 | }, 138 | "node_modules/@esbuild/linux-arm": { 139 | "version": "0.18.20", 140 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 141 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 142 | "cpu": [ 143 | "arm" 144 | ], 145 | "dev": true, 146 | "optional": true, 147 | "os": [ 148 | "linux" 149 | ], 150 | "engines": { 151 | "node": ">=12" 152 | } 153 | }, 154 | "node_modules/@esbuild/linux-arm64": { 155 | "version": "0.18.20", 156 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 157 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 158 | "cpu": [ 159 | "arm64" 160 | ], 161 | "dev": true, 162 | "optional": true, 163 | "os": [ 164 | "linux" 165 | ], 166 | "engines": { 167 | "node": ">=12" 168 | } 169 | }, 170 | "node_modules/@esbuild/linux-ia32": { 171 | "version": "0.18.20", 172 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 173 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 174 | "cpu": [ 175 | "ia32" 176 | ], 177 | "dev": true, 178 | "optional": true, 179 | "os": [ 180 | "linux" 181 | ], 182 | "engines": { 183 | "node": ">=12" 184 | } 185 | }, 186 | "node_modules/@esbuild/linux-loong64": { 187 | "version": "0.18.20", 188 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 189 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 190 | "cpu": [ 191 | "loong64" 192 | ], 193 | "dev": true, 194 | "optional": true, 195 | "os": [ 196 | "linux" 197 | ], 198 | "engines": { 199 | "node": ">=12" 200 | } 201 | }, 202 | "node_modules/@esbuild/linux-mips64el": { 203 | "version": "0.18.20", 204 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 205 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 206 | "cpu": [ 207 | "mips64el" 208 | ], 209 | "dev": true, 210 | "optional": true, 211 | "os": [ 212 | "linux" 213 | ], 214 | "engines": { 215 | "node": ">=12" 216 | } 217 | }, 218 | "node_modules/@esbuild/linux-ppc64": { 219 | "version": "0.18.20", 220 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 221 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 222 | "cpu": [ 223 | "ppc64" 224 | ], 225 | "dev": true, 226 | "optional": true, 227 | "os": [ 228 | "linux" 229 | ], 230 | "engines": { 231 | "node": ">=12" 232 | } 233 | }, 234 | "node_modules/@esbuild/linux-riscv64": { 235 | "version": "0.18.20", 236 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 237 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 238 | "cpu": [ 239 | "riscv64" 240 | ], 241 | "dev": true, 242 | "optional": true, 243 | "os": [ 244 | "linux" 245 | ], 246 | "engines": { 247 | "node": ">=12" 248 | } 249 | }, 250 | "node_modules/@esbuild/linux-s390x": { 251 | "version": "0.18.20", 252 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 253 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 254 | "cpu": [ 255 | "s390x" 256 | ], 257 | "dev": true, 258 | "optional": true, 259 | "os": [ 260 | "linux" 261 | ], 262 | "engines": { 263 | "node": ">=12" 264 | } 265 | }, 266 | "node_modules/@esbuild/linux-x64": { 267 | "version": "0.18.20", 268 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 269 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 270 | "cpu": [ 271 | "x64" 272 | ], 273 | "dev": true, 274 | "optional": true, 275 | "os": [ 276 | "linux" 277 | ], 278 | "engines": { 279 | "node": ">=12" 280 | } 281 | }, 282 | "node_modules/@esbuild/netbsd-x64": { 283 | "version": "0.18.20", 284 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 285 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 286 | "cpu": [ 287 | "x64" 288 | ], 289 | "dev": true, 290 | "optional": true, 291 | "os": [ 292 | "netbsd" 293 | ], 294 | "engines": { 295 | "node": ">=12" 296 | } 297 | }, 298 | "node_modules/@esbuild/openbsd-x64": { 299 | "version": "0.18.20", 300 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 301 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 302 | "cpu": [ 303 | "x64" 304 | ], 305 | "dev": true, 306 | "optional": true, 307 | "os": [ 308 | "openbsd" 309 | ], 310 | "engines": { 311 | "node": ">=12" 312 | } 313 | }, 314 | "node_modules/@esbuild/sunos-x64": { 315 | "version": "0.18.20", 316 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 317 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 318 | "cpu": [ 319 | "x64" 320 | ], 321 | "dev": true, 322 | "optional": true, 323 | "os": [ 324 | "sunos" 325 | ], 326 | "engines": { 327 | "node": ">=12" 328 | } 329 | }, 330 | "node_modules/@esbuild/win32-arm64": { 331 | "version": "0.18.20", 332 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 333 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 334 | "cpu": [ 335 | "arm64" 336 | ], 337 | "dev": true, 338 | "optional": true, 339 | "os": [ 340 | "win32" 341 | ], 342 | "engines": { 343 | "node": ">=12" 344 | } 345 | }, 346 | "node_modules/@esbuild/win32-ia32": { 347 | "version": "0.18.20", 348 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 349 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 350 | "cpu": [ 351 | "ia32" 352 | ], 353 | "dev": true, 354 | "optional": true, 355 | "os": [ 356 | "win32" 357 | ], 358 | "engines": { 359 | "node": ">=12" 360 | } 361 | }, 362 | "node_modules/@esbuild/win32-x64": { 363 | "version": "0.18.20", 364 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 365 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 366 | "cpu": [ 367 | "x64" 368 | ], 369 | "dev": true, 370 | "optional": true, 371 | "os": [ 372 | "win32" 373 | ], 374 | "engines": { 375 | "node": ">=12" 376 | } 377 | }, 378 | "node_modules/@noble/curves": { 379 | "version": "1.4.0", 380 | "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", 381 | "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", 382 | "dependencies": { 383 | "@noble/hashes": "1.4.0" 384 | }, 385 | "funding": { 386 | "url": "https://paulmillr.com/funding/" 387 | } 388 | }, 389 | "node_modules/@noble/hashes": { 390 | "version": "1.4.0", 391 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", 392 | "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", 393 | "engines": { 394 | "node": ">= 16" 395 | }, 396 | "funding": { 397 | "url": "https://paulmillr.com/funding/" 398 | } 399 | }, 400 | "node_modules/@scure/base": { 401 | "version": "1.1.7", 402 | "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", 403 | "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", 404 | "funding": { 405 | "url": "https://paulmillr.com/funding/" 406 | } 407 | }, 408 | "node_modules/@scure/bip32": { 409 | "version": "1.4.0", 410 | "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", 411 | "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", 412 | "dependencies": { 413 | "@noble/curves": "~1.4.0", 414 | "@noble/hashes": "~1.4.0", 415 | "@scure/base": "~1.1.6" 416 | }, 417 | "funding": { 418 | "url": "https://paulmillr.com/funding/" 419 | } 420 | }, 421 | "node_modules/@scure/bip39": { 422 | "version": "1.3.0", 423 | "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", 424 | "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", 425 | "dependencies": { 426 | "@noble/hashes": "~1.4.0", 427 | "@scure/base": "~1.1.6" 428 | }, 429 | "funding": { 430 | "url": "https://paulmillr.com/funding/" 431 | } 432 | }, 433 | "node_modules/@types/node": { 434 | "version": "20.11.0", 435 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", 436 | "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", 437 | "dev": true, 438 | "dependencies": { 439 | "undici-types": "~5.26.4" 440 | } 441 | }, 442 | "node_modules/abitype": { 443 | "version": "1.0.5", 444 | "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.5.tgz", 445 | "integrity": "sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==", 446 | "funding": { 447 | "url": "https://github.com/sponsors/wevm" 448 | }, 449 | "peerDependencies": { 450 | "typescript": ">=5.0.4", 451 | "zod": "^3 >=3.22.0" 452 | }, 453 | "peerDependenciesMeta": { 454 | "typescript": { 455 | "optional": true 456 | }, 457 | "zod": { 458 | "optional": true 459 | } 460 | } 461 | }, 462 | "node_modules/buffer-from": { 463 | "version": "1.1.2", 464 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 465 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 466 | "dev": true 467 | }, 468 | "node_modules/dotenv": { 469 | "version": "16.3.1", 470 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", 471 | "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", 472 | "engines": { 473 | "node": ">=12" 474 | }, 475 | "funding": { 476 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 477 | } 478 | }, 479 | "node_modules/esbuild": { 480 | "version": "0.18.20", 481 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 482 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 483 | "dev": true, 484 | "hasInstallScript": true, 485 | "bin": { 486 | "esbuild": "bin/esbuild" 487 | }, 488 | "engines": { 489 | "node": ">=12" 490 | }, 491 | "optionalDependencies": { 492 | "@esbuild/android-arm": "0.18.20", 493 | "@esbuild/android-arm64": "0.18.20", 494 | "@esbuild/android-x64": "0.18.20", 495 | "@esbuild/darwin-arm64": "0.18.20", 496 | "@esbuild/darwin-x64": "0.18.20", 497 | "@esbuild/freebsd-arm64": "0.18.20", 498 | "@esbuild/freebsd-x64": "0.18.20", 499 | "@esbuild/linux-arm": "0.18.20", 500 | "@esbuild/linux-arm64": "0.18.20", 501 | "@esbuild/linux-ia32": "0.18.20", 502 | "@esbuild/linux-loong64": "0.18.20", 503 | "@esbuild/linux-mips64el": "0.18.20", 504 | "@esbuild/linux-ppc64": "0.18.20", 505 | "@esbuild/linux-riscv64": "0.18.20", 506 | "@esbuild/linux-s390x": "0.18.20", 507 | "@esbuild/linux-x64": "0.18.20", 508 | "@esbuild/netbsd-x64": "0.18.20", 509 | "@esbuild/openbsd-x64": "0.18.20", 510 | "@esbuild/sunos-x64": "0.18.20", 511 | "@esbuild/win32-arm64": "0.18.20", 512 | "@esbuild/win32-ia32": "0.18.20", 513 | "@esbuild/win32-x64": "0.18.20" 514 | } 515 | }, 516 | "node_modules/fsevents": { 517 | "version": "2.3.3", 518 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 519 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 520 | "dev": true, 521 | "hasInstallScript": true, 522 | "optional": true, 523 | "os": [ 524 | "darwin" 525 | ], 526 | "engines": { 527 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 528 | } 529 | }, 530 | "node_modules/get-tsconfig": { 531 | "version": "4.7.2", 532 | "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", 533 | "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", 534 | "dev": true, 535 | "dependencies": { 536 | "resolve-pkg-maps": "^1.0.0" 537 | }, 538 | "funding": { 539 | "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 540 | } 541 | }, 542 | "node_modules/isows": { 543 | "version": "1.0.4", 544 | "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.4.tgz", 545 | "integrity": "sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==", 546 | "funding": [ 547 | { 548 | "type": "github", 549 | "url": "https://github.com/sponsors/wagmi-dev" 550 | } 551 | ], 552 | "peerDependencies": { 553 | "ws": "*" 554 | } 555 | }, 556 | "node_modules/permissionless": { 557 | "version": "0.2.0-rc-5", 558 | "resolved": "https://registry.npmjs.org/permissionless/-/permissionless-0.2.0-rc-5.tgz", 559 | "integrity": "sha512-aZhr7/jrXSjYk5yBKXtragikFxsDBL+LaUrbjPmAqbOcsNz3lQk96ga4QOcAJUQZbBs0ZGtZ4IM7l6qp1MUDcA==", 560 | "peerDependencies": { 561 | "viem": "^2.20.0" 562 | } 563 | }, 564 | "node_modules/resolve-pkg-maps": { 565 | "version": "1.0.0", 566 | "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 567 | "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 568 | "dev": true, 569 | "funding": { 570 | "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 571 | } 572 | }, 573 | "node_modules/source-map": { 574 | "version": "0.6.1", 575 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 576 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 577 | "dev": true, 578 | "engines": { 579 | "node": ">=0.10.0" 580 | } 581 | }, 582 | "node_modules/source-map-support": { 583 | "version": "0.5.21", 584 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 585 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 586 | "dev": true, 587 | "dependencies": { 588 | "buffer-from": "^1.0.0", 589 | "source-map": "^0.6.0" 590 | } 591 | }, 592 | "node_modules/tsx": { 593 | "version": "3.13.0", 594 | "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.13.0.tgz", 595 | "integrity": "sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==", 596 | "dev": true, 597 | "dependencies": { 598 | "esbuild": "~0.18.20", 599 | "get-tsconfig": "^4.7.2", 600 | "source-map-support": "^0.5.21" 601 | }, 602 | "bin": { 603 | "tsx": "dist/cli.mjs" 604 | }, 605 | "optionalDependencies": { 606 | "fsevents": "~2.3.3" 607 | } 608 | }, 609 | "node_modules/undici-types": { 610 | "version": "5.26.5", 611 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 612 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 613 | "dev": true 614 | }, 615 | "node_modules/viem": { 616 | "version": "2.20.0", 617 | "resolved": "https://registry.npmjs.org/viem/-/viem-2.20.0.tgz", 618 | "integrity": "sha512-cM4vs81HnSNbfceI1MLkx4pCVzbVjl9xiNSv5SCutYjUyFFOVSPDlEyhpg2iHinxx1NM4Qne3END5eLT8rvUdg==", 619 | "funding": [ 620 | { 621 | "type": "github", 622 | "url": "https://github.com/sponsors/wevm" 623 | } 624 | ], 625 | "dependencies": { 626 | "@adraffy/ens-normalize": "1.10.0", 627 | "@noble/curves": "1.4.0", 628 | "@noble/hashes": "1.4.0", 629 | "@scure/bip32": "1.4.0", 630 | "@scure/bip39": "1.3.0", 631 | "abitype": "1.0.5", 632 | "isows": "1.0.4", 633 | "webauthn-p256": "0.0.5", 634 | "ws": "8.17.1" 635 | }, 636 | "peerDependencies": { 637 | "typescript": ">=5.0.4" 638 | }, 639 | "peerDependenciesMeta": { 640 | "typescript": { 641 | "optional": true 642 | } 643 | } 644 | }, 645 | "node_modules/webauthn-p256": { 646 | "version": "0.0.5", 647 | "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.5.tgz", 648 | "integrity": "sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==", 649 | "funding": [ 650 | { 651 | "type": "github", 652 | "url": "https://github.com/sponsors/wevm" 653 | } 654 | ], 655 | "dependencies": { 656 | "@noble/curves": "^1.4.0", 657 | "@noble/hashes": "^1.4.0" 658 | } 659 | }, 660 | "node_modules/ws": { 661 | "version": "8.17.1", 662 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", 663 | "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", 664 | "engines": { 665 | "node": ">=10.0.0" 666 | }, 667 | "peerDependencies": { 668 | "bufferutil": "^4.0.1", 669 | "utf-8-validate": ">=5.0.2" 670 | }, 671 | "peerDependenciesMeta": { 672 | "bufferutil": { 673 | "optional": true 674 | }, 675 | "utf-8-validate": { 676 | "optional": true 677 | } 678 | } 679 | } 680 | } 681 | } 682 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pimlico-tutorials", 3 | "version": "1.0.0", 4 | "description": "A repository containing the full code for the Pimlico tutorials (https://docs.pimlico.io/permissionless)", 5 | "main": "index.ts", 6 | "type": "module", 7 | "scripts": { 8 | "tutorial-1": "tsx tutorial-1.ts", 9 | "tutorial-2": "tsx tutorial-2.ts", 10 | "tutorial-3": "tsx tutorial-3.ts" 11 | }, 12 | "author": "", 13 | "license": "MIT", 14 | "dependencies": { 15 | "dotenv": "^16.3.1", 16 | "permissionless": "0.2.0-rc-5", 17 | "viem": "2.20.0" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^20.11.0", 21 | "tsx": "^3.13.0" 22 | } 23 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | /* Projects */ 5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 11 | /* Language and Environment */ 12 | "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 13 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 14 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 15 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 16 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 17 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 18 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 19 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 20 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 21 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 22 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 23 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 24 | /* Modules */ 25 | "module": "ES2022", /* Specify what module code is generated. */ 26 | // "rootDir": "./", /* Specify the root folder within your source files. */ 27 | "moduleResolution": "Node", /* Specify how TypeScript looks up a file from a given module specifier. */ 28 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 29 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 30 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 31 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 32 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 33 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 34 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 35 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 36 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 37 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 38 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 39 | // "resolveJsonModule": true, /* Enable importing .json files. */ 40 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 41 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 42 | /* JavaScript Support */ 43 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 44 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 45 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 52 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 53 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 54 | // "removeComments": true, /* Disable emitting comments. */ 55 | // "noEmit": true, /* Disable emitting files from a compilation. */ 56 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 57 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 58 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 59 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | /* Interop Constraints */ 71 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 72 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | /* Type Checking */ 78 | "strict": true, /* Enable all strict type-checking options. */ 79 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 80 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 81 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 82 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 83 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 84 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 85 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 86 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 87 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 88 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 89 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 90 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 91 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 92 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 93 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 94 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 95 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 96 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | }, 101 | } -------------------------------------------------------------------------------- /tutorial-1.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { writeFileSync } from "fs" 3 | import { toSafeSmartAccount } from "permissionless/accounts" 4 | import { Hex, createPublicClient, getContract, http } from "viem" 5 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 6 | import { sepolia } from "viem/chains" 7 | import { createPimlicoClient } from "permissionless/clients/pimlico" 8 | import { createBundlerClient, entryPoint07Address } from "viem/account-abstraction" 9 | import { createSmartAccountClient } from "permissionless" 10 | 11 | const apiKey = process.env.PIMLICO_API_KEY 12 | if (!apiKey) throw new Error("Missing PIMLICO_API_KEY") 13 | 14 | const privateKey = 15 | (process.env.PRIVATE_KEY as Hex) ?? 16 | (() => { 17 | const pk = generatePrivateKey() 18 | writeFileSync(".env", `PRIVATE_KEY=${pk}`) 19 | return pk 20 | })() 21 | 22 | export const publicClient = createPublicClient({ 23 | chain: sepolia, 24 | transport: http("https://rpc.ankr.com/eth_sepolia"), 25 | }) 26 | 27 | const account = await toSafeSmartAccount({ 28 | client: publicClient, 29 | owner: privateKeyToAccount(privateKey), 30 | entryPoint: { 31 | address: entryPoint07Address, 32 | version: "0.7" 33 | }, // global entrypoint 34 | version: "1.4.1", 35 | }) 36 | 37 | console.log({ 38 | accountAddress: await account.getAddress(), 39 | }) 40 | 41 | console.log(`Smart account address: https://sepolia.etherscan.io/address/${account.address}`) 42 | 43 | const pimlicoUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}` 44 | 45 | const pimlicoClient = createPimlicoClient({ 46 | transport: http(pimlicoUrl), 47 | entryPoint: { 48 | address: entryPoint07Address, 49 | version: "0.7", 50 | } 51 | }) 52 | 53 | { 54 | const smartAccountClient = createSmartAccountClient({ 55 | account, 56 | chain: sepolia, 57 | bundlerTransport: http(pimlicoUrl), 58 | paymaster: pimlicoClient, 59 | userOperation: { 60 | estimateFeesPerGas: async () => { 61 | return (await pimlicoClient.getUserOperationGasPrice()).fast 62 | }, 63 | } 64 | }) 65 | 66 | const txHash = await smartAccountClient.sendTransaction({ 67 | to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 68 | value: 0n, 69 | data: "0x1234", 70 | 71 | }) 72 | 73 | console.log(`User operation with single transaction included: https://sepolia.etherscan.io/tx/${txHash}`) 74 | 75 | const contract = getContract({ 76 | address: "0x6D7A849791a8E869892f11E01c2A5f3b25a497B6", 77 | abi: [{"inputs":[],"name":"getLastGreeter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"greet","outputs":[],"stateMutability":"nonpayable","type":"function"}], 78 | client: { 79 | public: publicClient, 80 | wallet: smartAccountClient, 81 | } 82 | }) 83 | 84 | const txHash2 = await contract.write.greet() 85 | 86 | console.log(`User operation with contract call included: https://sepolia.etherscan.io/tx/${txHash2}`) 87 | 88 | const txHashMultiple = await smartAccountClient.sendTransaction({ 89 | calls: [ 90 | { 91 | to: "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", 92 | value: 0n, 93 | data: "0x1234", 94 | }, 95 | { 96 | abi: [{"inputs":[],"name":"getLastGreeter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"greet","outputs":[],"stateMutability":"nonpayable","type":"function"}], 97 | functionName: "greet", 98 | args: [], 99 | to: "0x6D7A849791a8E869892f11E01c2A5f3b25a497B6" 100 | } 101 | ], 102 | }) 103 | 104 | console.log(`User operation with multiple transactions included: https://sepolia.etherscan.io/tx/${txHashMultiple}`) 105 | } 106 | 107 | { 108 | const bundlerClient = createBundlerClient({ 109 | account, 110 | chain: sepolia, 111 | transport: http(pimlicoUrl), 112 | paymaster: pimlicoClient, 113 | userOperation: { 114 | estimateFeesPerGas: async () => { 115 | return (await pimlicoClient.getUserOperationGasPrice()).fast 116 | }, 117 | } 118 | }) 119 | 120 | const userOpHash = await bundlerClient.sendUserOperation({ 121 | calls: [{ 122 | to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 123 | value: 0n, 124 | data: "0x1234", 125 | }] 126 | }) 127 | 128 | const receipt = await bundlerClient.waitForUserOperationReceipt({ 129 | hash: userOpHash, 130 | }) 131 | 132 | console.log(`User operation included: https://sepolia.etherscan.io/tx/${receipt.receipt.transactionHash}`) 133 | 134 | const txHashMultiple = await bundlerClient.sendUserOperation({ 135 | calls: [ 136 | { 137 | to: "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", 138 | value: 0n, 139 | data: "0x1234", 140 | }, 141 | { 142 | to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 143 | value: 0n, 144 | data: "0x1234", 145 | } 146 | ], 147 | }) 148 | 149 | const receipt2 = await bundlerClient.getUserOperationReceipt({ 150 | hash: txHashMultiple, 151 | }) 152 | 153 | console.log(`User operation included: https://sepolia.etherscan.io/tx/${receipt2.receipt.transactionHash}`) 154 | } 155 | -------------------------------------------------------------------------------- /tutorial-2.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { createSmartAccountClient } from "permissionless"; 3 | import { toSafeSmartAccount } from "permissionless/accounts"; 4 | import { createPimlicoClient } from "permissionless/clients/pimlico"; 5 | import { 6 | createPublicClient, 7 | getAddress, 8 | Hex, 9 | http, 10 | maxUint256, 11 | parseAbi, 12 | } from "viem"; 13 | import { 14 | entryPoint07Address, 15 | EntryPointVersion, 16 | } from "viem/account-abstraction"; 17 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 18 | import { baseSepolia } from "viem/chains"; 19 | import { writeFileSync } from "fs"; 20 | 21 | const usdc = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; 22 | const paymaster = "0x0000000000000039cd5e8ae05257ce51c473ddd1"; 23 | 24 | const apiKey = process.env.PIMLICO_API_KEY; 25 | const pimlicoUrl = `https://api.pimlico.io/v2/${baseSepolia.id}/rpc?apikey=${apiKey}`; 26 | 27 | const privateKey = 28 | (process.env.PRIVATE_KEY as Hex) ?? 29 | (() => { 30 | const pk = generatePrivateKey(); 31 | writeFileSync(".env", `PRIVATE_KEY=${pk}`); 32 | return pk; 33 | })(); 34 | 35 | const publicClient = createPublicClient({ 36 | chain: baseSepolia, 37 | transport: http("https://sepolia.base.org"), 38 | }); 39 | 40 | const pimlicoClient = createPimlicoClient({ 41 | chain: baseSepolia, 42 | transport: http(pimlicoUrl), 43 | entryPoint: { 44 | address: entryPoint07Address, 45 | version: "0.7" as EntryPointVersion, 46 | }, 47 | }); 48 | 49 | const account = await toSafeSmartAccount({ 50 | client: publicClient, 51 | owners: [privateKeyToAccount(privateKey)], 52 | version: "1.4.1", 53 | }); 54 | 55 | const smartAccountClient = createSmartAccountClient({ 56 | account, 57 | chain: baseSepolia, 58 | bundlerTransport: http(pimlicoUrl), 59 | paymaster: pimlicoClient, 60 | userOperation: { 61 | estimateFeesPerGas: async () => { 62 | return (await pimlicoClient.getUserOperationGasPrice()).fast; 63 | }, 64 | }, 65 | }); 66 | 67 | console.log( 68 | `Smart account address: https://sepolia.basescan.org/address/${account.address}`, 69 | ); 70 | 71 | const senderUsdcBalance = await publicClient.readContract({ 72 | abi: parseAbi(["function balanceOf(address account) returns (uint256)"]), 73 | address: usdc, 74 | functionName: "balanceOf", 75 | args: [account.address], 76 | }); 77 | 78 | if (senderUsdcBalance < 1_000_000n) { 79 | throw new Error( 80 | `insufficient USDC balance for counterfactual wallet address ${account.address}: ${ 81 | Number(senderUsdcBalance) / 1_000_000 82 | } USDC, required at least 1 USDC. Load up balance at https://faucet.circle.com/`, 83 | ); 84 | } 85 | 86 | let txHash = await smartAccountClient.sendTransaction({ 87 | calls: [ 88 | { 89 | to: getAddress(usdc), 90 | abi: parseAbi(["function approve(address,uint)"]), 91 | functionName: "approve", 92 | args: [paymaster, maxUint256], 93 | }, 94 | { 95 | to: getAddress("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"), 96 | data: "0x1234" as Hex, 97 | }, 98 | ], 99 | paymasterContext: { 100 | token: usdc, 101 | }, 102 | }); 103 | 104 | console.log(`transactionHash: ${txHash}`); 105 | -------------------------------------------------------------------------------- /tutorial-3.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { writeFileSync } from "fs" 3 | import { createSmartAccountClient } from "permissionless" 4 | import { toSafeSmartAccount } from "permissionless/accounts" 5 | import { createPimlicoClient } from "permissionless/clients/pimlico" 6 | import { Hex, createPublicClient, encodeFunctionData, http, parseAbiItem } from "viem" 7 | import { entryPoint07Address } from "viem/account-abstraction" 8 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 9 | import { sepolia } from "viem/chains" 10 | 11 | const erc20PaymasterAddress = "0x000000000041F3aFe8892B48D88b6862efe0ec8d" as const 12 | const usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" 13 | 14 | const privateKey = 15 | (process.env.PRIVATE_KEY as Hex) ?? 16 | (() => { 17 | const pk = generatePrivateKey() 18 | writeFileSync(".env", `PRIVATE_KEY=${pk}`) 19 | return pk 20 | })() 21 | 22 | const publicClient = createPublicClient({ 23 | chain: sepolia, 24 | transport: http("https://rpc.ankr.com/eth_sepolia"), 25 | }) 26 | 27 | const apiKey = process.env.PIMLICO_API_KEY // REPLACE THIS 28 | const pimlicoUrl = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${apiKey}` 29 | 30 | const pimlicoClient = createPimlicoClient({ 31 | transport: http(pimlicoUrl), 32 | entryPoint: { 33 | address: entryPoint07Address, 34 | version: "0.7", 35 | }, 36 | }) 37 | 38 | const account = await toSafeSmartAccount( { 39 | client: publicClient, 40 | owner: privateKeyToAccount(privateKey), 41 | version: "1.4.1", 42 | setupTransactions: [ 43 | { 44 | to: usdcAddress, 45 | value: 0n, 46 | data: encodeFunctionData({ 47 | abi: [parseAbiItem("function approve(address spender, uint256 amount)")], 48 | args: [ 49 | erc20PaymasterAddress, 50 | 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn, 51 | ], 52 | }), 53 | }, 54 | ], 55 | }) 56 | 57 | console.log(`Smart account address: https://sepolia.etherscan.io/address/${account.address}`) 58 | 59 | const senderUsdcBalance = await publicClient.readContract({ 60 | abi: [parseAbiItem("function balanceOf(address account) returns (uint256)")], 61 | address: usdcAddress, 62 | functionName: "balanceOf", 63 | args: [account.address], 64 | }) 65 | 66 | if (senderUsdcBalance < 1_000_000n) { 67 | throw new Error( 68 | `insufficient USDC balance for counterfactual wallet address ${account.address}: ${ 69 | Number(senderUsdcBalance) / 1000000 70 | } USDC, required at least 1 USDC. Load up balance at https://faucet.circle.com/`, 71 | ) 72 | } 73 | 74 | console.log(`Smart account USDC balance: ${Number(senderUsdcBalance) / 1000000} USDC`) 75 | 76 | const smartAccountClient = createSmartAccountClient({ 77 | client: publicClient, 78 | account, 79 | chain: sepolia, 80 | bundlerTransport: http(pimlicoUrl), 81 | paymaster: { 82 | async getPaymasterData(parameters) { 83 | const gasEstimates = await pimlicoClient.estimateUserOperationGas({ 84 | ...parameters, 85 | paymaster: erc20PaymasterAddress, 86 | }) 87 | return { 88 | paymaster: erc20PaymasterAddress, 89 | paymasterData: "0x" as Hex, 90 | paymasterPostOpGasLimit: gasEstimates.paymasterPostOpGasLimit ?? 0n, 91 | paymasterVerificationGasLimit: gasEstimates.paymasterVerificationGasLimit ?? 0n, 92 | } 93 | }, 94 | async getPaymasterStubData(parameters) { 95 | const gasEstimates = await pimlicoClient.estimateUserOperationGas({ 96 | ...parameters, 97 | paymaster: erc20PaymasterAddress 98 | }) 99 | return { 100 | paymaster: erc20PaymasterAddress, 101 | paymasterData: "0x" as Hex, 102 | paymasterPostOpGasLimit: gasEstimates.paymasterPostOpGasLimit ?? 0n, 103 | paymasterVerificationGasLimit: gasEstimates.paymasterVerificationGasLimit ?? 0n 104 | } 105 | } 106 | }, 107 | userOperation: { 108 | estimateFeesPerGas: async () => { 109 | return (await pimlicoClient.getUserOperationGasPrice()).fast 110 | }, 111 | } 112 | }) 113 | 114 | const txHash = await smartAccountClient.sendTransaction({ 115 | to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", 116 | value: 0n, 117 | data: "0x1234", 118 | }) 119 | 120 | console.log(`User operation included: https://sepolia.etherscan.io/tx/${txHash}`) 121 | --------------------------------------------------------------------------------