├── 7702 ├── 7702.ts ├── 7702_7821.ts └── abi │ ├── entryPoint0_7_0.ts │ └── kernelV3Implementation.ts ├── .env.example ├── .gitignore ├── README.md ├── batch-transactions ├── batch-txns.ts ├── batch-userops.ts └── v1 │ ├── batch-txns.ts │ └── batch-userops.ts ├── bun.lockb ├── change-sudo-validator └── main.ts ├── create-account └── main.ts ├── create-ecdsa-migration-account └── main.ts ├── delegatecall └── main.ts ├── deploy-contract ├── Greeter.ts └── main.ts ├── emit-event-when-creating-account ├── main.ts └── usingInitConfig.ts ├── fallback-clients └── main.ts ├── guardians ├── recovery.ts └── recovery_call.ts ├── hooks ├── Test_ERC20abi.ts └── spendingLimit.ts ├── intent ├── .env.example ├── README.md ├── enableIntent.ts ├── estimateFee.ts ├── main.ts ├── migrateKernelAndIntentExecutor.ts ├── migrateToIntentExecutor.ts ├── native.ts └── sponsored.ts ├── multi-chain ├── main.ts ├── sendUserOpsWithEnable.ts └── useSessionKeyWithApproval.ts ├── multisig ├── main.ts └── with-session-key.ts ├── package-lock.json ├── package.json ├── pay-gas-with-erc20 ├── estimate-gas.ts ├── main.ts └── v1 │ └── test.ts ├── remote-signer └── main.ts ├── send-transactions ├── send-txn.ts ├── send-userop.ts └── with-2d-nonce.ts ├── session-keys ├── 7702 │ ├── 1-click-trading.ts │ └── transaction-automation.ts ├── 1-click-trading.ts ├── revoke-session-key-with-session-key.ts ├── transaction-automation.ts └── v2-old │ ├── agent-created.ts │ └── owner-created.ts ├── tsconfig.json ├── tutorial ├── completed.ts └── template.ts ├── utils.ts └── validate-signature └── validate-signature.ts /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | ZERODEV_RPC= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _cjs 2 | _esm 3 | _types 4 | *.local 5 | .DS_Store 6 | .eslintcache 7 | .next 8 | bench 9 | cache 10 | coverage 11 | node_modules 12 | tsconfig*.tsbuildinfo 13 | esbuild-why-permissionless* 14 | 15 | # local env files 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .envrc 22 | .vscode -------------------------------------------------------------------------------- /7702/7702.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createPublicClient, 4 | Hex, 5 | http, 6 | zeroAddress, 7 | } from "viem"; 8 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 9 | import { getUserOperationGasPrice } from "@zerodev/sdk/actions" 10 | import { sepolia } from "viem/chains"; 11 | import { 12 | getEntryPoint, 13 | KERNEL_V3_3, 14 | } from "@zerodev/sdk/constants"; 15 | import { createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient } from "@zerodev/sdk"; 16 | 17 | if (!process.env.ZERODEV_RPC) { 18 | throw new Error("ZERODEV_RPC is not set"); 19 | } 20 | 21 | const entryPoint = getEntryPoint("0.7"); 22 | const kernelVersion = KERNEL_V3_3; 23 | 24 | // We use the Sepolia testnet here, but you can use any network that 25 | // supports EIP-7702. 26 | const chain = sepolia; 27 | const ZERODEV_RPC = process.env.ZERODEV_RPC; 28 | 29 | const publicClient = createPublicClient({ 30 | transport: http(), 31 | chain, 32 | }); 33 | 34 | const main = async () => { 35 | const eip7702Account = privateKeyToAccount( 36 | generatePrivateKey() ?? (process.env.PRIVATE_KEY as Hex) 37 | ); 38 | console.log("EOA Address:", eip7702Account.address); 39 | 40 | const account = await createKernelAccount(publicClient, { 41 | eip7702Account, 42 | entryPoint, 43 | kernelVersion, 44 | }) 45 | console.log("account", account.address); 46 | 47 | const paymasterClient = createZeroDevPaymasterClient({ 48 | chain, 49 | transport: http(ZERODEV_RPC), 50 | }); 51 | 52 | const kernelClient = createKernelAccountClient({ 53 | account, 54 | chain, 55 | bundlerTransport: http(ZERODEV_RPC), 56 | paymaster: { 57 | getPaymasterData: async (userOperation) => { 58 | return paymasterClient.sponsorUserOperation({userOperation}) 59 | }, 60 | }, 61 | client: publicClient, 62 | userOperation: { 63 | estimateFeesPerGas: async ({ bundlerClient }) => { 64 | return getUserOperationGasPrice(bundlerClient) 65 | } 66 | } 67 | }) 68 | 69 | const userOpHash = await kernelClient.sendUserOperation({ 70 | callData: await kernelClient.account.encodeCalls([ 71 | { 72 | to: zeroAddress, 73 | value: BigInt(0), 74 | data: "0x", 75 | }, 76 | { 77 | to: zeroAddress, 78 | value: BigInt(0), 79 | data: "0x", 80 | }, 81 | ]), 82 | }); 83 | console.log("UserOp sent:", userOpHash); 84 | console.log("Waiting for UserOp to be completed..."); 85 | 86 | const { receipt } = await kernelClient.waitForUserOperationReceipt({ 87 | hash: userOpHash, 88 | }); 89 | console.log( 90 | "UserOp completed", 91 | `${chain.blockExplorers.default.url}/tx/${receipt.transactionHash}` 92 | ); 93 | 94 | process.exit(0); 95 | }; 96 | 97 | main(); 98 | -------------------------------------------------------------------------------- /7702/7702_7821.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createPublicClient, 4 | createWalletClient, 5 | Hex, 6 | http, 7 | zeroAddress, 8 | Hash 9 | } from "viem"; 10 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 11 | import { sepolia } from "viem/chains"; 12 | import { eip7702Actions, erc7821Actions, verifyAuthorization } from "viem/experimental"; 13 | import { 14 | getEntryPoint, 15 | KERNEL_V3_3_BETA, 16 | KernelVersionToAddressesMap, 17 | } from "@zerodev/sdk/constants"; 18 | import { createKernelAccountClient } from "@zerodev/sdk"; 19 | import { createKernelAccount } from "@zerodev/sdk/accounts"; 20 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 21 | import { createZeroDevPaymasterClient } from "@zerodev/sdk"; 22 | 23 | if (!process.env.ZERODEV_RPC) { 24 | throw new Error("ZERODEV_RPC is not set"); 25 | } 26 | 27 | const ZERODEV_RPC = process.env.ZERODEV_RPC; 28 | const entryPoint = getEntryPoint("0.7"); 29 | const kernelVersion = KERNEL_V3_3_BETA; 30 | 31 | // We use the Sepolia testnet here, but you can use any network that 32 | // supports EIP-7702. 33 | const chain = sepolia; 34 | 35 | const publicClient = createPublicClient({ 36 | transport: http(ZERODEV_RPC), 37 | chain, 38 | }); 39 | 40 | const main = async () => { 41 | if (!process.env.PRIVATE_KEY) { 42 | throw new Error("PRIVATE_KEY is required"); 43 | } 44 | 45 | const signer = privateKeyToAccount( 46 | process.env.PRIVATE_KEY as Hex 47 | ); 48 | console.log("EOA Address:", signer.address); 49 | 50 | const walletClient = createWalletClient({ 51 | // Use any Viem-compatible EOA account 52 | account: signer, 53 | chain, 54 | transport: http(ZERODEV_RPC), 55 | }).extend(erc7821Actions()); 56 | 57 | const authorization = await walletClient.signAuthorization({ 58 | contractAddress: 59 | KernelVersionToAddressesMap[kernelVersion].accountImplementationAddress, 60 | }); 61 | 62 | const hash = await walletClient.execute({ 63 | address : signer.address, 64 | calls : [{ 65 | to: "0x9775137314fE595c943712B0b336327dfa80aE8A", 66 | data : "0xdeadbeef", 67 | value : BigInt(1), 68 | },{ 69 | to: "0x9775137314fE595c943712B0b336327dfa80aE8A", 70 | data : "0xcafecafe", 71 | value : BigInt(2), 72 | }], 73 | authorizationList : [authorization] 74 | }) 75 | 76 | const receipt = await publicClient.waitForTransactionReceipt({ 77 | hash: hash 78 | }); 79 | 80 | console.log("tx completed ,",`${chain.blockExplorers.default.url}/tx/${receipt.transactionHash}`); 81 | 82 | 83 | process.exit(0); 84 | }; 85 | 86 | main(); 87 | -------------------------------------------------------------------------------- /7702/abi/entryPoint0_7_0.ts: -------------------------------------------------------------------------------- 1 | const abi = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: "bool", 6 | name: "success", 7 | type: "bool", 8 | }, 9 | { 10 | internalType: "bytes", 11 | name: "ret", 12 | type: "bytes", 13 | }, 14 | ], 15 | name: "DelegateAndRevert", 16 | type: "error", 17 | }, 18 | { 19 | inputs: [ 20 | { 21 | internalType: "uint256", 22 | name: "opIndex", 23 | type: "uint256", 24 | }, 25 | { 26 | internalType: "string", 27 | name: "reason", 28 | type: "string", 29 | }, 30 | ], 31 | name: "FailedOp", 32 | type: "error", 33 | }, 34 | { 35 | inputs: [ 36 | { 37 | internalType: "uint256", 38 | name: "opIndex", 39 | type: "uint256", 40 | }, 41 | { 42 | internalType: "string", 43 | name: "reason", 44 | type: "string", 45 | }, 46 | { 47 | internalType: "bytes", 48 | name: "inner", 49 | type: "bytes", 50 | }, 51 | ], 52 | name: "FailedOpWithRevert", 53 | type: "error", 54 | }, 55 | { 56 | inputs: [ 57 | { 58 | internalType: "bytes", 59 | name: "returnData", 60 | type: "bytes", 61 | }, 62 | ], 63 | name: "PostOpReverted", 64 | type: "error", 65 | }, 66 | { 67 | inputs: [], 68 | name: "ReentrancyGuardReentrantCall", 69 | type: "error", 70 | }, 71 | { 72 | inputs: [ 73 | { 74 | internalType: "address", 75 | name: "sender", 76 | type: "address", 77 | }, 78 | ], 79 | name: "SenderAddressResult", 80 | type: "error", 81 | }, 82 | { 83 | inputs: [ 84 | { 85 | internalType: "address", 86 | name: "aggregator", 87 | type: "address", 88 | }, 89 | ], 90 | name: "SignatureValidationFailed", 91 | type: "error", 92 | }, 93 | { 94 | anonymous: false, 95 | inputs: [ 96 | { 97 | indexed: true, 98 | internalType: "bytes32", 99 | name: "userOpHash", 100 | type: "bytes32", 101 | }, 102 | { 103 | indexed: true, 104 | internalType: "address", 105 | name: "sender", 106 | type: "address", 107 | }, 108 | { 109 | indexed: false, 110 | internalType: "address", 111 | name: "factory", 112 | type: "address", 113 | }, 114 | { 115 | indexed: false, 116 | internalType: "address", 117 | name: "paymaster", 118 | type: "address", 119 | }, 120 | ], 121 | name: "AccountDeployed", 122 | type: "event", 123 | }, 124 | { 125 | anonymous: false, 126 | inputs: [], 127 | name: "BeforeExecution", 128 | type: "event", 129 | }, 130 | { 131 | anonymous: false, 132 | inputs: [ 133 | { 134 | indexed: true, 135 | internalType: "address", 136 | name: "account", 137 | type: "address", 138 | }, 139 | { 140 | indexed: false, 141 | internalType: "uint256", 142 | name: "totalDeposit", 143 | type: "uint256", 144 | }, 145 | ], 146 | name: "Deposited", 147 | type: "event", 148 | }, 149 | { 150 | anonymous: false, 151 | inputs: [ 152 | { 153 | indexed: true, 154 | internalType: "bytes32", 155 | name: "userOpHash", 156 | type: "bytes32", 157 | }, 158 | { 159 | indexed: true, 160 | internalType: "address", 161 | name: "sender", 162 | type: "address", 163 | }, 164 | { 165 | indexed: false, 166 | internalType: "uint256", 167 | name: "nonce", 168 | type: "uint256", 169 | }, 170 | { 171 | indexed: false, 172 | internalType: "bytes", 173 | name: "revertReason", 174 | type: "bytes", 175 | }, 176 | ], 177 | name: "PostOpRevertReason", 178 | type: "event", 179 | }, 180 | { 181 | anonymous: false, 182 | inputs: [ 183 | { 184 | indexed: true, 185 | internalType: "address", 186 | name: "aggregator", 187 | type: "address", 188 | }, 189 | ], 190 | name: "SignatureAggregatorChanged", 191 | type: "event", 192 | }, 193 | { 194 | anonymous: false, 195 | inputs: [ 196 | { 197 | indexed: true, 198 | internalType: "address", 199 | name: "account", 200 | type: "address", 201 | }, 202 | { 203 | indexed: false, 204 | internalType: "uint256", 205 | name: "totalStaked", 206 | type: "uint256", 207 | }, 208 | { 209 | indexed: false, 210 | internalType: "uint256", 211 | name: "unstakeDelaySec", 212 | type: "uint256", 213 | }, 214 | ], 215 | name: "StakeLocked", 216 | type: "event", 217 | }, 218 | { 219 | anonymous: false, 220 | inputs: [ 221 | { 222 | indexed: true, 223 | internalType: "address", 224 | name: "account", 225 | type: "address", 226 | }, 227 | { 228 | indexed: false, 229 | internalType: "uint256", 230 | name: "withdrawTime", 231 | type: "uint256", 232 | }, 233 | ], 234 | name: "StakeUnlocked", 235 | type: "event", 236 | }, 237 | { 238 | anonymous: false, 239 | inputs: [ 240 | { 241 | indexed: true, 242 | internalType: "address", 243 | name: "account", 244 | type: "address", 245 | }, 246 | { 247 | indexed: false, 248 | internalType: "address", 249 | name: "withdrawAddress", 250 | type: "address", 251 | }, 252 | { 253 | indexed: false, 254 | internalType: "uint256", 255 | name: "amount", 256 | type: "uint256", 257 | }, 258 | ], 259 | name: "StakeWithdrawn", 260 | type: "event", 261 | }, 262 | { 263 | anonymous: false, 264 | inputs: [ 265 | { 266 | indexed: true, 267 | internalType: "bytes32", 268 | name: "userOpHash", 269 | type: "bytes32", 270 | }, 271 | { 272 | indexed: true, 273 | internalType: "address", 274 | name: "sender", 275 | type: "address", 276 | }, 277 | { 278 | indexed: true, 279 | internalType: "address", 280 | name: "paymaster", 281 | type: "address", 282 | }, 283 | { 284 | indexed: false, 285 | internalType: "uint256", 286 | name: "nonce", 287 | type: "uint256", 288 | }, 289 | { 290 | indexed: false, 291 | internalType: "bool", 292 | name: "success", 293 | type: "bool", 294 | }, 295 | { 296 | indexed: false, 297 | internalType: "uint256", 298 | name: "actualGasCost", 299 | type: "uint256", 300 | }, 301 | { 302 | indexed: false, 303 | internalType: "uint256", 304 | name: "actualGasUsed", 305 | type: "uint256", 306 | }, 307 | ], 308 | name: "UserOperationEvent", 309 | type: "event", 310 | }, 311 | { 312 | anonymous: false, 313 | inputs: [ 314 | { 315 | indexed: true, 316 | internalType: "bytes32", 317 | name: "userOpHash", 318 | type: "bytes32", 319 | }, 320 | { 321 | indexed: true, 322 | internalType: "address", 323 | name: "sender", 324 | type: "address", 325 | }, 326 | { 327 | indexed: false, 328 | internalType: "uint256", 329 | name: "nonce", 330 | type: "uint256", 331 | }, 332 | ], 333 | name: "UserOperationPrefundTooLow", 334 | type: "event", 335 | }, 336 | { 337 | anonymous: false, 338 | inputs: [ 339 | { 340 | indexed: true, 341 | internalType: "bytes32", 342 | name: "userOpHash", 343 | type: "bytes32", 344 | }, 345 | { 346 | indexed: true, 347 | internalType: "address", 348 | name: "sender", 349 | type: "address", 350 | }, 351 | { 352 | indexed: false, 353 | internalType: "uint256", 354 | name: "nonce", 355 | type: "uint256", 356 | }, 357 | { 358 | indexed: false, 359 | internalType: "bytes", 360 | name: "revertReason", 361 | type: "bytes", 362 | }, 363 | ], 364 | name: "UserOperationRevertReason", 365 | type: "event", 366 | }, 367 | { 368 | anonymous: false, 369 | inputs: [ 370 | { 371 | indexed: true, 372 | internalType: "address", 373 | name: "account", 374 | type: "address", 375 | }, 376 | { 377 | indexed: false, 378 | internalType: "address", 379 | name: "withdrawAddress", 380 | type: "address", 381 | }, 382 | { 383 | indexed: false, 384 | internalType: "uint256", 385 | name: "amount", 386 | type: "uint256", 387 | }, 388 | ], 389 | name: "Withdrawn", 390 | type: "event", 391 | }, 392 | { 393 | inputs: [ 394 | { 395 | internalType: "uint32", 396 | name: "unstakeDelaySec", 397 | type: "uint32", 398 | }, 399 | ], 400 | name: "addStake", 401 | outputs: [], 402 | stateMutability: "payable", 403 | type: "function", 404 | }, 405 | { 406 | inputs: [ 407 | { 408 | internalType: "address", 409 | name: "account", 410 | type: "address", 411 | }, 412 | ], 413 | name: "balanceOf", 414 | outputs: [ 415 | { 416 | internalType: "uint256", 417 | name: "", 418 | type: "uint256", 419 | }, 420 | ], 421 | stateMutability: "view", 422 | type: "function", 423 | }, 424 | { 425 | inputs: [ 426 | { 427 | internalType: "address", 428 | name: "target", 429 | type: "address", 430 | }, 431 | { 432 | internalType: "bytes", 433 | name: "data", 434 | type: "bytes", 435 | }, 436 | ], 437 | name: "delegateAndRevert", 438 | outputs: [], 439 | stateMutability: "nonpayable", 440 | type: "function", 441 | }, 442 | { 443 | inputs: [ 444 | { 445 | internalType: "address", 446 | name: "account", 447 | type: "address", 448 | }, 449 | ], 450 | name: "depositTo", 451 | outputs: [], 452 | stateMutability: "payable", 453 | type: "function", 454 | }, 455 | { 456 | inputs: [ 457 | { 458 | internalType: "address", 459 | name: "", 460 | type: "address", 461 | }, 462 | ], 463 | name: "deposits", 464 | outputs: [ 465 | { 466 | internalType: "uint256", 467 | name: "deposit", 468 | type: "uint256", 469 | }, 470 | { 471 | internalType: "bool", 472 | name: "staked", 473 | type: "bool", 474 | }, 475 | { 476 | internalType: "uint112", 477 | name: "stake", 478 | type: "uint112", 479 | }, 480 | { 481 | internalType: "uint32", 482 | name: "unstakeDelaySec", 483 | type: "uint32", 484 | }, 485 | { 486 | internalType: "uint48", 487 | name: "withdrawTime", 488 | type: "uint48", 489 | }, 490 | ], 491 | stateMutability: "view", 492 | type: "function", 493 | }, 494 | { 495 | inputs: [ 496 | { 497 | internalType: "address", 498 | name: "account", 499 | type: "address", 500 | }, 501 | ], 502 | name: "getDepositInfo", 503 | outputs: [ 504 | { 505 | components: [ 506 | { 507 | internalType: "uint256", 508 | name: "deposit", 509 | type: "uint256", 510 | }, 511 | { 512 | internalType: "bool", 513 | name: "staked", 514 | type: "bool", 515 | }, 516 | { 517 | internalType: "uint112", 518 | name: "stake", 519 | type: "uint112", 520 | }, 521 | { 522 | internalType: "uint32", 523 | name: "unstakeDelaySec", 524 | type: "uint32", 525 | }, 526 | { 527 | internalType: "uint48", 528 | name: "withdrawTime", 529 | type: "uint48", 530 | }, 531 | ], 532 | internalType: "struct IStakeManager.DepositInfo", 533 | name: "info", 534 | type: "tuple", 535 | }, 536 | ], 537 | stateMutability: "view", 538 | type: "function", 539 | }, 540 | { 541 | inputs: [ 542 | { 543 | internalType: "address", 544 | name: "sender", 545 | type: "address", 546 | }, 547 | { 548 | internalType: "uint192", 549 | name: "key", 550 | type: "uint192", 551 | }, 552 | ], 553 | name: "getNonce", 554 | outputs: [ 555 | { 556 | internalType: "uint256", 557 | name: "nonce", 558 | type: "uint256", 559 | }, 560 | ], 561 | stateMutability: "view", 562 | type: "function", 563 | }, 564 | { 565 | inputs: [ 566 | { 567 | internalType: "bytes", 568 | name: "initCode", 569 | type: "bytes", 570 | }, 571 | ], 572 | name: "getSenderAddress", 573 | outputs: [], 574 | stateMutability: "nonpayable", 575 | type: "function", 576 | }, 577 | { 578 | inputs: [ 579 | { 580 | components: [ 581 | { 582 | internalType: "address", 583 | name: "sender", 584 | type: "address", 585 | }, 586 | { 587 | internalType: "uint256", 588 | name: "nonce", 589 | type: "uint256", 590 | }, 591 | { 592 | internalType: "bytes", 593 | name: "initCode", 594 | type: "bytes", 595 | }, 596 | { 597 | internalType: "bytes", 598 | name: "callData", 599 | type: "bytes", 600 | }, 601 | { 602 | internalType: "bytes32", 603 | name: "accountGasLimits", 604 | type: "bytes32", 605 | }, 606 | { 607 | internalType: "uint256", 608 | name: "preVerificationGas", 609 | type: "uint256", 610 | }, 611 | { 612 | internalType: "bytes32", 613 | name: "gasFees", 614 | type: "bytes32", 615 | }, 616 | { 617 | internalType: "bytes", 618 | name: "paymasterAndData", 619 | type: "bytes", 620 | }, 621 | { 622 | internalType: "bytes", 623 | name: "signature", 624 | type: "bytes", 625 | }, 626 | ], 627 | internalType: "struct PackedUserOperation", 628 | name: "userOp", 629 | type: "tuple", 630 | }, 631 | ], 632 | name: "getUserOpHash", 633 | outputs: [ 634 | { 635 | internalType: "bytes32", 636 | name: "", 637 | type: "bytes32", 638 | }, 639 | ], 640 | stateMutability: "view", 641 | type: "function", 642 | }, 643 | { 644 | inputs: [ 645 | { 646 | components: [ 647 | { 648 | components: [ 649 | { 650 | internalType: "address", 651 | name: "sender", 652 | type: "address", 653 | }, 654 | { 655 | internalType: "uint256", 656 | name: "nonce", 657 | type: "uint256", 658 | }, 659 | { 660 | internalType: "bytes", 661 | name: "initCode", 662 | type: "bytes", 663 | }, 664 | { 665 | internalType: "bytes", 666 | name: "callData", 667 | type: "bytes", 668 | }, 669 | { 670 | internalType: "bytes32", 671 | name: "accountGasLimits", 672 | type: "bytes32", 673 | }, 674 | { 675 | internalType: "uint256", 676 | name: "preVerificationGas", 677 | type: "uint256", 678 | }, 679 | { 680 | internalType: "bytes32", 681 | name: "gasFees", 682 | type: "bytes32", 683 | }, 684 | { 685 | internalType: "bytes", 686 | name: "paymasterAndData", 687 | type: "bytes", 688 | }, 689 | { 690 | internalType: "bytes", 691 | name: "signature", 692 | type: "bytes", 693 | }, 694 | ], 695 | internalType: "struct PackedUserOperation[]", 696 | name: "userOps", 697 | type: "tuple[]", 698 | }, 699 | { 700 | internalType: "contract IAggregator", 701 | name: "aggregator", 702 | type: "address", 703 | }, 704 | { 705 | internalType: "bytes", 706 | name: "signature", 707 | type: "bytes", 708 | }, 709 | ], 710 | internalType: "struct IEntryPoint.UserOpsPerAggregator[]", 711 | name: "opsPerAggregator", 712 | type: "tuple[]", 713 | }, 714 | { 715 | internalType: "address payable", 716 | name: "beneficiary", 717 | type: "address", 718 | }, 719 | ], 720 | name: "handleAggregatedOps", 721 | outputs: [], 722 | stateMutability: "nonpayable", 723 | type: "function", 724 | }, 725 | { 726 | inputs: [ 727 | { 728 | components: [ 729 | { 730 | internalType: "address", 731 | name: "sender", 732 | type: "address", 733 | }, 734 | { 735 | internalType: "uint256", 736 | name: "nonce", 737 | type: "uint256", 738 | }, 739 | { 740 | internalType: "bytes", 741 | name: "initCode", 742 | type: "bytes", 743 | }, 744 | { 745 | internalType: "bytes", 746 | name: "callData", 747 | type: "bytes", 748 | }, 749 | { 750 | internalType: "bytes32", 751 | name: "accountGasLimits", 752 | type: "bytes32", 753 | }, 754 | { 755 | internalType: "uint256", 756 | name: "preVerificationGas", 757 | type: "uint256", 758 | }, 759 | { 760 | internalType: "bytes32", 761 | name: "gasFees", 762 | type: "bytes32", 763 | }, 764 | { 765 | internalType: "bytes", 766 | name: "paymasterAndData", 767 | type: "bytes", 768 | }, 769 | { 770 | internalType: "bytes", 771 | name: "signature", 772 | type: "bytes", 773 | }, 774 | ], 775 | internalType: "struct PackedUserOperation[]", 776 | name: "ops", 777 | type: "tuple[]", 778 | }, 779 | { 780 | internalType: "address payable", 781 | name: "beneficiary", 782 | type: "address", 783 | }, 784 | ], 785 | name: "handleOps", 786 | outputs: [], 787 | stateMutability: "nonpayable", 788 | type: "function", 789 | }, 790 | { 791 | inputs: [ 792 | { 793 | internalType: "uint192", 794 | name: "key", 795 | type: "uint192", 796 | }, 797 | ], 798 | name: "incrementNonce", 799 | outputs: [], 800 | stateMutability: "nonpayable", 801 | type: "function", 802 | }, 803 | { 804 | inputs: [ 805 | { 806 | internalType: "bytes", 807 | name: "callData", 808 | type: "bytes", 809 | }, 810 | { 811 | components: [ 812 | { 813 | components: [ 814 | { 815 | internalType: "address", 816 | name: "sender", 817 | type: "address", 818 | }, 819 | { 820 | internalType: "uint256", 821 | name: "nonce", 822 | type: "uint256", 823 | }, 824 | { 825 | internalType: "uint256", 826 | name: "verificationGasLimit", 827 | type: "uint256", 828 | }, 829 | { 830 | internalType: "uint256", 831 | name: "callGasLimit", 832 | type: "uint256", 833 | }, 834 | { 835 | internalType: "uint256", 836 | name: "paymasterVerificationGasLimit", 837 | type: "uint256", 838 | }, 839 | { 840 | internalType: "uint256", 841 | name: "paymasterPostOpGasLimit", 842 | type: "uint256", 843 | }, 844 | { 845 | internalType: "uint256", 846 | name: "preVerificationGas", 847 | type: "uint256", 848 | }, 849 | { 850 | internalType: "address", 851 | name: "paymaster", 852 | type: "address", 853 | }, 854 | { 855 | internalType: "uint256", 856 | name: "maxFeePerGas", 857 | type: "uint256", 858 | }, 859 | { 860 | internalType: "uint256", 861 | name: "maxPriorityFeePerGas", 862 | type: "uint256", 863 | }, 864 | ], 865 | internalType: "struct EntryPoint.MemoryUserOp", 866 | name: "mUserOp", 867 | type: "tuple", 868 | }, 869 | { 870 | internalType: "bytes32", 871 | name: "userOpHash", 872 | type: "bytes32", 873 | }, 874 | { 875 | internalType: "uint256", 876 | name: "prefund", 877 | type: "uint256", 878 | }, 879 | { 880 | internalType: "uint256", 881 | name: "contextOffset", 882 | type: "uint256", 883 | }, 884 | { 885 | internalType: "uint256", 886 | name: "preOpGas", 887 | type: "uint256", 888 | }, 889 | ], 890 | internalType: "struct EntryPoint.UserOpInfo", 891 | name: "opInfo", 892 | type: "tuple", 893 | }, 894 | { 895 | internalType: "bytes", 896 | name: "context", 897 | type: "bytes", 898 | }, 899 | ], 900 | name: "innerHandleOp", 901 | outputs: [ 902 | { 903 | internalType: "uint256", 904 | name: "actualGasCost", 905 | type: "uint256", 906 | }, 907 | ], 908 | stateMutability: "nonpayable", 909 | type: "function", 910 | }, 911 | { 912 | inputs: [ 913 | { 914 | internalType: "address", 915 | name: "", 916 | type: "address", 917 | }, 918 | { 919 | internalType: "uint192", 920 | name: "", 921 | type: "uint192", 922 | }, 923 | ], 924 | name: "nonceSequenceNumber", 925 | outputs: [ 926 | { 927 | internalType: "uint256", 928 | name: "", 929 | type: "uint256", 930 | }, 931 | ], 932 | stateMutability: "view", 933 | type: "function", 934 | }, 935 | { 936 | inputs: [ 937 | { 938 | internalType: "bytes4", 939 | name: "interfaceId", 940 | type: "bytes4", 941 | }, 942 | ], 943 | name: "supportsInterface", 944 | outputs: [ 945 | { 946 | internalType: "bool", 947 | name: "", 948 | type: "bool", 949 | }, 950 | ], 951 | stateMutability: "view", 952 | type: "function", 953 | }, 954 | { 955 | inputs: [], 956 | name: "unlockStake", 957 | outputs: [], 958 | stateMutability: "nonpayable", 959 | type: "function", 960 | }, 961 | { 962 | inputs: [ 963 | { 964 | internalType: "address payable", 965 | name: "withdrawAddress", 966 | type: "address", 967 | }, 968 | ], 969 | name: "withdrawStake", 970 | outputs: [], 971 | stateMutability: "nonpayable", 972 | type: "function", 973 | }, 974 | { 975 | inputs: [ 976 | { 977 | internalType: "address payable", 978 | name: "withdrawAddress", 979 | type: "address", 980 | }, 981 | { 982 | internalType: "uint256", 983 | name: "withdrawAmount", 984 | type: "uint256", 985 | }, 986 | ], 987 | name: "withdrawTo", 988 | outputs: [], 989 | stateMutability: "nonpayable", 990 | type: "function", 991 | }, 992 | { 993 | stateMutability: "payable", 994 | type: "receive", 995 | }, 996 | ] as const; 997 | 998 | export default abi; 999 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZeroDev Examples 2 | 3 | ## Setup and Installation 4 | 5 | Follow these steps to get this project up and running on your local machine: 6 | 7 | 1. **Clone the repository** 8 | 9 | Use the following command to clone this repository to your local machine: 10 | 11 | ```bash 12 | git clone git@github.com:zerodevapp/zerodev-examples.git 13 | ``` 14 | 15 | 2. **Install dependencies** 16 | 17 | Navigate to the project directory and install the dependencies: 18 | 19 | ```bash 20 | cd zerodev-examples 21 | npm install 22 | ``` 23 | 24 | 3. **Setup environment variables** 25 | 26 | Copy the `.env.example` file to `.env` and fill in the values: 27 | 28 | ```bash 29 | cp .env.example .env 30 | ``` 31 | 32 | For `ZERODEV_RPC`, you can get it from [the ZeroDev dashboard](https://dashboard.zerodev.app/) by creating a project. The examples use Sepolia, so make sure to create a project for Sepolia. 33 | 34 | The `PRIVATE_KEY` can be any valid Ethereum private key. You should use a random test key. 35 | 36 | If you want to run the examples on another network, make sure to update the `chain` object in the code (some examples use the chain object in [./utils.ts](./utils.ts) so you'd need to update it there). 37 | 38 | 4. **Run the script** 39 | 40 | Run any of the example scripts using the following command: 41 | 42 | ```bash 43 | npx ts-node path/to/script.ts 44 | ``` -------------------------------------------------------------------------------- /batch-transactions/batch-txns.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { zeroAddress } from "viem"; 3 | import { getKernelClient } from "../utils"; 4 | import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; 5 | 6 | async function main() { 7 | const kernelClient = await getKernelClient("0.7", KERNEL_V3_1); 8 | 9 | console.log("Account address:", kernelClient.account.address); 10 | 11 | const txnHash = await kernelClient.sendTransaction({ 12 | calls: [ 13 | { 14 | to: zeroAddress, 15 | value: BigInt(0), 16 | data: "0x", 17 | }, 18 | { 19 | to: zeroAddress, 20 | value: BigInt(0), 21 | data: "0x", 22 | }, 23 | ], 24 | }); 25 | 26 | console.log("Txn hash:", txnHash); 27 | } 28 | 29 | main(); 30 | -------------------------------------------------------------------------------- /batch-transactions/batch-userops.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { zeroAddress } from "viem"; 3 | import { getKernelClient } from "../utils"; 4 | import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; 5 | 6 | async function main() { 7 | const kernelClient = await getKernelClient("0.7", KERNEL_V3_1); 8 | 9 | console.log("Account address:", kernelClient.account.address); 10 | 11 | const userOpHash = await kernelClient.sendUserOperation({ 12 | callData: await kernelClient.account.encodeCalls([ 13 | { 14 | to: zeroAddress, 15 | value: BigInt(0), 16 | data: "0x", 17 | }, 18 | { 19 | to: zeroAddress, 20 | value: BigInt(0), 21 | data: "0x", 22 | }, 23 | ]), 24 | }); 25 | 26 | console.log("UserOp hash:", userOpHash); 27 | console.log("Waiting for UserOp to complete..."); 28 | 29 | await kernelClient.waitForUserOperationReceipt({ 30 | hash: userOpHash, 31 | }); 32 | 33 | console.log("UserOp completed"); 34 | } 35 | 36 | main(); 37 | -------------------------------------------------------------------------------- /batch-transactions/v1/batch-txns.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { http, zeroAddress } from "viem"; 3 | import { 4 | getKernelV1Account, 5 | getKernelV1AccountClient, 6 | getZeroDevPaymasterClient, 7 | } from "../../utils"; 8 | 9 | async function main() { 10 | const kernelAccount = await getKernelV1Account(); 11 | const paymaster = getZeroDevPaymasterClient(); 12 | const kernelClient = await getKernelV1AccountClient({ 13 | account: kernelAccount, 14 | paymaster: { 15 | getPaymasterData(userOperation) { 16 | return paymaster.sponsorUserOperation({ userOperation }); 17 | }, 18 | }, 19 | }); 20 | 21 | console.log("Account address:", kernelClient.account.address); 22 | 23 | const txnHash = await kernelClient.sendTransaction({ 24 | calls: [ 25 | { 26 | to: zeroAddress, 27 | value: BigInt(0), 28 | data: "0x", 29 | }, 30 | { 31 | to: zeroAddress, 32 | value: BigInt(0), 33 | data: "0x", 34 | }, 35 | ], 36 | }); 37 | 38 | console.log("Txn hash:", txnHash); 39 | } 40 | 41 | main(); 42 | -------------------------------------------------------------------------------- /batch-transactions/v1/batch-userops.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { http, zeroAddress } from "viem"; 3 | import { 4 | getKernelV1Account, 5 | getKernelV1AccountClient, 6 | getZeroDevPaymasterClient, 7 | } from "../../utils"; 8 | 9 | async function main() { 10 | const kernelAccount = await getKernelV1Account(); 11 | const paymaster = getZeroDevPaymasterClient(); 12 | const kernelClient = await getKernelV1AccountClient({ 13 | account: kernelAccount, 14 | paymaster: { 15 | getPaymasterData(userOperation) { 16 | return paymaster.sponsorUserOperation({ userOperation }); 17 | }, 18 | }, 19 | }); 20 | 21 | console.log("Account address:", kernelClient.account.address); 22 | 23 | const userOpHash = await kernelClient.sendUserOperation({ 24 | callData: await kernelClient.account.encodeCalls([ 25 | { 26 | to: zeroAddress, 27 | value: BigInt(0), 28 | data: "0x", 29 | }, 30 | { 31 | to: zeroAddress, 32 | value: BigInt(0), 33 | data: "0x", 34 | }, 35 | ]), 36 | }); 37 | 38 | console.log("UserOp hash:", userOpHash); 39 | console.log("Waiting for UserOp to complete..."); 40 | 41 | await kernelClient.waitForUserOperationReceipt({ 42 | hash: userOpHash, 43 | }); 44 | 45 | console.log("UserOp completed"); 46 | } 47 | 48 | main(); 49 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zerodevapp/zerodev-examples/462ae7ae8e54a1208d48daceb2c7280f790eade5/bun.lockb -------------------------------------------------------------------------------- /change-sudo-validator/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 8 | import { http, Hex, createPublicClient, zeroAddress } from "viem"; 9 | import { privateKeyToAccount } from "viem/accounts"; 10 | import { sepolia } from "viem/chains"; 11 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 12 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 13 | 14 | if ( 15 | !process.env.ZERODEV_RPC || 16 | !process.env.PRIVATE_KEY 17 | ) { 18 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 19 | } 20 | 21 | const chain = sepolia; 22 | const publicClient = createPublicClient({ 23 | transport: http(process.env.ZERODEV_RPC), 24 | chain, 25 | }); 26 | 27 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 28 | const entryPoint = getEntryPoint("0.7"); 29 | const kernelVersion = KERNEL_V3_1; 30 | 31 | const main = async () => { 32 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 33 | signer, 34 | entryPoint, 35 | kernelVersion, 36 | }); 37 | 38 | const account = await createKernelAccount(publicClient, { 39 | plugins: { 40 | sudo: ecdsaValidator, 41 | }, 42 | entryPoint, 43 | kernelVersion, 44 | }); 45 | console.log("My account:", account.address); 46 | const paymaster = createZeroDevPaymasterClient({ 47 | chain, 48 | transport: http(process.env.ZERODEV_RPC), 49 | }); 50 | 51 | const kernelClient = createKernelAccountClient({ 52 | account, 53 | chain, 54 | bundlerTransport: http(process.env.ZERODEV_RPC), 55 | paymaster: { 56 | getPaymasterData(userOperation) { 57 | return paymaster.sponsorUserOperation({ userOperation }); 58 | }, 59 | }, 60 | }); 61 | 62 | // initialize multiChainECDSAValidatorPlugin 63 | const multiChainECDSAValidatorPlugin = await toMultiChainECDSAValidator( 64 | publicClient, 65 | { 66 | entryPoint, 67 | kernelVersion, 68 | signer, 69 | } 70 | ); 71 | 72 | /** 73 | * @dev In this example, we initialize kernel with ecdsaValidator as sudoValidator and then change it to multiChainECDSAValidatorPlugin. But in most cases, these are separate actions since you would want to change sudoValidator to a different one after deploying the kernel. 74 | */ 75 | const changeSudoValidatorUserOpHash = await kernelClient.changeSudoValidator({ 76 | sudoValidator: multiChainECDSAValidatorPlugin, 77 | }); 78 | 79 | console.log( 80 | "changeSudoValidatorUserOpHash hash:", 81 | changeSudoValidatorUserOpHash 82 | ); 83 | 84 | const _receipt = await kernelClient.waitForUserOperationReceipt({ 85 | hash: changeSudoValidatorUserOpHash, 86 | }); 87 | 88 | console.log("userOp completed"); 89 | 90 | // after this, now you can use multiChainECDSAValidatorPlugin as sudoValidator. For usage of the multi-chain ecdsa validator, refer to the example in `multi-chain` directory. For the multi-chain webauthn validator, refer to this [repo](https://github.com/zerodevapp/multi-chain-passkey-example) 91 | }; 92 | 93 | main(); 94 | -------------------------------------------------------------------------------- /create-account/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk" 7 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" 8 | import { http, Hex, createPublicClient, zeroAddress } from "viem" 9 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 10 | import { sepolia } from "viem/chains" 11 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants" 12 | 13 | if (!process.env.ZERODEV_RPC) { 14 | throw new Error("ZERODEV_RPC is not set") 15 | } 16 | 17 | const chain = sepolia 18 | const publicClient = createPublicClient({ 19 | // Use your own RPC for public client in production 20 | transport: http(process.env.ZERODEV_RPC), 21 | chain, 22 | }) 23 | 24 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex || generatePrivateKey()) 25 | const entryPoint = getEntryPoint("0.7") 26 | const kernelVersion = KERNEL_V3_1 27 | 28 | const main = async () => { 29 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 30 | signer, 31 | entryPoint, 32 | kernelVersion, 33 | }) 34 | 35 | const account = await createKernelAccount(publicClient, { 36 | plugins: { 37 | sudo: ecdsaValidator, 38 | }, 39 | entryPoint, 40 | kernelVersion, 41 | }) 42 | console.log("My account:", account.address) 43 | 44 | const paymasterClient = createZeroDevPaymasterClient({ 45 | chain, 46 | transport: http(process.env.ZERODEV_RPC), 47 | }) 48 | 49 | const kernelClient = createKernelAccountClient({ 50 | account, 51 | chain, 52 | bundlerTransport: http(process.env.ZERODEV_RPC), 53 | client: publicClient, 54 | paymaster: { 55 | getPaymasterData: (userOperation) => { 56 | return paymasterClient.sponsorUserOperation({ 57 | userOperation, 58 | }) 59 | } 60 | } 61 | }) 62 | 63 | const userOpHash = await kernelClient.sendUserOperation({ 64 | callData: await account.encodeCalls([ 65 | { 66 | to: zeroAddress, 67 | value: BigInt(0), 68 | data: "0x", 69 | }, 70 | ]), 71 | }) 72 | 73 | console.log("userOp hash:", userOpHash) 74 | 75 | const _receipt = await kernelClient.waitForUserOperationReceipt({ 76 | hash: userOpHash, 77 | }) 78 | console.log('bundle txn hash: ', _receipt.receipt.transactionHash) 79 | 80 | console.log("userOp completed") 81 | 82 | process.exit(0); 83 | } 84 | 85 | main() 86 | -------------------------------------------------------------------------------- /create-ecdsa-migration-account/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | getUserOperationGasPrice, 7 | } from "@zerodev/sdk" 8 | import { createEcdsaKernelMigrationAccount, signerToEcdsaValidator } from "@zerodev/ecdsa-validator" 9 | import { http, Hex, createPublicClient, zeroAddress, Address, isAddressEqual } from "viem" 10 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 11 | import { sepolia } from "viem/chains" 12 | import { KERNEL_V3_0, KERNEL_V3_1, KernelVersionToAddressesMap } from "@zerodev/sdk/constants" 13 | import { 14 | entryPoint07Address, 15 | EntryPointVersion, 16 | } from "viem/account-abstraction" 17 | import { getKernelImplementationAddress, getKernelVersion } from "@zerodev/sdk/actions" 18 | 19 | if ( 20 | !process.env.ZERODEV_RPC || 21 | !process.env.PRIVATE_KEY 22 | ) { 23 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set") 24 | } 25 | 26 | const chain = sepolia 27 | const publicClient = createPublicClient({ 28 | transport: http(process.env.ZERODEV_RPC), 29 | chain, 30 | }) 31 | 32 | const signer = privateKeyToAccount(generatePrivateKey() as Hex) 33 | const entryPoint = { 34 | address: entryPoint07Address as Address, 35 | version: "0.7" as EntryPointVersion, 36 | } 37 | 38 | const main = async () => { 39 | const originalKernelVersion = KERNEL_V3_0 40 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 41 | signer, 42 | entryPoint, 43 | kernelVersion: originalKernelVersion, 44 | }) 45 | 46 | const account = await createKernelAccount(publicClient, { 47 | plugins: { 48 | sudo: ecdsaValidator, 49 | }, 50 | entryPoint, 51 | kernelVersion: originalKernelVersion, 52 | }) 53 | console.log("My account:", account.address) 54 | 55 | const paymasterClient = createZeroDevPaymasterClient({ 56 | chain, 57 | transport: http(process.env.ZERODEV_RPC), 58 | }) 59 | const kernelClient = createKernelAccountClient({ 60 | account, 61 | chain, 62 | bundlerTransport: http(process.env.ZERODEV_RPC), 63 | client: publicClient, 64 | paymaster: { 65 | getPaymasterData: (userOperation) => { 66 | return paymasterClient.sponsorUserOperation({ 67 | userOperation, 68 | }) 69 | } 70 | }, 71 | }) 72 | 73 | // Sending a dummy transaction just to deploy the account but not not needed for the migration 74 | // If the account is not deployed, the migration will still keep the same address as the one generated by the original kernel version 75 | // but the kernel version will be the migrated one 76 | const txHash = await kernelClient.sendTransaction({ 77 | to: zeroAddress, 78 | value: BigInt(0), 79 | data: "0x", 80 | }) 81 | console.log("txHash:", txHash) 82 | 83 | const kernelVersion = await getKernelVersion(publicClient, { 84 | address: account.address 85 | }) 86 | console.log("Kernel version before migration:", kernelVersion) 87 | 88 | 89 | const migrationVersion = KERNEL_V3_1 90 | 91 | const migrationAccount = await createEcdsaKernelMigrationAccount(publicClient, { 92 | entryPoint, 93 | signer, 94 | migrationVersion: { 95 | from: originalKernelVersion, 96 | to: migrationVersion, 97 | }, 98 | }) 99 | 100 | const migrationKernelClient = createKernelAccountClient({ 101 | account: migrationAccount, 102 | chain, 103 | bundlerTransport: http(process.env.ZERODEV_RPC), 104 | client: publicClient, 105 | paymaster: { 106 | getPaymasterData: (userOperation) => { 107 | return paymasterClient.sponsorUserOperation({ 108 | userOperation, 109 | }) 110 | } 111 | }, 112 | }) 113 | 114 | // The first transaction from the migration account will be the one that will migrate the account 115 | const migrationTxHash = await migrationKernelClient.sendTransaction({ 116 | to: zeroAddress, 117 | value: BigInt(0), 118 | data: "0x", 119 | }) 120 | console.log("migrationTxHash:", migrationTxHash) 121 | 122 | const migrationKernelVersion = await getKernelVersion(publicClient, { 123 | address: migrationAccount.address 124 | }) 125 | console.log("Kernel version after migration:", migrationKernelVersion) 126 | 127 | } 128 | 129 | main() 130 | -------------------------------------------------------------------------------- /delegatecall/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { getKernelClient } from "../utils"; 3 | import { zeroAddress } from "viem"; 4 | import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; 5 | 6 | async function main() { 7 | const kernelClient = await getKernelClient("0.7", KERNEL_V3_1); 8 | 9 | console.log("Account address:", kernelClient.account.address); 10 | 11 | const userOpHash = await kernelClient.sendUserOperation({ 12 | callData: await kernelClient.account.encodeCalls( 13 | [ 14 | { 15 | to: zeroAddress, 16 | value: BigInt(0), 17 | data: "0x", 18 | }, 19 | ], 20 | "delegatecall" 21 | ), 22 | }); 23 | 24 | console.log("UserOp hash:", userOpHash); 25 | const receipt = await kernelClient.waitForUserOperationReceipt({ 26 | hash: userOpHash, 27 | }); 28 | console.log( 29 | `https://sepolia.etherscan.io/tx/${receipt.receipt.transactionHash}` 30 | ); 31 | } 32 | 33 | main(); 34 | -------------------------------------------------------------------------------- /deploy-contract/Greeter.ts: -------------------------------------------------------------------------------- 1 | export const GreeterBytecode = 2 | "0x60806040523480156200001157600080fd5b50620000776040518060600160405280602281526020016200133f602291396040518060400160405280600581526020017f48656c6c6f000000000000000000000000000000000000000000000000000000815250620000c460201b620003cc1760201c565b6040518060400160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525060009081620000bd91906200040d565b50620005be565b620001668282604051602401620000dd92919062000583565b6040516020818303038152906040527f4b5c4277000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506200016a60201b60201c565b5050565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200021557607f821691505b6020821081036200022b576200022a620001cd565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620002957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000256565b620002a1868362000256565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b6000620002ee620002e8620002e284620002b9565b620002c3565b620002b9565b9050919050565b6000819050919050565b6200030a83620002cd565b620003226200031982620002f5565b84845462000263565b825550505050565b600090565b620003396200032a565b62000346818484620002ff565b505050565b5b818110156200036e57620003626000826200032f565b6001810190506200034c565b5050565b601f821115620003bd57620003878162000231565b620003928462000246565b81016020851015620003a2578190505b620003ba620003b18562000246565b8301826200034b565b50505b505050565b600082821c905092915050565b6000620003e260001984600802620003c2565b1980831691505092915050565b6000620003fd8383620003cf565b9150826002028217905092915050565b620004188262000193565b67ffffffffffffffff8111156200043457620004336200019e565b5b620004408254620001fc565b6200044d82828562000372565b600060209050601f83116001811462000485576000841562000470578287015190505b6200047c8582620003ef565b865550620004ec565b601f198416620004958662000231565b60005b82811015620004bf5784890151825560018201915060208501945060208101905062000498565b86831015620004df5784890151620004db601f891682620003cf565b8355505b6001600288020188555050505b505050505050565b600082825260208201905092915050565b60005b838110156200052557808201518184015260208101905062000508565b60008484015250505050565b6000601f19601f8301169050919050565b60006200054f8262000193565b6200055b8185620004f4565b93506200056d81856020860162000505565b620005788162000531565b840191505092915050565b600060408201905081810360008301526200059f818562000542565b90508181036020830152620005b5818462000542565b90509392505050565b610d7180620005ce6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80636250ce311461005c578063a413686214610078578063c3023ff414610094578063cd20c713146100b0578063cfae3217146100e0575b600080fd5b61007660048036038101906100719190610671565b6100fe565b005b610092600480360381019061008d91906107f7565b610183565b005b6100ae60048036038101906100a99190610840565b610243565b005b6100ca60048036038101906100c59190610840565b610315565b6040516100d7919061088f565b60405180910390f35b6100e861033a565b6040516100f59190610929565b60405180910390f35b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505050565b610230604051806060016040528060238152602001610d1960239139600080546101ac9061097a565b80601f01602080910402602001604051908101604052809291908181526020018280546101d89061097a565b80156102255780601f106101fa57610100808354040283529160200191610225565b820191906000526020600020905b81548152906001019060200180831161020857829003601f168201915b505050505083610468565b806000908161023f9190610b57565b5050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506102cd81610507565b60008114610310576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161030790610c75565b60405180910390fd5b505050565b6001602052816000526040600020602052806000526040600020600091509150505481565b6060600080546103499061097a565b80601f01602080910402602001604051908101604052809291908181526020018280546103759061097a565b80156103c25780601f10610397576101008083540402835291602001916103c2565b820191906000526020600020905b8154815290600101906020018083116103a557829003601f168201915b5050505050905090565b61046482826040516024016103e2929190610c95565b6040516020818303038152906040527f4b5c4277000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506105a0565b5050565b61050283838360405160240161048093929190610ccc565b6040516020818303038152906040527f2ced7cef000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506105a0565b505050565b61059d8160405160240161051b919061088f565b6040516020818303038152906040527ff82c50f1000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506105a0565b50565b60008151905060006a636f6e736f6c652e6c6f679050602083016000808483855afa5050505050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610608826105dd565b9050919050565b610618816105fd565b811461062357600080fd5b50565b6000813590506106358161060f565b92915050565b6000819050919050565b61064e8161063b565b811461065957600080fd5b50565b60008135905061066b81610645565b92915050565b60008060408385031215610688576106876105d3565b5b600061069685828601610626565b92505060206106a78582860161065c565b9150509250929050565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610704826106bb565b810181811067ffffffffffffffff82111715610723576107226106cc565b5b80604052505050565b60006107366105c9565b905061074282826106fb565b919050565b600067ffffffffffffffff821115610762576107616106cc565b5b61076b826106bb565b9050602081019050919050565b82818337600083830152505050565b600061079a61079584610747565b61072c565b9050828152602081018484840111156107b6576107b56106b6565b5b6107c1848285610778565b509392505050565b600082601f8301126107de576107dd6106b1565b5b81356107ee848260208601610787565b91505092915050565b60006020828403121561080d5761080c6105d3565b5b600082013567ffffffffffffffff81111561082b5761082a6105d8565b5b610837848285016107c9565b91505092915050565b60008060408385031215610857576108566105d3565b5b600061086585828601610626565b925050602061087685828601610626565b9150509250929050565b6108898161063b565b82525050565b60006020820190506108a46000830184610880565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156108e45780820151818401526020810190506108c9565b60008484015250505050565b60006108fb826108aa565b61090581856108b5565b93506109158185602086016108c6565b61091e816106bb565b840191505092915050565b6000602082019050818103600083015261094381846108f0565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061099257607f821691505b6020821081036109a5576109a461094b565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302610a0d7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff826109d0565b610a1786836109d0565b95508019841693508086168417925050509392505050565b6000819050919050565b6000610a54610a4f610a4a8461063b565b610a2f565b61063b565b9050919050565b6000819050919050565b610a6e83610a39565b610a82610a7a82610a5b565b8484546109dd565b825550505050565b600090565b610a97610a8a565b610aa2818484610a65565b505050565b5b81811015610ac657610abb600082610a8f565b600181019050610aa8565b5050565b601f821115610b0b57610adc816109ab565b610ae5846109c0565b81016020851015610af4578190505b610b08610b00856109c0565b830182610aa7565b50505b505050565b600082821c905092915050565b6000610b2e60001984600802610b10565b1980831691505092915050565b6000610b478383610b1d565b9150826002028217905092915050565b610b60826108aa565b67ffffffffffffffff811115610b7957610b786106cc565b5b610b83825461097a565b610b8e828285610aca565b600060209050601f831160018114610bc15760008415610baf578287015190505b610bb98582610b3b565b865550610c21565b601f198416610bcf866109ab565b60005b82811015610bf757848901518255600182019150602085019450602081019050610bd2565b86831015610c145784890151610c10601f891682610b1d565b8355505b6001600288020188555050505b505050505050565b7f4e6f20476173206c696d69740000000000000000000000000000000000000000600082015250565b6000610c5f600c836108b5565b9150610c6a82610c29565b602082019050919050565b60006020820190508181036000830152610c8e81610c52565b9050919050565b60006040820190508181036000830152610caf81856108f0565b90508181036020830152610cc381846108f0565b90509392505050565b60006060820190508181036000830152610ce681866108f0565b90508181036020830152610cfa81856108f0565b90508181036040830152610d0e81846108f0565b905094935050505056fe4368616e67696e67206772656574696e672066726f6d202725732720746f2027257327a26469706673582212208a9d4f617c5917088dbed912c8d8cb47f8edd6cf21b2c4643b7ead8977183a5964736f6c634300081200334465706c6f79696e67206120477265657465722077697468206772656574696e673a" 3 | 4 | export const GreeterAbi = [ 5 | { 6 | inputs: [], 7 | stateMutability: "nonpayable", 8 | type: "constructor" 9 | }, 10 | { 11 | inputs: [ 12 | { 13 | internalType: "address", 14 | name: "", 15 | type: "address" 16 | }, 17 | { 18 | internalType: "address", 19 | name: "", 20 | type: "address" 21 | } 22 | ], 23 | name: "approvedGasLimit", 24 | outputs: [ 25 | { 26 | internalType: "uint256", 27 | name: "", 28 | type: "uint256" 29 | } 30 | ], 31 | stateMutability: "view", 32 | type: "function" 33 | }, 34 | { 35 | inputs: [ 36 | { 37 | internalType: "address", 38 | name: "safe", 39 | type: "address" 40 | }, 41 | { 42 | internalType: "address", 43 | name: "token", 44 | type: "address" 45 | } 46 | ], 47 | name: "getApprovedGasLimit", 48 | outputs: [], 49 | stateMutability: "view", 50 | type: "function" 51 | }, 52 | { 53 | inputs: [], 54 | name: "greet", 55 | outputs: [ 56 | { 57 | internalType: "string", 58 | name: "", 59 | type: "string" 60 | } 61 | ], 62 | stateMutability: "view", 63 | type: "function" 64 | }, 65 | { 66 | inputs: [ 67 | { 68 | internalType: "address", 69 | name: "token", 70 | type: "address" 71 | }, 72 | { 73 | internalType: "uint256", 74 | name: "gasLimit", 75 | type: "uint256" 76 | } 77 | ], 78 | name: "setApprovedGasLimit", 79 | outputs: [], 80 | stateMutability: "nonpayable", 81 | type: "function" 82 | }, 83 | { 84 | inputs: [ 85 | { 86 | internalType: "string", 87 | name: "_greeting", 88 | type: "string" 89 | } 90 | ], 91 | name: "setGreeting", 92 | outputs: [], 93 | stateMutability: "nonpayable", 94 | type: "function" 95 | } 96 | ] as const 97 | -------------------------------------------------------------------------------- /deploy-contract/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { getKernelClient } from "../utils"; 3 | import { GreeterAbi, GreeterBytecode } from "./Greeter"; 4 | import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; 5 | 6 | async function main() { 7 | const kernelClient = await getKernelClient("0.7", KERNEL_V3_1); 8 | 9 | console.log("Account address:", kernelClient.account.address); 10 | 11 | const txnHash = await kernelClient.sendTransaction({ 12 | callData: await kernelClient.account.encodeDeployCallData({ 13 | abi: GreeterAbi, 14 | bytecode: GreeterBytecode, 15 | }), 16 | }); 17 | 18 | console.log("Txn hash:", txnHash); 19 | } 20 | 21 | main(); 22 | -------------------------------------------------------------------------------- /emit-event-when-creating-account/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | getUserOperationGasPrice, 7 | } from "@zerodev/sdk" 8 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" 9 | import { 10 | http, 11 | Hex, 12 | createPublicClient, 13 | zeroAddress, 14 | Address, 15 | concatHex, 16 | decodeEventLog, 17 | parseAbi, 18 | } from "viem" 19 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 20 | import { sepolia } from "viem/chains" 21 | import { 22 | KERNEL_V3_1, 23 | PLUGIN_TYPE, 24 | VALIDATOR_TYPE, 25 | } from "@zerodev/sdk/constants" 26 | import { 27 | entryPoint07Address, 28 | EntryPointVersion, 29 | } from "viem/account-abstraction" 30 | 31 | if ( 32 | !process.env.ZERODEV_RPC || 33 | !process.env.PRIVATE_KEY 34 | ) { 35 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set") 36 | } 37 | 38 | const chain = sepolia 39 | const publicClient = createPublicClient({ 40 | transport: http(process.env.ZERODEV_RPC), 41 | chain, 42 | }) 43 | 44 | const signer = privateKeyToAccount( 45 | generatePrivateKey() ?? (process.env.PRIVATE_KEY as Hex) 46 | ) 47 | const entryPoint = { 48 | address: entryPoint07Address as Address, 49 | version: "0.7" as EntryPointVersion, 50 | } 51 | const kernelVersion = KERNEL_V3_1 52 | const identifierEmittedAbi = parseAbi([ 53 | "event IdentifierEmitted(bytes id, address indexed kernel)", 54 | ]) 55 | 56 | const main = async () => { 57 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 58 | signer, 59 | entryPoint, 60 | kernelVersion, 61 | }) 62 | 63 | const account = await createKernelAccount(publicClient, { 64 | plugins: { 65 | sudo: ecdsaValidator, 66 | }, 67 | entryPoint, 68 | kernelVersion, 69 | pluginMigrations: [ 70 | { 71 | address: "0x43C757131417c5a245a99c4D5B7722ec20Cb0b31", 72 | type: PLUGIN_TYPE.VALIDATOR, 73 | // Identifier 74 | data: "0xb33f", 75 | }, 76 | ], 77 | }) 78 | console.log("My account:", account.address) 79 | 80 | const paymasterClient = createZeroDevPaymasterClient({ 81 | chain, 82 | transport: http(process.env.ZERODEV_RPC), 83 | }) 84 | const kernelClient = createKernelAccountClient({ 85 | account, 86 | chain, 87 | bundlerTransport: http(process.env.ZERODEV_RPC), 88 | client: publicClient, 89 | paymaster: { 90 | getPaymasterData: (userOperation) => { 91 | return paymasterClient.sponsorUserOperation({ 92 | userOperation, 93 | }) 94 | } 95 | }, 96 | }) 97 | 98 | const userOpHash = await kernelClient.sendUserOperation({ 99 | callData: await account.encodeCalls([ 100 | { 101 | to: zeroAddress, 102 | value: BigInt(0), 103 | data: "0x", 104 | }, 105 | ]), 106 | }) 107 | 108 | console.log("userOp hash:", userOpHash) 109 | 110 | const _receipt = await kernelClient.waitForUserOperationReceipt({ 111 | hash: userOpHash, 112 | }) 113 | console.log({ txHash: _receipt.receipt.transactionHash }) 114 | 115 | for (const log of _receipt.logs) { 116 | try { 117 | const event = decodeEventLog({ 118 | abi: identifierEmittedAbi, 119 | ...log, 120 | }) 121 | if (event.eventName === "IdentifierEmitted") { 122 | console.log({ id: event.args.id, kernel: event.args.kernel }) 123 | } 124 | } catch { } 125 | } 126 | console.log("userOp completed") 127 | } 128 | 129 | main() 130 | -------------------------------------------------------------------------------- /emit-event-when-creating-account/usingInitConfig.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | getUserOperationGasPrice, 7 | KernelV3_1AccountAbi, 8 | } from "@zerodev/sdk" 9 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator" 10 | import { 11 | http, 12 | Hex, 13 | createPublicClient, 14 | zeroAddress, 15 | Address, 16 | concatHex, 17 | decodeEventLog, 18 | parseAbi, 19 | encodeFunctionData, 20 | } from "viem" 21 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 22 | import { sepolia } from "viem/chains" 23 | import { 24 | KERNEL_V3_1, 25 | PLUGIN_TYPE, 26 | VALIDATOR_TYPE, 27 | } from "@zerodev/sdk/constants" 28 | import { 29 | entryPoint07Address, 30 | EntryPointVersion, 31 | } from "viem/account-abstraction" 32 | 33 | if ( 34 | !process.env.ZERODEV_RPC || 35 | !process.env.PRIVATE_KEY 36 | ) { 37 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set") 38 | } 39 | 40 | const chain = sepolia 41 | const publicClient = createPublicClient({ 42 | transport: http(process.env.ZERODEV_RPC), 43 | chain, 44 | }) 45 | 46 | const signer = privateKeyToAccount( 47 | generatePrivateKey() ?? (process.env.PRIVATE_KEY as Hex) 48 | ) 49 | const entryPoint = { 50 | address: entryPoint07Address as Address, 51 | version: "0.7" as EntryPointVersion, 52 | } 53 | const kernelVersion = KERNEL_V3_1 54 | const identifierEmittedAbi = parseAbi([ 55 | "event IdentifierEmitted(bytes id, address indexed kernel)", 56 | ]) 57 | 58 | const main = async () => { 59 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 60 | signer, 61 | entryPoint, 62 | kernelVersion, 63 | }) 64 | 65 | const account = await createKernelAccount(publicClient, { 66 | plugins: { 67 | sudo: ecdsaValidator, 68 | }, 69 | entryPoint, 70 | kernelVersion, 71 | initConfig: [ 72 | encodeFunctionData({ 73 | abi: KernelV3_1AccountAbi, 74 | functionName: "installValidations", 75 | args: [ 76 | [ 77 | concatHex([ 78 | VALIDATOR_TYPE.SECONDARY, 79 | "0x43C757131417c5a245a99c4D5B7722ec20Cb0b31", 80 | ]), 81 | ], 82 | [{ nonce: 1, hook: zeroAddress }], 83 | // Identifier 84 | ["0xb33f"], 85 | ["0x"], 86 | ], 87 | }), 88 | ], 89 | }) 90 | console.log("My account:", account.address) 91 | 92 | const paymasterClient = createZeroDevPaymasterClient({ 93 | chain, 94 | transport: http(process.env.ZERODEV_RPC), 95 | }) 96 | const kernelClient = createKernelAccountClient({ 97 | account, 98 | chain, 99 | bundlerTransport: http(process.env.ZERODEV_RPC), 100 | client: publicClient, 101 | paymaster: { 102 | getPaymasterData: (userOperation) => { 103 | return paymasterClient.sponsorUserOperation({ 104 | userOperation, 105 | }) 106 | } 107 | }, 108 | }) 109 | 110 | const userOpHash = await kernelClient.sendUserOperation({ 111 | callData: await account.encodeCalls([ 112 | { 113 | to: zeroAddress, 114 | value: BigInt(0), 115 | data: "0x", 116 | }, 117 | ]), 118 | }) 119 | 120 | console.log("userOp hash:", userOpHash) 121 | 122 | const _receipt = await kernelClient.waitForUserOperationReceipt({ 123 | hash: userOpHash, 124 | }) 125 | console.log({ txHash: _receipt.receipt.transactionHash }) 126 | 127 | for (const log of _receipt.logs) { 128 | try { 129 | const event = decodeEventLog({ 130 | abi: identifierEmittedAbi, 131 | ...log, 132 | }) 133 | if (event.eventName === "IdentifierEmitted") { 134 | console.log({ id: event.args.id, kernel: event.args.kernel }) 135 | } 136 | } catch { } 137 | } 138 | console.log("userOp completed") 139 | } 140 | 141 | main() 142 | -------------------------------------------------------------------------------- /fallback-clients/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { signerToEcdsaValidator } from '@zerodev/ecdsa-validator' 3 | import { createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient, createFallbackKernelAccountClient } from '@zerodev/sdk' 4 | import { Hex, createPublicClient, http, zeroAddress } from 'viem' 5 | import { privateKeyToAccount } from 'viem/accounts' 6 | import { sepolia } from 'viem/chains' 7 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 8 | 9 | const privateKey = process.env.PRIVATE_KEY 10 | if (!process.env.ZERODEV_RPC || !privateKey) { 11 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set") 12 | } 13 | 14 | const signer = privateKeyToAccount(privateKey as Hex) 15 | const chain = sepolia 16 | const publicClient = createPublicClient({ 17 | transport: http(process.env.ZERODEV_RPC), 18 | chain 19 | }) 20 | const entryPoint = getEntryPoint("0.7") 21 | 22 | async function main() { 23 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 24 | signer, 25 | entryPoint, 26 | kernelVersion: KERNEL_V3_1 27 | }) 28 | 29 | const account = await createKernelAccount(publicClient, { 30 | plugins: { 31 | sudo: ecdsaValidator, 32 | }, 33 | entryPoint, 34 | kernelVersion: KERNEL_V3_1 35 | }) 36 | 37 | const pimlicoPaymasterClient = createZeroDevPaymasterClient({ 38 | chain, 39 | transport: http(process.env.ZERODEV_RPC + '?provider=PIMLICO'), 40 | }) 41 | 42 | const pimlicoKernelClient = createKernelAccountClient({ 43 | account, 44 | chain, 45 | bundlerTransport: http(process.env.ZERODEV_RPC + "_make_it_fail" + '?provider=PIMLICO'), 46 | paymaster: { 47 | getPaymasterData(userOperation) { 48 | return pimlicoPaymasterClient.sponsorUserOperation({ userOperation }); 49 | }, 50 | }, 51 | }) 52 | 53 | const alchemyPaymasterClient = createZeroDevPaymasterClient({ 54 | chain, 55 | transport: http(process.env.ZERODEV_RPC + '?provider=ALCHEMY'), 56 | }) 57 | 58 | const alchemyKernelClient = createKernelAccountClient({ 59 | account, 60 | chain, 61 | bundlerTransport: http(process.env.ZERODEV_RPC + '?provider=ALCHEMY'), 62 | paymaster: { 63 | getPaymasterData(userOperation) { 64 | return alchemyPaymasterClient.sponsorUserOperation({ userOperation }); 65 | }, 66 | }, 67 | }) 68 | 69 | const fallbackKernelClient = createFallbackKernelAccountClient([ 70 | pimlicoKernelClient, 71 | alchemyKernelClient 72 | ]) 73 | 74 | console.log("Account address:", fallbackKernelClient.account.address) 75 | 76 | const txHash = await fallbackKernelClient.sendTransaction({ 77 | to: zeroAddress, 78 | value: BigInt(0), 79 | data: "0x" 80 | }) 81 | 82 | console.log("Txn hash:", txHash) 83 | } 84 | 85 | main() -------------------------------------------------------------------------------- /guardians/recovery.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { 8 | http, 9 | createPublicClient, 10 | Hex, 11 | toFunctionSelector, 12 | parseAbi, 13 | encodeFunctionData, 14 | zeroAddress, 15 | } from "viem"; 16 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 17 | import { sepolia } from "viem/chains"; 18 | import { 19 | createWeightedECDSAValidator, 20 | getRecoveryAction, 21 | } from "@zerodev/weighted-ecdsa-validator"; 22 | import { 23 | getValidatorAddress, 24 | signerToEcdsaValidator, 25 | } from "@zerodev/ecdsa-validator"; 26 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 27 | 28 | if ( 29 | !process.env.ZERODEV_RPC || 30 | !process.env.PRIVATE_KEY 31 | ) { 32 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 33 | } 34 | 35 | const publicClient = createPublicClient({ 36 | transport: http(process.env.ZERODEV_RPC), 37 | chain: sepolia, 38 | }); 39 | 40 | const oldSigner = privateKeyToAccount(generatePrivateKey()); 41 | const newSigner = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 42 | const guardian = privateKeyToAccount(generatePrivateKey()); 43 | 44 | const entryPoint = getEntryPoint("0.7"); 45 | const recoveryExecutorFunction = 46 | "function doRecovery(address _validator, bytes calldata _data)"; 47 | const main = async () => { 48 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 49 | signer: oldSigner, 50 | entryPoint, 51 | kernelVersion: KERNEL_V3_1, 52 | }); 53 | 54 | const guardianValidator = await createWeightedECDSAValidator(publicClient, { 55 | entryPoint, 56 | config: { 57 | threshold: 100, 58 | signers: [{ address: guardian.address, weight: 100 }], 59 | }, 60 | signers: [guardian], 61 | kernelVersion: KERNEL_V3_1, 62 | }); 63 | 64 | const account = await createKernelAccount(publicClient, { 65 | entryPoint, 66 | plugins: { 67 | sudo: ecdsaValidator, 68 | regular: guardianValidator, 69 | action: getRecoveryAction(entryPoint.version), 70 | }, 71 | kernelVersion: KERNEL_V3_1, 72 | }); 73 | 74 | const paymasterClient = createZeroDevPaymasterClient({ 75 | chain: sepolia, 76 | transport: http(process.env.ZERODEV_RPC), 77 | }); 78 | 79 | const kernelClient = createKernelAccountClient({ 80 | account, 81 | chain: sepolia, 82 | bundlerTransport: http(process.env.ZERODEV_RPC), 83 | paymaster: { 84 | getPaymasterData(userOperation) { 85 | return paymasterClient.sponsorUserOperation({ userOperation }); 86 | }, 87 | }, 88 | }); 89 | 90 | console.log("performing recovery..."); 91 | const userOpHash = await kernelClient.sendUserOperation({ 92 | callData: encodeFunctionData({ 93 | abi: parseAbi([recoveryExecutorFunction]), 94 | functionName: "doRecovery", 95 | args: [getValidatorAddress(entryPoint, KERNEL_V3_1), newSigner.address], 96 | }), 97 | }); 98 | 99 | console.log("recovery userOp hash:", userOpHash); 100 | 101 | await kernelClient.waitForUserOperationReceipt({ 102 | hash: userOpHash, 103 | }); 104 | 105 | console.log("recovery completed!"); 106 | 107 | const newEcdsaValidator = await signerToEcdsaValidator(publicClient, { 108 | signer: newSigner, 109 | entryPoint, 110 | kernelVersion: KERNEL_V3_1, 111 | }); 112 | 113 | const newAccount = await createKernelAccount(publicClient, { 114 | address: account.address, 115 | entryPoint, 116 | plugins: { 117 | sudo: newEcdsaValidator, 118 | }, 119 | kernelVersion: KERNEL_V3_1, 120 | }); 121 | 122 | const newKernelClient = createKernelAccountClient({ 123 | account: newAccount, 124 | chain: sepolia, 125 | bundlerTransport: http(process.env.ZERODEV_RPC), 126 | paymaster: paymasterClient, 127 | }); 128 | 129 | console.log("sending userOp with new signer"); 130 | const userOpHash2 = await newKernelClient.sendUserOperation({ 131 | callData: await newAccount.encodeCalls([ 132 | { 133 | to: zeroAddress, 134 | value: BigInt(0), 135 | data: "0x", 136 | }, 137 | ]), 138 | }); 139 | console.log("userOp hash:", userOpHash2); 140 | 141 | await newKernelClient.waitForUserOperationReceipt({ 142 | hash: userOpHash2, 143 | }); 144 | console.log("userOp completed!"); 145 | }; 146 | 147 | main(); 148 | -------------------------------------------------------------------------------- /guardians/recovery_call.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | AccountNotFoundError, 7 | KernelValidator, 8 | } from "@zerodev/sdk"; 9 | import { 10 | http, 11 | createPublicClient, 12 | Hex, 13 | toFunctionSelector, 14 | parseAbi, 15 | encodeFunctionData, 16 | zeroAddress, 17 | concat, 18 | encodeAbiParameters, 19 | parseAbiParameters, 20 | Address, 21 | } from "viem"; 22 | import { generatePrivateKey, parseAccount, privateKeyToAccount } from "viem/accounts"; 23 | import { lineaSepolia} from "viem/chains"; 24 | import { 25 | signerToEcdsaValidator, 26 | } from "@zerodev/ecdsa-validator"; 27 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 28 | import type { Chain, Client, Hash, Prettify, Transport } from "viem" 29 | import { getAction } from "viem/utils"; 30 | import { 31 | type SmartAccount, 32 | sendUserOperation 33 | } from "viem/account-abstraction" 34 | 35 | if ( 36 | !process.env.ZERODEV_RPC || 37 | !process.env.PRIVATE_KEY 38 | ) { 39 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 40 | } 41 | 42 | const publicClient = createPublicClient({ 43 | transport: http(process.env.ZERODEV_RPC), 44 | chain: lineaSepolia, 45 | }); 46 | 47 | const CALLER_HOOK = "0x990a9FC8189D96d59E3cE98bd87F42135a24a30E"; 48 | const RECOVERY_ACTION_ADDRESS = "0xe884C2868CC82c16177eC73a93f7D9E6F3A5DC6E" 49 | const ACTION_MODULE_TYPE = 3; 50 | const oldSigner = privateKeyToAccount(generatePrivateKey()); 51 | const newSigner = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 52 | 53 | const entryPoint = getEntryPoint("0.7"); 54 | const recoveryExecutorFunction = 55 | "function doRecovery(address _validator, bytes calldata _data)"; 56 | 57 | const installModuleFunction = "function installModule(uint256 _type, address _module, bytes calldata _initData)" 58 | 59 | type RegisterGuardianParameters = { 60 | guardian: Address; 61 | account?: SmartAccount; 62 | } 63 | 64 | export async function registerGuardian< 65 | account extends SmartAccount | undefined, 66 | chain extends Chain | undefined, 67 | >( 68 | client: Client, 69 | args: Prettify 70 | ): Promise { 71 | const { guardian, account : account_ = client.account } = args 72 | if (!account_) 73 | throw new AccountNotFoundError() 74 | 75 | const account = parseAccount(account_) as SmartAccount 76 | 77 | return await getAction( 78 | client, 79 | sendUserOperation, 80 | "sendUserOperation" 81 | )({ 82 | account, 83 | callData :encodeFunctionData({ 84 | abi: parseAbi([installModuleFunction]), 85 | functionName: "installModule", 86 | args: [ 87 | BigInt(ACTION_MODULE_TYPE), 88 | RECOVERY_ACTION_ADDRESS, 89 | concat( 90 | [ 91 | toFunctionSelector(parseAbi([recoveryExecutorFunction])[0]) as `0x${string}`, 92 | CALLER_HOOK as `0x${string}`, 93 | encodeAbiParameters( 94 | parseAbiParameters('bytes selectorData, bytes hookData'), 95 | [ 96 | "0xff" as `0x${string}`, // selectorData, use delegatecall 97 | concat( 98 | [ 99 | "0xff", // flag to install hook 100 | encodeAbiParameters( 101 | parseAbiParameters('address[] guardians'), 102 | [ 103 | [ 104 | guardian, 105 | ], 106 | ] 107 | ), 108 | ], 109 | ), 110 | ] 111 | ), 112 | ] 113 | ), 114 | ], 115 | }), 116 | }) 117 | 118 | } 119 | 120 | type RecoveryParameters = { 121 | targetAccount: Address; 122 | guardian: SmartAccount; 123 | newSigner: KernelValidator; 124 | } 125 | 126 | export async function recoverAccount< 127 | account extends SmartAccount | undefined, 128 | chain extends Chain | undefined, 129 | >( 130 | client: Client, 131 | args: Prettify 132 | ) { 133 | const { targetAccount, guardian, newSigner } = args 134 | 135 | return await getAction( 136 | client, 137 | sendUserOperation, 138 | "sendUserOperation" 139 | )({ 140 | account: guardian, 141 | calls: [ 142 | { 143 | to: targetAccount, 144 | data: encodeFunctionData({ 145 | abi: parseAbi([recoveryExecutorFunction]), 146 | functionName: "doRecovery", 147 | args: [newSigner.address, await newSigner.getEnableData()], 148 | }), 149 | }, 150 | ], 151 | callGasLimit: BigInt(1000000), 152 | }) 153 | } 154 | 155 | 156 | const main = async () => { 157 | const paymasterClient = createZeroDevPaymasterClient({ 158 | chain: lineaSepolia, 159 | transport: http(process.env.ZERODEV_RPC), 160 | }); 161 | 162 | // ---- set up target account ---- 163 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 164 | signer: oldSigner, 165 | entryPoint, 166 | kernelVersion: KERNEL_V3_1, 167 | }); 168 | 169 | const targetAccount = await createKernelAccount(publicClient, { 170 | entryPoint, 171 | plugins: { 172 | sudo: ecdsaValidator, 173 | }, 174 | kernelVersion: KERNEL_V3_1, 175 | }); 176 | console.log("target account created:", targetAccount.address); 177 | 178 | // ---- set up guardian account ---- 179 | const guardian = privateKeyToAccount(generatePrivateKey()); 180 | const guardianValidator = await signerToEcdsaValidator(publicClient, { 181 | signer: guardian, 182 | entryPoint, 183 | kernelVersion: KERNEL_V3_1, 184 | }); 185 | 186 | const guardianAccount = await createKernelAccount(publicClient, { 187 | entryPoint, 188 | plugins: { 189 | sudo: guardianValidator, 190 | }, 191 | kernelVersion: KERNEL_V3_1, 192 | }); 193 | console.log("guardian account created:", guardianAccount.address); 194 | 195 | // ---- set up new signer ---- 196 | const newEcdsaValidator = await signerToEcdsaValidator(publicClient, { 197 | signer: newSigner, 198 | entryPoint, 199 | kernelVersion: KERNEL_V3_1, 200 | }); 201 | 202 | // ---- install recovery action with caller Hook ---- 203 | const targetClient = createKernelAccountClient({ 204 | account: targetAccount, 205 | chain: lineaSepolia, 206 | bundlerTransport: http(process.env.ZERODEV_RPC), 207 | paymaster: { 208 | getPaymasterData(userOperation) { 209 | return paymasterClient.sponsorUserOperation({ userOperation }); 210 | }, 211 | }, 212 | }); 213 | 214 | console.log("installing recovery action with caller hook..."); 215 | 216 | await registerGuardian(targetClient, { 217 | guardian: guardianAccount.address, 218 | account: targetAccount, 219 | }); 220 | 221 | // ---- perform recovery ---- 222 | console.log("performing recovery..."); 223 | const guardianClient = createKernelAccountClient({ 224 | account: guardianAccount, 225 | chain: lineaSepolia, 226 | bundlerTransport: http(process.env.ZERODEV_RPC), 227 | paymaster: paymasterClient, 228 | }); 229 | 230 | const userOpHash = await recoverAccount(guardianClient, { 231 | targetAccount: targetAccount.address, 232 | guardian: guardianAccount, 233 | newSigner: newEcdsaValidator, 234 | }); 235 | 236 | console.log("recovery userOp hash:", userOpHash); 237 | await guardianClient.waitForUserOperationReceipt({ 238 | hash: userOpHash, 239 | }); 240 | 241 | console.log("recovery completed!"); 242 | 243 | const newAccount = await createKernelAccount(publicClient, { 244 | address: targetAccount.address, 245 | entryPoint, 246 | plugins: { 247 | sudo: newEcdsaValidator, 248 | }, 249 | kernelVersion: KERNEL_V3_1, 250 | }); 251 | 252 | const newKernelClient = createKernelAccountClient({ 253 | account: newAccount, 254 | chain: lineaSepolia, 255 | bundlerTransport: http(process.env.ZERODEV_RPC), 256 | paymaster: paymasterClient, 257 | }); 258 | 259 | console.log("sending userOp with new signer"); 260 | const userOpHash2 = await newKernelClient.sendUserOperation({ 261 | callData: await newAccount.encodeCalls([ 262 | { 263 | to: zeroAddress, 264 | value: BigInt(0), 265 | data: "0x", 266 | }, 267 | ]), 268 | }); 269 | console.log("userOp hash:", userOpHash2); 270 | 271 | await newKernelClient.waitForUserOperationReceipt({ 272 | hash: userOpHash2, 273 | }); 274 | console.log("userOp completed!"); 275 | }; 276 | 277 | main(); 278 | -------------------------------------------------------------------------------- /hooks/Test_ERC20abi.ts: -------------------------------------------------------------------------------- 1 | export const TEST_ERC20Abi = [ 2 | { 3 | inputs: [ 4 | { internalType: "uint8", name: "_decimals", type: "uint8" }, 5 | { internalType: "string", name: "_name", type: "string" }, 6 | { internalType: "string", name: "_symbol", type: "string" } 7 | ], 8 | stateMutability: "nonpayable", 9 | type: "constructor" 10 | }, 11 | { 12 | anonymous: false, 13 | inputs: [ 14 | { 15 | indexed: true, 16 | internalType: "address", 17 | name: "owner", 18 | type: "address" 19 | }, 20 | { 21 | indexed: true, 22 | internalType: "address", 23 | name: "spender", 24 | type: "address" 25 | }, 26 | { 27 | indexed: false, 28 | internalType: "uint256", 29 | name: "value", 30 | type: "uint256" 31 | } 32 | ], 33 | name: "Approval", 34 | type: "event" 35 | }, 36 | { 37 | anonymous: false, 38 | inputs: [ 39 | { 40 | indexed: true, 41 | internalType: "address", 42 | name: "from", 43 | type: "address" 44 | }, 45 | { 46 | indexed: true, 47 | internalType: "address", 48 | name: "to", 49 | type: "address" 50 | }, 51 | { 52 | indexed: false, 53 | internalType: "uint256", 54 | name: "value", 55 | type: "uint256" 56 | } 57 | ], 58 | name: "Transfer", 59 | type: "event" 60 | }, 61 | { 62 | inputs: [ 63 | { internalType: "address", name: "owner", type: "address" }, 64 | { internalType: "address", name: "spender", type: "address" } 65 | ], 66 | name: "allowance", 67 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 68 | stateMutability: "view", 69 | type: "function" 70 | }, 71 | { 72 | inputs: [ 73 | { internalType: "address", name: "spender", type: "address" }, 74 | { internalType: "uint256", name: "amount", type: "uint256" } 75 | ], 76 | name: "approve", 77 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 78 | stateMutability: "nonpayable", 79 | type: "function" 80 | }, 81 | { 82 | inputs: [{ internalType: "address", name: "account", type: "address" }], 83 | name: "balanceOf", 84 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 85 | stateMutability: "view", 86 | type: "function" 87 | }, 88 | { 89 | inputs: [], 90 | name: "decimals", 91 | outputs: [{ internalType: "uint8", name: "", type: "uint8" }], 92 | stateMutability: "view", 93 | type: "function" 94 | }, 95 | { 96 | inputs: [ 97 | { internalType: "address", name: "spender", type: "address" }, 98 | { 99 | internalType: "uint256", 100 | name: "subtractedValue", 101 | type: "uint256" 102 | } 103 | ], 104 | name: "decreaseAllowance", 105 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 106 | stateMutability: "nonpayable", 107 | type: "function" 108 | }, 109 | { 110 | inputs: [ 111 | { internalType: "address", name: "spender", type: "address" }, 112 | { internalType: "uint256", name: "addedValue", type: "uint256" } 113 | ], 114 | name: "increaseAllowance", 115 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 116 | stateMutability: "nonpayable", 117 | type: "function" 118 | }, 119 | { 120 | inputs: [ 121 | { internalType: "address", name: "to", type: "address" }, 122 | { internalType: "uint256", name: "amount", type: "uint256" } 123 | ], 124 | name: "mint", 125 | outputs: [], 126 | stateMutability: "nonpayable", 127 | type: "function" 128 | }, 129 | { 130 | inputs: [], 131 | name: "name", 132 | outputs: [{ internalType: "string", name: "", type: "string" }], 133 | stateMutability: "view", 134 | type: "function" 135 | }, 136 | { 137 | inputs: [], 138 | name: "symbol", 139 | outputs: [{ internalType: "string", name: "", type: "string" }], 140 | stateMutability: "view", 141 | type: "function" 142 | }, 143 | { 144 | inputs: [], 145 | name: "totalSupply", 146 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 147 | stateMutability: "view", 148 | type: "function" 149 | }, 150 | { 151 | inputs: [ 152 | { internalType: "address", name: "to", type: "address" }, 153 | { internalType: "uint256", name: "amount", type: "uint256" } 154 | ], 155 | name: "transfer", 156 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 157 | stateMutability: "nonpayable", 158 | type: "function" 159 | }, 160 | { 161 | inputs: [ 162 | { internalType: "address", name: "from", type: "address" }, 163 | { internalType: "address", name: "to", type: "address" }, 164 | { internalType: "uint256", name: "amount", type: "uint256" } 165 | ], 166 | name: "transferFrom", 167 | outputs: [{ internalType: "bool", name: "", type: "bool" }], 168 | stateMutability: "nonpayable", 169 | type: "function" 170 | } 171 | ] as const 172 | -------------------------------------------------------------------------------- /hooks/spendingLimit.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 8 | import { toPermissionValidator } from "@zerodev/permissions"; 9 | import { toECDSASigner } from "@zerodev/permissions/signers"; 10 | import { toSudoPolicy } from "@zerodev/permissions/policies"; 11 | import { http, createPublicClient, encodeFunctionData } from "viem"; 12 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 13 | import { sepolia } from "viem/chains"; 14 | import { toSpendingLimitHook } from "@zerodev/hooks"; 15 | import { TEST_ERC20Abi } from "./Test_ERC20abi"; 16 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 17 | 18 | if ( 19 | !process.env.ZERODEV_RPC || 20 | !process.env.PRIVATE_KEY 21 | ) { 22 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 23 | } 24 | const chain = sepolia; 25 | const publicClient = createPublicClient({ 26 | transport: http(process.env.ZERODEV_RPC), 27 | chain, 28 | }); 29 | 30 | const signer = privateKeyToAccount(generatePrivateKey()); 31 | 32 | const entryPoint = getEntryPoint("0.7"); 33 | 34 | const Test_ERC20Address = "0x3870419Ba2BBf0127060bCB37f69A1b1C090992B"; 35 | 36 | const main = async () => { 37 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 38 | signer, 39 | entryPoint, 40 | kernelVersion: KERNEL_V3_1, 41 | }); 42 | 43 | const ecdsaSigner = await toECDSASigner({ 44 | signer: privateKeyToAccount(generatePrivateKey()), 45 | }); 46 | 47 | const sudoPolicy = await toSudoPolicy({}); 48 | 49 | const permissoinPlugin = await toPermissionValidator(publicClient, { 50 | signer: ecdsaSigner, 51 | policies: [sudoPolicy], 52 | entryPoint, 53 | kernelVersion: KERNEL_V3_1, 54 | }); 55 | 56 | const spendingLimitHook = await toSpendingLimitHook({ 57 | limits: [{ token: Test_ERC20Address, allowance: BigInt(4337) }], 58 | }); 59 | 60 | const kernelAccount = await createKernelAccount(publicClient, { 61 | entryPoint, 62 | plugins: { 63 | sudo: ecdsaValidator, 64 | regular: permissoinPlugin, 65 | hook: spendingLimitHook, 66 | }, 67 | kernelVersion: KERNEL_V3_1, 68 | }); 69 | const paymaster = createZeroDevPaymasterClient({ 70 | chain: chain, 71 | transport: http(process.env.ZERODEV_RPC), 72 | }); 73 | 74 | const kernelClient = await createKernelAccountClient({ 75 | account: kernelAccount, 76 | chain, 77 | bundlerTransport: http(process.env.ZERODEV_RPC), 78 | paymaster: { 79 | getPaymasterData(userOperation) { 80 | return paymaster.sponsorUserOperation({ userOperation }); 81 | }, 82 | }, 83 | }); 84 | 85 | const amountToMint = BigInt(10000); 86 | 87 | const mintData = encodeFunctionData({ 88 | abi: TEST_ERC20Abi, 89 | functionName: "mint", 90 | args: [kernelAccount.address, amountToMint], 91 | }); 92 | 93 | const mintTransactionHash = await kernelClient.sendTransaction({ 94 | to: Test_ERC20Address, 95 | data: mintData, 96 | }); 97 | 98 | console.log("Mint transaction hash:", mintTransactionHash); 99 | 100 | const amountToTransfer = BigInt(4337); 101 | const transferData = encodeFunctionData({ 102 | abi: TEST_ERC20Abi, 103 | functionName: "transfer", 104 | args: [signer.address, amountToTransfer], 105 | }); 106 | 107 | const response = await kernelClient.sendTransaction({ 108 | to: Test_ERC20Address, 109 | data: transferData, 110 | }); 111 | 112 | console.log("Transfer transaction hash:", response); 113 | 114 | const transferDataWillFail = encodeFunctionData({ 115 | abi: TEST_ERC20Abi, 116 | functionName: "transfer", 117 | args: [signer.address, BigInt(1)], 118 | }); 119 | 120 | try { 121 | await kernelClient.sendTransaction({ 122 | to: Test_ERC20Address, 123 | data: transferDataWillFail, 124 | }); 125 | } catch (error) { 126 | console.log("Transfer failed as expected"); 127 | } 128 | }; 129 | 130 | main(); 131 | -------------------------------------------------------------------------------- /intent/.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | BUNDLER_RPC= 3 | ZERODEV_MULTI_CHAIN_PROJECT_ID= -------------------------------------------------------------------------------- /intent/README.md: -------------------------------------------------------------------------------- 1 | # ZeroDev Intent 2 | ## Overview 3 | 4 | The examples show how to use zerodev intent with three different gas payment approaches: 5 | 6 | 1. `main.ts` - Default gas payment using same tokens as inputTokens. 7 | 2. `native.ts` - Gas paid with native tokens (ETH) 8 | 3. `sponsored.ts` - Gas sponsored by the developer 9 | 10 | Additionally, there are utilities for migration and fee estimation: 11 | 12 | ### Migration Tools 13 | - `enableIntent.ts` - Upgrades kernel version and installs intent executor (required for intent functionality) 14 | - `migrateToIntentExecutor.ts` - Installs the intent executor for existing accounts 15 | 16 | ### Fee Estimation 17 | - `estimateFee.ts` - Estimates transaction fees before sending an intent, helping users understand costs upfront 18 | 19 | ## Prerequisites 20 | 21 | 1. Set up environment variables: 22 | ``` 23 | PRIVATE_KEY=your_private_key 24 | ZERODEV_RPC=your_zerodev_rpc_url 25 | ZERODEV_MULTI_CHAIN_PROJECT_ID=your_project_id # Required for sponsored transactions 26 | ``` 27 | 28 | 2. Install dependencies: 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | ## Usage 34 | 35 | ### Cross-chain Transfer Examples 36 | Each transfer example follows the same flow: 37 | 1. Creates a kernel account with intent executor 38 | 2. Get the CAB balance 39 | 3. Performs a cross-chain transfer to Base 40 | 4. Waits for intent confirmation 41 | 42 | ```bash 43 | npx ts-node intent/main.ts # Default gas payment with USDC 44 | npx ts-node intent/native.ts # Gas payment with native tokens 45 | npx ts-node intent/sponsored.ts # Gas sponsored by developer 46 | ``` 47 | 48 | ### Migration and Utilities Examples 49 | ```bash 50 | # Upgrade kernel and enable intent 51 | npx ts-node intent/enableIntent.ts 52 | 53 | # Install intent executor on existing account 54 | npx ts-node intent/migrateToIntentExecutor.ts 55 | 56 | # Estimate fees for an intent 57 | npx ts-node intent/estimateFee.ts 58 | ``` 59 | 60 | 61 | -------------------------------------------------------------------------------- /intent/enableIntent.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | KERNEL_V3_0, 4 | KERNEL_V3_2, 5 | getEntryPoint, 6 | } from "@zerodev/sdk/constants"; 7 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 8 | import { Address, type Chain, createPublicClient, http, zeroAddress } from "viem"; 9 | import { 10 | createKernelAccount, 11 | createZeroDevPaymasterClient, 12 | } from "@zerodev/sdk"; 13 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 14 | import { createIntentClient, INTENT_V0_4 } from "@zerodev/intent"; 15 | import { arbitrum, sepolia } from "viem/chains"; 16 | import { SmartAccount } from "viem/account-abstraction"; 17 | import dotenv from "dotenv"; 18 | 19 | dotenv.config(); 20 | 21 | if (!process.env.PRIVATE_KEY || !process.env.ZERODEV_RPC) { 22 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 23 | } 24 | 25 | const timeout = 100_000; 26 | const chain = arbitrum; 27 | const zerodevRpc = process.env.ZERODEV_RPC as string; 28 | // For testing purposes, we generate a new private key 29 | const privateKey = generatePrivateKey(); 30 | 31 | const publicClient = createPublicClient({ 32 | chain, 33 | transport: http(), 34 | }); 35 | 36 | async function getSigner() { 37 | return privateKeyToAccount(privateKey); 38 | } 39 | 40 | async function createKernelWithV3_0() { 41 | const signer = await getSigner(); 42 | 43 | const ecdsaValidator = await toMultiChainECDSAValidator(publicClient, { 44 | signer, 45 | kernelVersion: KERNEL_V3_0, 46 | entryPoint: getEntryPoint("0.7"), 47 | }); 48 | 49 | return createKernelAccount(publicClient, { 50 | plugins: { 51 | sudo: ecdsaValidator, 52 | }, 53 | kernelVersion: KERNEL_V3_0, 54 | entryPoint: getEntryPoint("0.7"), 55 | }); 56 | } 57 | 58 | async function createIntentClientV3_0(kernelAccount: SmartAccount) { 59 | return createIntentClient({ 60 | account: kernelAccount, 61 | chain, 62 | bundlerTransport: http(zerodevRpc, { timeout }), 63 | paymaster: createZeroDevPaymasterClient({ 64 | chain, 65 | transport: http(zerodevRpc, { timeout }), 66 | }), 67 | client: publicClient, 68 | version: INTENT_V0_4 69 | }); 70 | } 71 | 72 | async function createKernelWithV3_2(kernelAddress: Address) { 73 | const signer = await getSigner(); 74 | 75 | const v3_2Validator = await toMultiChainECDSAValidator(publicClient, { 76 | signer, 77 | kernelVersion: KERNEL_V3_2, 78 | entryPoint: getEntryPoint("0.7"), 79 | }); 80 | 81 | return createKernelAccount(publicClient, { 82 | plugins: { 83 | sudo: v3_2Validator, 84 | }, 85 | kernelVersion: KERNEL_V3_2, 86 | entryPoint: getEntryPoint("0.7"), 87 | // Important: We pass the address of the existing kernel account because we upgrade the existing kernel account 88 | address: kernelAddress, 89 | }); 90 | } 91 | 92 | async function createIntentClientV3_2(kernelAccount: any) { 93 | return createIntentClient({ 94 | account: kernelAccount, 95 | chain, 96 | bundlerTransport: http(zerodevRpc, { timeout }), 97 | version: INTENT_V0_4 98 | }); 99 | } 100 | 101 | async function verifyUpgrade(upgradedClient: any) { 102 | console.log("Verifying upgraded V3.2 client..."); 103 | const verifyTx = await upgradedClient.sendTransaction({ 104 | to: zeroAddress, 105 | data: "0x", 106 | }); 107 | console.log("Verification transaction hash:", verifyTx.uiHash); 108 | } 109 | 110 | async function main() { 111 | // Create and setup V3.0 kernel 112 | const kernelAccount = await createKernelWithV3_0(); 113 | console.log("Initial V3.0 kernel account:", kernelAccount.address); 114 | 115 | const intentClient = await createIntentClientV3_0(kernelAccount); 116 | 117 | // Enable intent and upgrade to V3.2 118 | console.log("Enabling intent and upgrading to V3.2..."); 119 | const enableHash = await intentClient.enableIntent(); 120 | console.log("Enable intent hash:", enableHash); 121 | 122 | const receipt = await intentClient.waitForUserOperationReceipt({ 123 | hash: enableHash, 124 | }); 125 | console.log("Upgrade transaction:", receipt.receipt.transactionHash); 126 | 127 | // Create and verify V3.2 client 128 | const v3_2KernelAccount = await createKernelWithV3_2(kernelAccount.address); 129 | const upgradedClient = await createIntentClientV3_2(v3_2KernelAccount); 130 | await verifyUpgrade(upgradedClient); 131 | 132 | console.log( 133 | "Successfully upgraded kernel to V3.2 with intent functionality enabled" 134 | ); 135 | } 136 | 137 | main().catch((error) => { 138 | console.error(error); 139 | process.exit(1); 140 | }); 141 | -------------------------------------------------------------------------------- /intent/estimateFee.ts: -------------------------------------------------------------------------------- 1 | import { KERNEL_V3_2, getEntryPoint } from "@zerodev/sdk/constants"; 2 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 3 | import { 4 | type Hex, 5 | type Chain, 6 | createPublicClient, 7 | http, 8 | zeroAddress, 9 | parseUnits, 10 | } from "viem"; 11 | import { createKernelAccount } from "@zerodev/sdk"; 12 | import { privateKeyToAccount } from "viem/accounts"; 13 | import { createIntentClient, installIntentExecutor, INTENT_V0_4 } from "@zerodev/intent"; 14 | import { base, optimism } from "viem/chains"; 15 | import dotenv from "dotenv"; 16 | 17 | dotenv.config(); 18 | 19 | if (!process.env.PRIVATE_KEY || !process.env.ZERODEV_RPC) { 20 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 21 | } 22 | 23 | const timeout = 100_000; 24 | const privateKey = process.env.PRIVATE_KEY as Hex; 25 | const account = privateKeyToAccount(privateKey); 26 | 27 | const intentVersion = INTENT_V0_4; 28 | 29 | const zerodevRpc = process.env.ZERODEV_RPC as string; 30 | 31 | const publicClient = createPublicClient({ 32 | chain: optimism, 33 | transport: http(), 34 | }); 35 | 36 | async function createIntentClinet(chain: Chain) { 37 | // set kernel and entryPoint version 38 | const entryPoint = getEntryPoint("0.7"); 39 | const kernelVersion = KERNEL_V3_2; 40 | 41 | // create ecdsa validator 42 | const ecdsaValidator = await toMultiChainECDSAValidator(publicClient, { 43 | signer: account, 44 | kernelVersion, 45 | entryPoint, 46 | }); 47 | 48 | const kernelAccount = await createKernelAccount(publicClient, { 49 | plugins: { 50 | sudo: ecdsaValidator, 51 | }, 52 | kernelVersion, 53 | entryPoint, 54 | initConfig: [installIntentExecutor(intentVersion)], 55 | }); 56 | 57 | // the cabclient can be used to send normal userOp and cross-chain cab tx 58 | const intentClient = createIntentClient({ 59 | account: kernelAccount, 60 | chain, 61 | bundlerTransport: http(zerodevRpc, { timeout }), 62 | version: intentVersion, 63 | }); 64 | 65 | return intentClient; 66 | } 67 | 68 | async function main() { 69 | const intentClient = await createIntentClinet(optimism); 70 | console.log('account', intentClient.account.address); 71 | 72 | const result = await intentClient.estimateUserIntentFees({ 73 | calls: [ 74 | { 75 | to: zeroAddress, 76 | value: BigInt(0), 77 | data: '0x' 78 | } 79 | ], 80 | outputTokens: [ 81 | { 82 | chainId: base.id, 83 | address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on base 84 | amount: parseUnits("100", 6), // 100 USDC 85 | }, 86 | ], 87 | }); 88 | console.log('Intent fee estimation', result); 89 | 90 | process.exit(0); 91 | } 92 | main(); 93 | -------------------------------------------------------------------------------- /intent/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { KERNEL_V3_2, getEntryPoint } from "@zerodev/sdk/constants"; 3 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 4 | import { 5 | formatUnits, 6 | erc20Abi, 7 | parseUnits, 8 | type Hex, 9 | type Chain, 10 | createPublicClient, 11 | http, 12 | encodeFunctionData, 13 | } from "viem"; 14 | import { createKernelAccount } from "@zerodev/sdk"; 15 | import { privateKeyToAccount } from "viem/accounts"; 16 | import { 17 | createIntentClient, 18 | installIntentExecutor, 19 | INTENT_V0_4, 20 | } from "@zerodev/intent"; 21 | import { arbitrum, base, optimism } from "viem/chains"; 22 | import dotenv from "dotenv"; 23 | 24 | dotenv.config(); 25 | 26 | if (!process.env.PRIVATE_KEY || !process.env.ZERODEV_RPC) { 27 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 28 | } 29 | 30 | const timeout = 100_000; 31 | const privateKey = process.env.PRIVATE_KEY as Hex; 32 | const account = privateKeyToAccount(privateKey); 33 | 34 | const chain = arbitrum; 35 | const zerodevRpc = process.env.ZERODEV_RPC as string; 36 | 37 | const publicClient = createPublicClient({ 38 | chain, 39 | transport: http(), 40 | }); 41 | 42 | const waitForUserInput = async () => { 43 | return new Promise((resolve) => { 44 | process.stdin.once("data", () => { 45 | resolve(); 46 | }); 47 | }); 48 | }; 49 | 50 | async function createIntentClinet(chain: Chain) { 51 | // set kernel and entryPoint version 52 | const entryPoint = getEntryPoint("0.7"); 53 | const kernelVersion = KERNEL_V3_2; 54 | 55 | // create ecdsa validator 56 | const ecdsaValidator = await toMultiChainECDSAValidator(publicClient, { 57 | signer: account, 58 | kernelVersion, 59 | entryPoint, 60 | }); 61 | 62 | // create a kernel account with intent executor plugin 63 | const kernelAccount = await createKernelAccount(publicClient, { 64 | plugins: { 65 | sudo: ecdsaValidator, 66 | }, 67 | kernelVersion, 68 | entryPoint, 69 | initConfig: [installIntentExecutor(INTENT_V0_4)], 70 | }); 71 | 72 | // the cabclient can be used to send normal userOp and cross-chain cab tx 73 | const intentClient = createIntentClient({ 74 | account: kernelAccount, 75 | chain, 76 | bundlerTransport: http(zerodevRpc, { timeout }), 77 | version: INTENT_V0_4, 78 | }); 79 | return intentClient; 80 | } 81 | 82 | async function main() { 83 | const intentClient = await createIntentClinet(chain); 84 | 85 | while (true) { 86 | console.log( 87 | `Please deposit USDC to ${intentClient.account.address} on Arbitrum.` 88 | ); 89 | await waitForUserInput(); 90 | const cab = await intentClient.getCAB({ 91 | networks: [arbitrum.id, base.id], 92 | tokenTickers: ["USDC"], 93 | }); 94 | if (BigInt(cab.tokens[0].amount) >= parseUnits("0.1", 6)) { 95 | break; 96 | } 97 | console.log( 98 | `Insufficient USDC balance: ${formatUnits( 99 | BigInt(cab.tokens[0].amount), 100 | 6 101 | )}. Please deposit at least 0.1 USDC.` 102 | ); 103 | } 104 | 105 | // Get chain-abstracted balance 106 | const cab = await intentClient.getCAB({ 107 | networks: [arbitrum.id, base.id], 108 | tokenTickers: ["USDC"], 109 | }); 110 | console.log("Chain abstracted balance (CAB):", cab); 111 | 112 | // send the intent - using gasTokens as inputTokens if not specified 113 | console.log("start sending UserIntent"); 114 | const result = await intentClient.sendUserIntent({ 115 | calls: [ 116 | { 117 | to: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", 118 | value: BigInt(0), 119 | // send output amount to eoa address 120 | data: encodeFunctionData({ 121 | abi: erc20Abi, 122 | functionName: "transfer", 123 | args: [account.address, parseUnits("0.1", 6)], 124 | }), 125 | }, 126 | ], 127 | outputTokens: [ 128 | { 129 | chainId: base.id, 130 | address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on base 131 | amount: parseUnits("0.1", 6), 132 | }, 133 | ], 134 | }); 135 | console.log(`succesfully send cab tx, intentId: ${result.outputUiHash.uiHash}`); 136 | 137 | // wait for the intent to be opened on the input chains 138 | await Promise.all( 139 | result.inputsUiHash.map(async (data) => { 140 | const openReceipts = await intentClient.waitForUserIntentOpenReceipt({ 141 | uiHash: data.uiHash, 142 | }); 143 | console.log(`intent open on chain ${openReceipts?.openChainId} txHash: ${openReceipts?.receipt.transactionHash}`); 144 | }) 145 | ); 146 | 147 | // wait for the intent to be executed on the destination chain 148 | const receipt = await intentClient.waitForUserIntentExecutionReceipt({ 149 | uiHash: result.outputUiHash.uiHash, 150 | }); 151 | console.log( 152 | `intent executed on chain: ${receipt?.executionChainId} txHash: ${receipt?.receipt.transactionHash}` 153 | ); 154 | process.exit(0); 155 | } 156 | main(); 157 | -------------------------------------------------------------------------------- /intent/migrateKernelAndIntentExecutor.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { type Hex, type Chain, createPublicClient, http, zeroAddress, parseUnits, encodeFunctionData, erc20Abi } from "viem"; 3 | import { KERNEL_V3_1, KERNEL_V3_2, getEntryPoint } from "@zerodev/sdk/constants"; 4 | import { createZeroDevPaymasterClient } from "@zerodev/sdk"; 5 | import { privateKeyToAccount } from "viem/accounts"; 6 | import { createEcdsaKernelMigrationAccount } from "@zerodev/ecdsa-validator"; 7 | import { createIntentClient, INTENT_V0_4, getIntentExecutorPluginData } from "@zerodev/intent"; 8 | import { base } from "viem/chains"; 9 | import dotenv from "dotenv"; 10 | 11 | dotenv.config(); 12 | 13 | if (!process.env.PRIVATE_KEY || !process.env.ZERODEV_RPC) { 14 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 15 | } 16 | 17 | const timeout = 100_000; 18 | const privateKey = process.env.PRIVATE_KEY as Hex; 19 | const account = privateKeyToAccount(privateKey); 20 | 21 | const chain = base; 22 | const zerodevRpc = process.env.ZERODEV_RPC as string; 23 | const publicClient = createPublicClient({ 24 | chain, 25 | transport: http(), 26 | }); 27 | 28 | async function getIntentClient(chain: Chain) { 29 | // set kernel and entryPoint version 30 | const entryPoint = getEntryPoint("0.7"); 31 | const kernelVersion = KERNEL_V3_1; 32 | const migrationVersion = KERNEL_V3_2; 33 | 34 | // create migration kernel account, replace `createKernelAccount` with `createEcdsaKernelMigrationAccount` 35 | const kernelAccount = await createEcdsaKernelMigrationAccount(publicClient, { 36 | entryPoint, 37 | signer: account, 38 | migrationVersion: { 39 | from: kernelVersion, 40 | to: migrationVersion, 41 | }, 42 | pluginMigrations: [getIntentExecutorPluginData(INTENT_V0_4)], 43 | }); 44 | 45 | const paymasterClient = createZeroDevPaymasterClient({ 46 | chain, 47 | transport: http(zerodevRpc, { timeout }), 48 | }); 49 | 50 | const intentClient = createIntentClient({ 51 | account: kernelAccount, 52 | chain, 53 | bundlerTransport: http(zerodevRpc, { timeout }), 54 | paymaster: { 55 | getPaymasterData: async (userOperation) => { 56 | return await paymasterClient.sponsorUserOperation({ 57 | userOperation, 58 | }); 59 | }, 60 | }, 61 | version: INTENT_V0_4, 62 | }); 63 | return intentClient; 64 | } 65 | 66 | async function main() { 67 | const intentClient = await getIntentClient(chain); 68 | 69 | // NOTE: Send migration transactions on all chains 70 | const userOpHash = await intentClient.sendUserOperation({ 71 | callData: await intentClient.account.encodeCalls([ 72 | { 73 | to: zeroAddress, 74 | value: BigInt(0), 75 | data: "0x", 76 | }, 77 | ]), 78 | }); 79 | console.log(`migration userOpHash: ${userOpHash} on chain ${chain.id}`); 80 | 81 | const receipt = await intentClient.waitForUserOperationReceipt({ 82 | hash: userOpHash, 83 | }); 84 | 85 | console.log('migration txHash', receipt.receipt.transactionHash); 86 | 87 | // Send the intent 88 | const outputToken = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' 89 | const outputChainId = base.id 90 | const amount = parseUnits("0.19", 6) 91 | const result = await intentClient.sendUserIntent({ 92 | calls: [ 93 | { 94 | to: outputToken, 95 | value: BigInt(0), 96 | data: encodeFunctionData({ 97 | abi: erc20Abi, 98 | functionName: "transfer", 99 | args: [account.address, amount], 100 | }), 101 | }, 102 | ], 103 | outputTokens: [ 104 | { 105 | address: outputToken, 106 | chainId: outputChainId, 107 | amount: amount, 108 | }, 109 | ], 110 | }); 111 | console.log(`succesfully send cab tx, intentId: ${result.outputUiHash.uiHash}`); 112 | 113 | // wait for the intent to be opened on the input chains 114 | await Promise.all( 115 | result.inputsUiHash.map(async (data) => { 116 | const openReceipts = await intentClient.waitForUserIntentOpenReceipt({ 117 | uiHash: data.uiHash, 118 | }); 119 | console.log(`intent open on chain ${openReceipts?.openChainId} txHash: ${openReceipts?.receipt?.transactionHash}`); 120 | }) 121 | ); 122 | 123 | // wait for the intent to be executed on the destination chain 124 | const intentReceipt = await intentClient.waitForUserIntentExecutionReceipt({ 125 | uiHash: result.outputUiHash.uiHash, 126 | }); 127 | console.log( 128 | `intent executed on chain: ${intentReceipt?.executionChainId} txHash: ${intentReceipt?.receipt?.transactionHash}` 129 | ); 130 | 131 | process.exit(0); 132 | } 133 | main(); 134 | -------------------------------------------------------------------------------- /intent/migrateToIntentExecutor.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { KERNEL_V3_2, getEntryPoint } from "@zerodev/sdk/constants"; 3 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 4 | import { type Hex, type Chain, createPublicClient, http, zeroAddress } from "viem"; 5 | import { 6 | createKernelAccount, 7 | createZeroDevPaymasterClient, 8 | } from "@zerodev/sdk"; 9 | import { privateKeyToAccount } from "viem/accounts"; 10 | import { createIntentClient, INTENT_V0_4 } from "@zerodev/intent"; 11 | import { sepolia } from "viem/chains"; 12 | import { getIntentExecutorPluginData } from "@zerodev/intent"; 13 | import dotenv from "dotenv"; 14 | 15 | dotenv.config(); 16 | 17 | if (!process.env.PRIVATE_KEY || !process.env.ZERODEV_RPC) { 18 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 19 | } 20 | 21 | const timeout = 100_000; 22 | const privateKey = process.env.PRIVATE_KEY as Hex; 23 | const account = privateKeyToAccount(privateKey); 24 | 25 | const chain = sepolia; 26 | const zerodevRpc = process.env.ZERODEV_RPC as string; 27 | const publicClient = createPublicClient({ 28 | chain, 29 | transport: http(), 30 | }); 31 | 32 | async function getIntentClient(chain: Chain) { 33 | // set kernel and entryPoint version 34 | const entryPoint = getEntryPoint("0.7"); 35 | const kernelVersion = KERNEL_V3_2; 36 | 37 | // create ecdsa validator 38 | const ecdsaValidator = await toMultiChainECDSAValidator(publicClient, { 39 | signer: account, 40 | kernelVersion, 41 | entryPoint, 42 | }); 43 | 44 | // 45 | const kernelAccount = await createKernelAccount(publicClient, { 46 | plugins: { 47 | sudo: ecdsaValidator, 48 | }, 49 | kernelVersion, 50 | entryPoint, 51 | pluginMigrations: [getIntentExecutorPluginData(INTENT_V0_4)], 52 | }); 53 | 54 | const paymasterClient = createZeroDevPaymasterClient({ 55 | chain, 56 | transport: http(zerodevRpc, { timeout }), 57 | }); 58 | 59 | const intentClient = createIntentClient({ 60 | account: kernelAccount, 61 | chain, 62 | bundlerTransport: http(zerodevRpc, { timeout }), 63 | paymaster: { 64 | getPaymasterData: async (userOperation) => { 65 | return await paymasterClient.sponsorUserOperation({ 66 | userOperation, 67 | }); 68 | }, 69 | }, 70 | version: INTENT_V0_4, 71 | }); 72 | return intentClient; 73 | } 74 | 75 | async function main() { 76 | const intentClient = await getIntentClient(chain); 77 | 78 | const uoHash = await intentClient.sendUserOperation({ 79 | callData: await intentClient.account.encodeCalls([ 80 | { 81 | to: zeroAddress, 82 | value: BigInt(0), 83 | data: "0x", 84 | }, 85 | ]), 86 | }); 87 | console.log("uoHash", uoHash); 88 | 89 | const receipt = await intentClient.waitForUserOperationReceipt({ 90 | hash: uoHash, 91 | }); 92 | 93 | console.log(receipt.receipt.transactionHash); 94 | 95 | process.exit(0); 96 | } 97 | main(); 98 | -------------------------------------------------------------------------------- /intent/native.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { KERNEL_V3_2, getEntryPoint } from "@zerodev/sdk/constants"; 3 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 4 | import { 5 | formatUnits, 6 | erc20Abi, 7 | parseUnits, 8 | type Hex, 9 | type Chain, 10 | createPublicClient, 11 | http, 12 | encodeFunctionData, 13 | } from "viem"; 14 | import { createKernelAccount } from "@zerodev/sdk"; 15 | import { privateKeyToAccount } from "viem/accounts"; 16 | import { 17 | createIntentClient, 18 | installIntentExecutor, 19 | INTENT_V0_4, 20 | } from "@zerodev/intent"; 21 | import { arbitrum, base } from "viem/chains"; 22 | import dotenv from "dotenv"; 23 | 24 | dotenv.config(); 25 | 26 | if (!process.env.PRIVATE_KEY || !process.env.ZERODEV_RPC) { 27 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 28 | } 29 | 30 | const timeout = 100_000; 31 | const privateKey = process.env.PRIVATE_KEY as Hex; 32 | const account = privateKeyToAccount(privateKey); 33 | 34 | const chain = arbitrum; 35 | const zerodevRpc = process.env.ZERODEV_RPC as string; 36 | 37 | const publicClient = createPublicClient({ 38 | chain, 39 | transport: http(), 40 | }); 41 | 42 | const waitForUserInput = async () => { 43 | return new Promise((resolve) => { 44 | process.stdin.once("data", () => { 45 | resolve(); 46 | }); 47 | }); 48 | }; 49 | 50 | async function createIntentClinet(chain: Chain) { 51 | // set kernel and entryPoint version 52 | const entryPoint = getEntryPoint("0.7"); 53 | const kernelVersion = KERNEL_V3_2; 54 | 55 | // create ecdsa validator 56 | const ecdsaValidator = await toMultiChainECDSAValidator(publicClient, { 57 | signer: account, 58 | kernelVersion, 59 | entryPoint, 60 | }); 61 | 62 | // 63 | const kernelAccount = await createKernelAccount(publicClient, { 64 | plugins: { 65 | sudo: ecdsaValidator, 66 | }, 67 | kernelVersion, 68 | entryPoint, 69 | initConfig: [installIntentExecutor(INTENT_V0_4)], 70 | }); 71 | 72 | // the cabclient can be used to send normal userOp and cross-chain cab tx 73 | const intentClient = createIntentClient({ 74 | account: kernelAccount, 75 | chain, 76 | bundlerTransport: http(zerodevRpc, { timeout }), 77 | version: INTENT_V0_4, 78 | }); 79 | return intentClient; 80 | } 81 | 82 | async function main() { 83 | const intentClient = await createIntentClinet(chain); 84 | 85 | while (true) { 86 | console.log( 87 | `Please deposit USDC to ${intentClient.account.address} on Arbitrum.` 88 | ); 89 | await waitForUserInput(); 90 | const cab = await intentClient.getCAB({ 91 | networks: [arbitrum.id, base.id], 92 | tokenTickers: ["USDC"], 93 | }); 94 | if (BigInt(cab.tokens[0].amount) >= parseUnits("0.1", 6)) { 95 | break; 96 | } 97 | console.log( 98 | `Insufficient USDC balance: ${formatUnits( 99 | BigInt(cab.tokens[0].amount), 100 | 6 101 | )}. Please deposit at least 0.1 USDC.` 102 | ); 103 | } 104 | 105 | // Get chain-abstracted balance 106 | const cab = await intentClient.getCAB({ 107 | networks: [arbitrum.id, base.id], 108 | tokenTickers: ["USDC"], 109 | }); 110 | console.log("Chain abstracted balance (CAB):", cab); 111 | 112 | // send the intent with native token gas payment 113 | console.log("start sending UserIntent"); 114 | const result = await intentClient.sendUserIntent({ 115 | calls: [ 116 | { 117 | to: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", 118 | value: BigInt(0), 119 | // send output amount to eoa address 120 | data: encodeFunctionData({ 121 | abi: erc20Abi, 122 | functionName: "transfer", 123 | args: [account.address, parseUnits("0.1", 6)], 124 | }), 125 | }, 126 | ], 127 | outputTokens: [ 128 | { 129 | chainId: base.id, 130 | address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on base 131 | amount: parseUnits("0.1", 6), 132 | }, 133 | ], 134 | gasToken: 'NATIVE' // Explicitly use native tokens (ETH) for gas payment 135 | }); 136 | console.log(`succesfully send cab tx, intentId: ${result.outputUiHash.uiHash}`); 137 | 138 | // wait for the intent to be opened on the input chains 139 | await Promise.all( 140 | result.inputsUiHash.map(async (data) => { 141 | const openReceipts = await intentClient.waitForUserIntentOpenReceipt({ 142 | uiHash: data.uiHash, 143 | }); 144 | console.log(`intent open on chain ${openReceipts?.openChainId} txHash: ${openReceipts?.receipt.transactionHash}`); 145 | }) 146 | ); 147 | 148 | // wait for the intent to be executed on the destination chain 149 | const receipt = await intentClient.waitForUserIntentExecutionReceipt({ 150 | uiHash: result.outputUiHash.uiHash, 151 | }); 152 | console.log( 153 | `intent executed on chain: ${receipt?.executionChainId} txHash: ${receipt?.receipt.transactionHash}` 154 | ); 155 | process.exit(0); 156 | } 157 | main(); 158 | -------------------------------------------------------------------------------- /intent/sponsored.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { KERNEL_V3_2, getEntryPoint } from "@zerodev/sdk/constants"; 3 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 4 | import { 5 | formatUnits, 6 | erc20Abi, 7 | parseUnits, 8 | type Hex, 9 | type Chain, 10 | createPublicClient, 11 | http, 12 | encodeFunctionData, 13 | } from "viem"; 14 | import { createKernelAccount } from "@zerodev/sdk"; 15 | import { privateKeyToAccount } from "viem/accounts"; 16 | import { 17 | createIntentClient, 18 | installIntentExecutor, 19 | INTENT_V0_4, 20 | } from "@zerodev/intent"; 21 | import { arbitrum, base } from "viem/chains"; 22 | import dotenv from "dotenv"; 23 | 24 | dotenv.config(); 25 | 26 | if (!process.env.PRIVATE_KEY || !process.env.ZERODEV_RPC) { 27 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 28 | } 29 | 30 | const timeout = 100_000; 31 | const privateKey = process.env.PRIVATE_KEY as Hex; 32 | const account = privateKeyToAccount(privateKey); 33 | 34 | const chain = arbitrum; 35 | const zerodevRpc = process.env.ZERODEV_RPC as string; 36 | 37 | const publicClient = createPublicClient({ 38 | chain, 39 | transport: http(), 40 | }); 41 | 42 | const waitForUserInput = async () => { 43 | return new Promise((resolve) => { 44 | process.stdin.once("data", () => { 45 | resolve(); 46 | }); 47 | }); 48 | }; 49 | 50 | async function createIntentClinet(chain: Chain) { 51 | // set kernel and entryPoint version 52 | const entryPoint = getEntryPoint("0.7"); 53 | const kernelVersion = KERNEL_V3_2; 54 | 55 | // create ecdsa validator 56 | const ecdsaValidator = await toMultiChainECDSAValidator(publicClient, { 57 | signer: account, 58 | kernelVersion, 59 | entryPoint, 60 | }); 61 | 62 | // 63 | const kernelAccount = await createKernelAccount(publicClient, { 64 | plugins: { 65 | sudo: ecdsaValidator, 66 | }, 67 | kernelVersion, 68 | entryPoint, 69 | initConfig: [installIntentExecutor(INTENT_V0_4)], 70 | }); 71 | 72 | // the cabclient can be used to send normal userOp and cross-chain cab tx 73 | const intentClient = createIntentClient({ 74 | account: kernelAccount, 75 | chain, 76 | bundlerTransport: http(zerodevRpc, { timeout }), 77 | projectId: process.env.ZERODEV_MULTI_CHAIN_PROJECT_ID, // projectId is required for sponsored intent 78 | version: INTENT_V0_4, 79 | }); 80 | return intentClient; 81 | } 82 | 83 | async function main() { 84 | const intentClient = await createIntentClinet(chain); 85 | 86 | while (true) { 87 | console.log( 88 | `Please deposit USDC to ${intentClient.account.address} on Arbitrum.` 89 | ); 90 | await waitForUserInput(); 91 | const cab = await intentClient.getCAB({ 92 | networks: [arbitrum.id, base.id], 93 | tokenTickers: ["USDC"], 94 | }); 95 | if (BigInt(cab.tokens[0].amount) >= parseUnits("0.1", 6)) { 96 | break; 97 | } 98 | console.log( 99 | `Insufficient USDC balance: ${formatUnits( 100 | BigInt(cab.tokens[0].amount), 101 | 6 102 | )}. Please deposit at least 0.1 USDC.` 103 | ); 104 | } 105 | 106 | // Get chain-abstracted balance 107 | const cab = await intentClient.getCAB({ 108 | networks: [arbitrum.id, base.id], 109 | tokenTickers: ["USDC"], 110 | }); 111 | console.log("Chain abstracted balance (CAB):", cab); 112 | 113 | // send the intent with sponsored gas 114 | console.log("start sending UserIntent"); 115 | const result = await intentClient.sendUserIntent({ 116 | calls: [ 117 | { 118 | to: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", 119 | value: BigInt(0), 120 | // send output amount to eoa address 121 | data: encodeFunctionData({ 122 | abi: erc20Abi, 123 | functionName: "transfer", 124 | args: [account.address, parseUnits("0.1", 6)], 125 | }), 126 | }, 127 | ], 128 | outputTokens: [ 129 | { 130 | chainId: base.id, 131 | address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on base 132 | amount: parseUnits("0.1", 6), 133 | }, 134 | ], 135 | gasToken: 'SPONSORED' // Use sponsored gas - requires valid project ID 136 | }); 137 | console.log(`succesfully send cab tx, intentId: ${result.outputUiHash.uiHash}`); 138 | 139 | // wait for the intent to be opened on the input chains 140 | await Promise.all( 141 | result.inputsUiHash.map(async (data) => { 142 | const openReceipts = await intentClient.waitForUserIntentOpenReceipt({ 143 | uiHash: data.uiHash, 144 | }); 145 | console.log(`intent open on chain ${openReceipts?.openChainId} txHash: ${openReceipts?.receipt.transactionHash}`); 146 | }) 147 | ); 148 | 149 | // wait for the intent to be executed on the destination chain 150 | const receipt = await intentClient.waitForUserIntentExecutionReceipt({ 151 | uiHash: result.outputUiHash.uiHash, 152 | }); 153 | console.log( 154 | `intent executed on chain: ${receipt?.executionChainId} txHash: ${receipt?.receipt.transactionHash}` 155 | ); 156 | process.exit(0); 157 | } 158 | main(); 159 | -------------------------------------------------------------------------------- /multi-chain/main.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { 3 | prepareAndSignUserOperations, 4 | toMultiChainECDSAValidator 5 | } from "@zerodev/multi-chain-ecdsa-validator" 6 | import { 7 | createKernelAccount, 8 | createKernelAccountClient, 9 | createZeroDevPaymasterClient 10 | } from "@zerodev/sdk" 11 | import { 12 | Chain, 13 | Client, 14 | Hex, 15 | Transport, 16 | createPublicClient, 17 | http, 18 | zeroAddress 19 | } from "viem" 20 | import { privateKeyToAccount } from "viem/accounts" 21 | import { optimismSepolia, sepolia } from "viem/chains" 22 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants" 23 | import { SmartAccount } from "viem/account-abstraction" 24 | 25 | dotenv.config() 26 | 27 | if ( 28 | !process.env.PRIVATE_KEY || 29 | !process.env.RPC_URL || 30 | !process.env.OPTIMISM_SEPOLIA_RPC_URL || 31 | !process.env.ZERODEV_RPC || 32 | !process.env.OPTIMISM_SEPOLIA_ZERODEV_RPC 33 | ) { 34 | console.error( 35 | "Please set PRIVATE_KEY, RPC_URL, OPTIMISM_SEPOLIA_RPC_URL, ZERODEV_RPC, OPTIMISM_SEPOLIA_ZERODEV_RPC" 36 | ) 37 | process.exit(1) 38 | } 39 | 40 | const PRIVATE_KEY = process.env.PRIVATE_KEY 41 | 42 | const SEPOLIA_RPC_URL = process.env.RPC_URL 43 | const OPTIMISM_SEPOLIA_RPC_URL = process.env.OPTIMISM_SEPOLIA_RPC_URL 44 | 45 | const SEPOLIA_ZERODEV_RPC_URL = process.env.ZERODEV_RPC 46 | const OPTIMISM_SEPOLIA_ZERODEV_RPC_URL = process.env.OPTIMISM_SEPOLIA_ZERODEV_RPC 47 | 48 | const entryPoint = getEntryPoint("0.7") 49 | 50 | const main = async () => { 51 | const sepoliaPublicClient = createPublicClient({ 52 | transport: http(SEPOLIA_RPC_URL), 53 | chain: sepolia 54 | }) 55 | const optimismSepoliaPublicClient = createPublicClient({ 56 | transport: http(OPTIMISM_SEPOLIA_RPC_URL), 57 | chain: optimismSepolia 58 | }) 59 | 60 | const signer = privateKeyToAccount(PRIVATE_KEY as Hex) 61 | const sepoliaMultiSigECDSAValidatorPlugin = 62 | await toMultiChainECDSAValidator(sepoliaPublicClient, { 63 | entryPoint, 64 | signer, 65 | kernelVersion: KERNEL_V3_1, 66 | multiChainIds: [sepolia.id, optimismSepolia.id] 67 | }) 68 | const optimismSepoliaMultiSigECDSAValidatorPlugin = 69 | await toMultiChainECDSAValidator(optimismSepoliaPublicClient, { 70 | entryPoint, 71 | signer, 72 | kernelVersion: KERNEL_V3_1, 73 | multiChainIds: [sepolia.id, optimismSepolia.id] 74 | }) 75 | 76 | const sepoliaKernelAccount = await createKernelAccount( 77 | sepoliaPublicClient, 78 | { 79 | entryPoint, 80 | plugins: { 81 | sudo: sepoliaMultiSigECDSAValidatorPlugin 82 | }, 83 | kernelVersion: KERNEL_V3_1 84 | } 85 | ) 86 | 87 | const optimismSepoliaKernelAccount = await createKernelAccount( 88 | optimismSepoliaPublicClient, 89 | { 90 | entryPoint, 91 | plugins: { 92 | sudo: optimismSepoliaMultiSigECDSAValidatorPlugin 93 | }, 94 | kernelVersion: KERNEL_V3_1 95 | } 96 | ) 97 | 98 | console.log("sepoliaKernelAccount.address", sepoliaKernelAccount.address) 99 | console.log( 100 | "optimismSepoliaKernelAccount.address", 101 | optimismSepoliaKernelAccount.address 102 | ) 103 | 104 | const sepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ 105 | chain: sepolia, 106 | transport: http(SEPOLIA_ZERODEV_RPC_URL) 107 | }) 108 | 109 | const opSepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ 110 | chain: optimismSepolia, 111 | transport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL) 112 | }) 113 | 114 | const sepoliaZerodevKernelClient = createKernelAccountClient({ 115 | account: sepoliaKernelAccount, 116 | chain: sepolia, 117 | bundlerTransport: http(SEPOLIA_ZERODEV_RPC_URL), 118 | paymaster: { 119 | getPaymasterData(userOperation) { 120 | return sepoliaZeroDevPaymasterClient.sponsorUserOperation({ 121 | userOperation 122 | }) 123 | } 124 | } 125 | }) 126 | 127 | const optimismSepoliaZerodevKernelClient = createKernelAccountClient({ 128 | account: optimismSepoliaKernelAccount, 129 | chain: optimismSepolia, 130 | bundlerTransport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL), 131 | paymaster: { 132 | getPaymasterData(userOperation) { 133 | return opSepoliaZeroDevPaymasterClient.sponsorUserOperation({ 134 | userOperation 135 | }) 136 | } 137 | } 138 | }) 139 | 140 | const clients: Client[] = [ 141 | { 142 | ...sepoliaZerodevKernelClient 143 | }, 144 | { 145 | ...optimismSepoliaZerodevKernelClient 146 | } 147 | ] 148 | 149 | const userOps = await Promise.all( 150 | clients.map(async (client) => { 151 | return { 152 | callData: await client.account.encodeCalls([ 153 | { 154 | to: zeroAddress, 155 | value: BigInt(0), 156 | data: "0x" 157 | } 158 | ]) 159 | } 160 | }) 161 | ) 162 | 163 | const userOpParams = [ 164 | { 165 | ...userOps[0], 166 | chainId: sepolia.id 167 | }, 168 | { 169 | ...userOps[1], 170 | chainId: optimismSepolia.id 171 | } 172 | ] 173 | 174 | // prepare and sign user operations with multi-chain ecdsa validator 175 | const signedUserOps = await prepareAndSignUserOperations( 176 | clients, 177 | userOpParams 178 | ) 179 | const sepoliaUserOp = signedUserOps[0] 180 | const optimismSepoliaUserOp = signedUserOps[1] 181 | 182 | console.log("sending sepoliaUserOp") 183 | const sepoliaUserOpHash = 184 | await sepoliaZerodevKernelClient.sendUserOperation(sepoliaUserOp) 185 | 186 | console.log("sepoliaUserOpHash", sepoliaUserOpHash) 187 | await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ 188 | hash: sepoliaUserOpHash 189 | }) 190 | 191 | console.log("sending optimismSepoliaUserOp") 192 | const optimismSepoliaUserOpHash = 193 | await optimismSepoliaZerodevKernelClient.sendUserOperation( 194 | optimismSepoliaUserOp 195 | ) 196 | 197 | console.log("optimismSepoliaUserOpHash", optimismSepoliaUserOpHash) 198 | await optimismSepoliaZerodevKernelClient.waitForUserOperationReceipt({ 199 | hash: optimismSepoliaUserOpHash 200 | }) 201 | } 202 | 203 | main() 204 | -------------------------------------------------------------------------------- /multi-chain/sendUserOpsWithEnable.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv" 2 | import { 3 | prepareAndSignUserOperations, 4 | toMultiChainECDSAValidator 5 | } from "@zerodev/multi-chain-ecdsa-validator" 6 | import { 7 | createKernelAccount, 8 | createKernelAccountClient, 9 | createZeroDevPaymasterClient 10 | } from "@zerodev/sdk" 11 | import { 12 | Chain, 13 | Client, 14 | Hex, 15 | Transport, 16 | createPublicClient, 17 | http, 18 | zeroAddress 19 | } from "viem" 20 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 21 | import { optimismSepolia, sepolia } from "viem/chains" 22 | import { toECDSASigner } from "@zerodev/permissions/signers" 23 | import { toSudoPolicy } from "@zerodev/permissions/policies" 24 | import { toPermissionValidator } from "@zerodev/permissions" 25 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants" 26 | import { SmartAccount } from "viem/account-abstraction" 27 | 28 | dotenv.config() 29 | 30 | if ( 31 | !process.env.PRIVATE_KEY || 32 | !process.env.RPC_URL || 33 | !process.env.OPTIMISM_SEPOLIA_RPC_URL || 34 | !process.env.ZERODEV_RPC || 35 | !process.env.OPTIMISM_SEPOLIA_ZERODEV_RPC 36 | ) { 37 | console.error( 38 | "Please set PRIVATE_KEY, RPC_URL, OPTIMISM_SEPOLIA_RPC_URL, ZERODEV_RPC, OPTIMISM_SEPOLIA_ZERODEV_RPC" 39 | ) 40 | process.exit(1) 41 | } 42 | 43 | const PRIVATE_KEY = process.env.PRIVATE_KEY 44 | 45 | const SEPOLIA_RPC_URL = process.env.RPC_URL 46 | const OPTIMISM_SEPOLIA_RPC_URL = process.env.OPTIMISM_SEPOLIA_RPC_URL 47 | 48 | const SEPOLIA_ZERODEV_RPC_URL = process.env.ZERODEV_RPC 49 | const OPTIMISM_SEPOLIA_ZERODEV_RPC_URL = process.env.OPTIMISM_SEPOLIA_ZERODEV_RPC 50 | 51 | const entryPoint = getEntryPoint("0.7") 52 | 53 | const main = async () => { 54 | const sepoliaPublicClient = createPublicClient({ 55 | transport: http(SEPOLIA_RPC_URL), 56 | chain: sepolia 57 | }) 58 | const optimismSepoliaPublicClient = createPublicClient({ 59 | transport: http(OPTIMISM_SEPOLIA_RPC_URL), 60 | chain: optimismSepolia 61 | }) 62 | 63 | const signer = privateKeyToAccount(PRIVATE_KEY as Hex) 64 | const sepoliaMultiSigECDSAValidatorPlugin = 65 | await toMultiChainECDSAValidator(sepoliaPublicClient, { 66 | entryPoint, 67 | signer, 68 | kernelVersion: KERNEL_V3_1 69 | }) 70 | const optimismSepoliaMultiSigECDSAValidatorPlugin = 71 | await toMultiChainECDSAValidator(optimismSepoliaPublicClient, { 72 | entryPoint, 73 | signer, 74 | kernelVersion: KERNEL_V3_1 75 | }) 76 | 77 | const sepoliaEcdsaSigner = privateKeyToAccount(generatePrivateKey()) 78 | const sepoliaEcdsaModularSigner = await toECDSASigner({ 79 | signer: sepoliaEcdsaSigner 80 | }) 81 | 82 | const optimismSepoliaEcdsaSigner = privateKeyToAccount(generatePrivateKey()) 83 | const optimismSepoliaEcdsaModularSigner = await toECDSASigner({ 84 | signer: optimismSepoliaEcdsaSigner 85 | }) 86 | 87 | const sudoPolicy = toSudoPolicy({}) 88 | 89 | const sepoliaPermissionPlugin = await toPermissionValidator( 90 | sepoliaPublicClient, 91 | { 92 | entryPoint, 93 | signer: sepoliaEcdsaModularSigner, 94 | policies: [sudoPolicy], 95 | kernelVersion: KERNEL_V3_1 96 | } 97 | ) 98 | 99 | const optimismSepoliaPermissionPlugin = await toPermissionValidator( 100 | optimismSepoliaPublicClient, 101 | { 102 | entryPoint, 103 | signer: optimismSepoliaEcdsaModularSigner, 104 | policies: [sudoPolicy], 105 | kernelVersion: KERNEL_V3_1 106 | } 107 | ) 108 | 109 | const sepoliaKernelAccount = await createKernelAccount( 110 | sepoliaPublicClient, 111 | { 112 | entryPoint, 113 | plugins: { 114 | sudo: sepoliaMultiSigECDSAValidatorPlugin, 115 | regular: sepoliaPermissionPlugin 116 | }, 117 | kernelVersion: KERNEL_V3_1 118 | } 119 | ) 120 | 121 | const optimismSepoliaKernelAccount = await createKernelAccount( 122 | optimismSepoliaPublicClient, 123 | { 124 | entryPoint, 125 | plugins: { 126 | sudo: optimismSepoliaMultiSigECDSAValidatorPlugin, 127 | regular: optimismSepoliaPermissionPlugin 128 | }, 129 | kernelVersion: KERNEL_V3_1 130 | } 131 | ) 132 | 133 | console.log("sepoliaKernelAccount.address", sepoliaKernelAccount.address) 134 | console.log( 135 | "optimismSepoliaKernelAccount.address", 136 | optimismSepoliaKernelAccount.address 137 | ) 138 | 139 | const sepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ 140 | chain: sepolia, 141 | transport: http(SEPOLIA_ZERODEV_RPC_URL) 142 | }) 143 | 144 | const opSepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ 145 | chain: optimismSepolia, 146 | transport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL) 147 | }) 148 | 149 | const sepoliaZerodevKernelClient = createKernelAccountClient({ 150 | account: sepoliaKernelAccount, 151 | chain: sepolia, 152 | bundlerTransport: http(SEPOLIA_ZERODEV_RPC_URL), 153 | paymaster: { 154 | getPaymasterData(userOperation) { 155 | return sepoliaZeroDevPaymasterClient.sponsorUserOperation({ 156 | userOperation 157 | }) 158 | } 159 | } 160 | }) 161 | 162 | const optimismSepoliaZerodevKernelClient = createKernelAccountClient({ 163 | account: optimismSepoliaKernelAccount, 164 | chain: optimismSepolia, 165 | bundlerTransport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL), 166 | paymaster: { 167 | getPaymasterData(userOperation) { 168 | return opSepoliaZeroDevPaymasterClient.sponsorUserOperation({ 169 | userOperation 170 | }) 171 | } 172 | } 173 | }) 174 | 175 | const clients: Client[] = [ 176 | { 177 | ...sepoliaZerodevKernelClient 178 | }, 179 | { 180 | ...optimismSepoliaZerodevKernelClient 181 | } 182 | ] 183 | 184 | const userOps = await Promise.all( 185 | clients.map(async (client) => { 186 | return { 187 | callData: await client.account.encodeCalls([ 188 | { 189 | to: zeroAddress, 190 | value: BigInt(0), 191 | data: "0x" 192 | } 193 | ]) 194 | } 195 | }) 196 | ) 197 | 198 | const userOpParams = [ 199 | { 200 | ...userOps[0], 201 | chainId: sepolia.id 202 | }, 203 | { 204 | ...userOps[1], 205 | chainId: optimismSepolia.id 206 | } 207 | ] 208 | 209 | // prepare and sign user operations with multi-chain ecdsa validator 210 | const signedUserOps = await prepareAndSignUserOperations( 211 | clients, 212 | userOpParams 213 | ) 214 | const sepoliaUserOp = signedUserOps[0] 215 | const optimismSepoliaUserOp = signedUserOps[1] 216 | 217 | console.log("sending sepoliaUserOp") 218 | const sepoliaUserOpHash = 219 | await sepoliaZerodevKernelClient.sendUserOperation(sepoliaUserOp) 220 | 221 | console.log("sepoliaUserOpHash", sepoliaUserOpHash) 222 | await sepoliaZerodevKernelClient.waitForUserOperationReceipt({ 223 | hash: sepoliaUserOpHash 224 | }) 225 | 226 | console.log("sending optimismSepoliaUserOp") 227 | const optimismSepoliaUserOpHash = 228 | await optimismSepoliaZerodevKernelClient.sendUserOperation( 229 | optimismSepoliaUserOp 230 | ) 231 | 232 | console.log("optimismSepoliaUserOpHash", optimismSepoliaUserOpHash) 233 | await optimismSepoliaZerodevKernelClient.waitForUserOperationReceipt({ 234 | hash: optimismSepoliaUserOpHash 235 | }) 236 | 237 | // now you can use sendTransaction or sendUserOperation since you've already enabled the regular validator, which is permission here. 238 | const sepoliaTxHash = await sepoliaZerodevKernelClient.sendTransaction({ 239 | to: zeroAddress, 240 | value: BigInt(0), 241 | data: "0x" 242 | }) 243 | console.log("sepoliaTxHash", sepoliaTxHash) 244 | 245 | const optimismSepoliaTxHash = 246 | await optimismSepoliaZerodevKernelClient.sendTransaction({ 247 | to: zeroAddress, 248 | value: BigInt(0), 249 | data: "0x" 250 | }) 251 | console.log("optimismSepoliaTxHash", optimismSepoliaTxHash) 252 | } 253 | 254 | main() 255 | -------------------------------------------------------------------------------- /multi-chain/useSessionKeyWithApproval.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import { toMultiChainECDSAValidator } from "@zerodev/multi-chain-ecdsa-validator"; 3 | import { 4 | addressToEmptyAccount, 5 | createKernelAccount, 6 | createKernelAccountClient, 7 | createZeroDevPaymasterClient, 8 | } from "@zerodev/sdk"; 9 | import { Hex, createPublicClient, http, zeroAddress } from "viem"; 10 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 11 | import { optimismSepolia, sepolia } from "viem/chains"; 12 | import { toECDSASigner } from "@zerodev/permissions/signers"; 13 | import { toSudoPolicy } from "@zerodev/permissions/policies"; 14 | import { 15 | deserializePermissionAccount, 16 | serializeMultiChainPermissionAccounts, 17 | toPermissionValidator, 18 | } from "@zerodev/permissions"; 19 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 20 | 21 | dotenv.config(); 22 | 23 | if ( 24 | !process.env.PRIVATE_KEY || 25 | !process.env.RPC_URL || 26 | !process.env.OPTIMISM_SEPOLIA_RPC_URL || 27 | !process.env.ZERODEV_RPC || 28 | !process.env.OPTIMISM_SEPOLIA_ZERODEV_RPC 29 | ) { 30 | console.error( 31 | "Please set PRIVATE_KEY, RPC_URL, OPTIMISM_SEPOLIA_RPC_URL, ZERODEV_RPC, OPTIMISM_SEPOLIA_ZERODEV_RPC" 32 | ); 33 | process.exit(1); 34 | } 35 | 36 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 37 | 38 | const SEPOLIA_RPC_URL = process.env.RPC_URL; 39 | const OPTIMISM_SEPOLIA_RPC_URL = process.env.OPTIMISM_SEPOLIA_RPC_URL; 40 | 41 | const SEPOLIA_ZERODEV_RPC_URL = process.env.ZERODEV_RPC; 42 | const OPTIMISM_SEPOLIA_ZERODEV_RPC_URL = process.env.OPTIMISM_SEPOLIA_ZERODEV_RPC; 43 | 44 | const entryPoint = getEntryPoint("0.7"); 45 | 46 | const main = async () => { 47 | const sepoliaPublicClient = createPublicClient({ 48 | transport: http(SEPOLIA_RPC_URL), 49 | chain: sepolia, 50 | }); 51 | const optimismSepoliaPublicClient = createPublicClient({ 52 | transport: http(OPTIMISM_SEPOLIA_RPC_URL), 53 | chain: optimismSepolia, 54 | }); 55 | 56 | const signer = privateKeyToAccount(PRIVATE_KEY as Hex); 57 | const sepoliaMultiSigECDSAValidatorPlugin = await toMultiChainECDSAValidator( 58 | sepoliaPublicClient, 59 | { 60 | entryPoint, 61 | signer, 62 | kernelVersion: KERNEL_V3_1, 63 | } 64 | ); 65 | const optimismSepoliaMultiSigECDSAValidatorPlugin = 66 | await toMultiChainECDSAValidator(optimismSepoliaPublicClient, { 67 | entryPoint, 68 | signer, 69 | kernelVersion: KERNEL_V3_1, 70 | }); 71 | 72 | const sepoliaSessionKeyAccount = privateKeyToAccount(generatePrivateKey()); 73 | 74 | const optimismSepoliaSessionKeyAccount = privateKeyToAccount( 75 | generatePrivateKey() 76 | ); 77 | 78 | // create an empty account as the session key signer for approvals 79 | const sepoliaEmptyAccount = addressToEmptyAccount( 80 | sepoliaSessionKeyAccount.address 81 | ); 82 | const optimismSepoliaEmptyAccount = addressToEmptyAccount( 83 | optimismSepoliaSessionKeyAccount.address 84 | ); 85 | 86 | const sepoliaEmptySessionKeySigner = await toECDSASigner({ 87 | signer: sepoliaEmptyAccount, 88 | }); 89 | 90 | const optimismSepoliaEmptySessionKeySigner = await toECDSASigner({ 91 | signer: optimismSepoliaEmptyAccount, 92 | }); 93 | 94 | const sudoPolicy = toSudoPolicy({}); 95 | 96 | // create a permission validator plugin with empty account signer 97 | const sepoliaPermissionPlugin = await toPermissionValidator( 98 | sepoliaPublicClient, 99 | { 100 | entryPoint, 101 | signer: sepoliaEmptySessionKeySigner, 102 | policies: [sudoPolicy], 103 | kernelVersion: KERNEL_V3_1, 104 | } 105 | ); 106 | 107 | const optimismSepoliaPermissionPlugin = await toPermissionValidator( 108 | optimismSepoliaPublicClient, 109 | { 110 | entryPoint, 111 | signer: optimismSepoliaEmptySessionKeySigner, 112 | policies: [sudoPolicy], 113 | kernelVersion: KERNEL_V3_1, 114 | } 115 | ); 116 | 117 | const sepoliaKernelAccount = await createKernelAccount(sepoliaPublicClient, { 118 | entryPoint, 119 | plugins: { 120 | sudo: sepoliaMultiSigECDSAValidatorPlugin, 121 | regular: sepoliaPermissionPlugin, 122 | }, 123 | kernelVersion: KERNEL_V3_1, 124 | }); 125 | 126 | const optimismSepoliaKernelAccount = await createKernelAccount( 127 | optimismSepoliaPublicClient, 128 | { 129 | entryPoint, 130 | plugins: { 131 | sudo: optimismSepoliaMultiSigECDSAValidatorPlugin, 132 | regular: optimismSepoliaPermissionPlugin, 133 | }, 134 | kernelVersion: KERNEL_V3_1, 135 | } 136 | ); 137 | 138 | console.log("sepoliaKernelAccount.address", sepoliaKernelAccount.address); 139 | console.log( 140 | "optimismSepoliaKernelAccount.address", 141 | optimismSepoliaKernelAccount.address 142 | ); 143 | 144 | // serialize multi chain permission account with empty account signer, so get approvals 145 | const [sepoliaApproval, optimismSepoliaApproval] = 146 | await serializeMultiChainPermissionAccounts([ 147 | { 148 | account: sepoliaKernelAccount, 149 | }, 150 | { 151 | account: optimismSepoliaKernelAccount, 152 | }, 153 | ]); 154 | 155 | // get real session key signers 156 | const sepoliaSessionKeySigner = await toECDSASigner({ 157 | signer: sepoliaSessionKeyAccount, 158 | }); 159 | 160 | const optimismSepoliaSessionKeySigner = await toECDSASigner({ 161 | signer: optimismSepoliaSessionKeyAccount, 162 | }); 163 | 164 | // deserialize the permission account with the real session key signers 165 | const deserializeSepoliaKernelAccount = await deserializePermissionAccount( 166 | sepoliaPublicClient, 167 | entryPoint, 168 | KERNEL_V3_1, 169 | sepoliaApproval, 170 | sepoliaSessionKeySigner 171 | ); 172 | 173 | const deserializeOptimismSepoliaKernelAccount = 174 | await deserializePermissionAccount( 175 | optimismSepoliaPublicClient, 176 | entryPoint, 177 | KERNEL_V3_1, 178 | optimismSepoliaApproval, 179 | optimismSepoliaSessionKeySigner 180 | ); 181 | 182 | const sepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ 183 | chain: sepolia, 184 | transport: http(SEPOLIA_ZERODEV_RPC_URL), 185 | }); 186 | 187 | const opSepoliaZeroDevPaymasterClient = createZeroDevPaymasterClient({ 188 | chain: optimismSepolia, 189 | transport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL), 190 | }); 191 | 192 | // use createKernelMultiChainClient to support multi-chain operations instead of createKernelAccountClient 193 | const sepoliaZerodevKernelClient = createKernelAccountClient({ 194 | // use the deserialized permission account 195 | account: deserializeSepoliaKernelAccount, 196 | chain: sepolia, 197 | bundlerTransport: http(SEPOLIA_ZERODEV_RPC_URL), 198 | paymaster: { 199 | getPaymasterData(userOperation) { 200 | return sepoliaZeroDevPaymasterClient.sponsorUserOperation({ 201 | userOperation, 202 | }); 203 | }, 204 | }, 205 | }); 206 | 207 | const optimismSepoliaZerodevKernelClient = createKernelAccountClient({ 208 | // use the deserialized permission account 209 | account: deserializeOptimismSepoliaKernelAccount, 210 | chain: optimismSepolia, 211 | bundlerTransport: http(OPTIMISM_SEPOLIA_ZERODEV_RPC_URL), 212 | paymaster: { 213 | getPaymasterData(userOperation) { 214 | return opSepoliaZeroDevPaymasterClient.sponsorUserOperation({ 215 | userOperation, 216 | }); 217 | }, 218 | }, 219 | }); 220 | 221 | // send user ops. you don't need additional enables like `prepareAndSignUserOperations`, since it already has the approvals with serialized account 222 | const sepoliaTxHash = await sepoliaZerodevKernelClient.sendTransaction({ 223 | to: zeroAddress, 224 | value: BigInt(0), 225 | data: "0x", 226 | }); 227 | 228 | console.log("sepoliaTxHash", sepoliaTxHash); 229 | 230 | const optimismSepoliaTxHash = 231 | await optimismSepoliaZerodevKernelClient.sendTransaction({ 232 | to: zeroAddress, 233 | value: BigInt(0), 234 | data: "0x", 235 | }); 236 | 237 | console.log("optimismSepoliaTxHash", optimismSepoliaTxHash); 238 | }; 239 | 240 | main(); 241 | -------------------------------------------------------------------------------- /multisig/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config" 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | } from "@zerodev/sdk" 6 | import { http, createPublicClient, zeroAddress } from "viem" 7 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts" 8 | import { sepolia } from "viem/chains" 9 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants" 10 | import { 11 | createWeightedKernelAccountClient, 12 | createWeightedValidator, 13 | toECDSASigner, 14 | type WeightedSigner, 15 | } from "@zerodev/weighted-validator" 16 | 17 | if (!process.env.ZERODEV_RPC) { 18 | throw new Error("ZERODEV_RPC is not set") 19 | } 20 | 21 | const publicClient = createPublicClient({ 22 | transport: http(process.env.ZERODEV_RPC), 23 | chain: sepolia, 24 | }) 25 | 26 | // Generate or use existing private keys for signers 27 | const pKey1 = generatePrivateKey() 28 | const pKey2 = generatePrivateKey() 29 | 30 | const eoaAccount1 = privateKeyToAccount(pKey1) 31 | const eoaAccount2 = privateKeyToAccount(pKey2) 32 | 33 | const entryPoint = getEntryPoint("0.7") 34 | 35 | const main = async () => { 36 | // Create ECDSA signers 37 | const ecdsaSigner1 = await toECDSASigner({ signer: eoaAccount1 }) 38 | const ecdsaSigner2 = await toECDSASigner({ signer: eoaAccount2 }) 39 | 40 | // Helper function to create client for each signer 41 | const createWeightedAccountClient = async (signer: WeightedSigner) => { 42 | const multiSigValidator = await createWeightedValidator(publicClient, { 43 | entryPoint, 44 | signer, 45 | config: { 46 | threshold: 100, 47 | signers: [ 48 | { publicKey: ecdsaSigner1.account.address, weight: 50 }, 49 | { publicKey: ecdsaSigner2.account.address, weight: 50 } 50 | ] 51 | }, 52 | kernelVersion: KERNEL_V3_1 53 | }) 54 | 55 | const account = await createKernelAccount(publicClient, { 56 | entryPoint, 57 | plugins: { 58 | sudo: multiSigValidator 59 | }, 60 | kernelVersion: KERNEL_V3_1 61 | }) 62 | 63 | console.log(`Account address: ${account.address}`) 64 | 65 | const paymasterClient = createZeroDevPaymasterClient({ 66 | chain: sepolia, 67 | transport: http(process.env.ZERODEV_RPC), 68 | }) 69 | 70 | return createWeightedKernelAccountClient({ 71 | account, 72 | chain: sepolia, 73 | bundlerTransport: http(process.env.ZERODEV_RPC), 74 | paymaster: paymasterClient 75 | }) 76 | } 77 | 78 | // Create clients for both signers 79 | const client1 = await createWeightedAccountClient(ecdsaSigner1) 80 | const client2 = await createWeightedAccountClient(ecdsaSigner2) 81 | 82 | // Prepare the UserOperation that needs to be signed 83 | const callData = await client1.account.encodeCalls([ 84 | { 85 | to: zeroAddress, 86 | data: "0x", 87 | value: BigInt(0) 88 | } 89 | ]) 90 | 91 | // Get approval from first signer 92 | const signature1 = await client1.approveUserOperation({ 93 | callData 94 | }) 95 | 96 | // Get approval from second signer 97 | const signature2 = await client2.approveUserOperation({ 98 | callData 99 | }) 100 | 101 | // Send the transaction with both signatures 102 | const userOpHash = await client2.sendUserOperationWithSignatures({ 103 | callData, 104 | signatures: [signature1, signature2] 105 | }) 106 | 107 | console.log("UserOperation hash:", userOpHash) 108 | 109 | const receipt = await client2.waitForUserOperationReceipt({ 110 | hash: userOpHash 111 | }) 112 | 113 | console.log("Transaction hash:", receipt.receipt.transactionHash) 114 | console.log("UserOperation completed!") 115 | } 116 | 117 | main().catch(console.error) -------------------------------------------------------------------------------- /multisig/with-session-key.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { 8 | toPermissionValidator, 9 | deserializePermissionAccount, 10 | serializePermissionAccount, 11 | } from "@zerodev/permissions"; 12 | import { toECDSASigner } from "@zerodev/permissions/signers"; 13 | import { toSudoPolicy } from "@zerodev/permissions/policies"; 14 | import { http, createPublicClient, parseAbi, type Address, zeroAddress } from "viem"; 15 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 16 | import { sepolia } from "viem/chains"; 17 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 18 | import { createWeightedECDSAValidator } from "@zerodev/weighted-ecdsa-validator"; 19 | 20 | if ( 21 | !process.env.ZERODEV_RPC || 22 | !process.env.PRIVATE_KEY 23 | ) { 24 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 25 | } 26 | 27 | const publicClient = createPublicClient({ 28 | chain: sepolia, 29 | transport: http(process.env.ZERODEV_RPC), 30 | }); 31 | 32 | const signer1 = privateKeyToAccount(generatePrivateKey()); 33 | const signer2 = privateKeyToAccount(generatePrivateKey()); 34 | const signer3 = privateKeyToAccount(generatePrivateKey()); 35 | 36 | const contractAddress = "0x34bE7f35132E97915633BC1fc020364EA5134863" as Address; 37 | const contractABI = parseAbi([ 38 | "function mint(address _to) public", 39 | "function balanceOf(address owner) external view returns (uint256 balance)", 40 | ]); 41 | const sessionPrivateKey = generatePrivateKey(); 42 | const entryPoint = getEntryPoint("0.7"); 43 | 44 | const createSessionKey = async () => { 45 | const multisigValidator = await createWeightedECDSAValidator(publicClient, { 46 | entryPoint, 47 | config: { 48 | threshold: 100, 49 | signers: [ 50 | { address: signer1.address as Address, weight: 100 }, 51 | { address: signer2.address as Address, weight: 50 }, 52 | { address: signer3.address as Address, weight: 50 }, 53 | ], 54 | }, 55 | signers: [signer1], 56 | kernelVersion: KERNEL_V3_1, 57 | }); 58 | 59 | const masterAccount = await createKernelAccount(publicClient, { 60 | entryPoint, 61 | plugins: { 62 | sudo: multisigValidator, 63 | }, 64 | kernelVersion: KERNEL_V3_1, 65 | }); 66 | console.log("Account address:", masterAccount.address); 67 | 68 | const sessionKeySigner = await toECDSASigner({ 69 | signer: privateKeyToAccount(sessionPrivateKey) 70 | }); 71 | 72 | const sessionKeyValidator = await toPermissionValidator(publicClient, { 73 | entryPoint, 74 | signer: sessionKeySigner, 75 | policies: [ 76 | toSudoPolicy({}), 77 | ], 78 | kernelVersion: KERNEL_V3_1, 79 | }); 80 | 81 | const sessionKeyAccount = await createKernelAccount(publicClient, { 82 | entryPoint, 83 | plugins: { 84 | sudo: multisigValidator, 85 | regular: sessionKeyValidator, 86 | }, 87 | kernelVersion: KERNEL_V3_1, 88 | }); 89 | 90 | // Serialize the session key account with its private key 91 | return await serializePermissionAccount(sessionKeyAccount, sessionPrivateKey); 92 | }; 93 | 94 | const useSessionKey = async (serializedSessionKey: string) => { 95 | // Deserialize the session key account 96 | const sessionKeyAccount = await deserializePermissionAccount( 97 | publicClient, 98 | entryPoint, 99 | KERNEL_V3_1, 100 | serializedSessionKey 101 | ); 102 | 103 | const kernelPaymaster = createZeroDevPaymasterClient({ 104 | chain: sepolia, 105 | transport: http(process.env.ZERODEV_RPC), 106 | }); 107 | 108 | const kernelClient = createKernelAccountClient({ 109 | account: sessionKeyAccount, 110 | chain: sepolia, 111 | bundlerTransport: http(process.env.ZERODEV_RPC), 112 | paymaster: { 113 | getPaymasterData(userOperation) { 114 | return kernelPaymaster.sponsorUserOperation({ userOperation }); 115 | }, 116 | }, 117 | }); 118 | 119 | const userOpHash = await kernelClient.sendUserOperation({ 120 | callData: await sessionKeyAccount.encodeCalls([ 121 | { 122 | to: zeroAddress, 123 | value: BigInt(0), 124 | data: "0x", 125 | }, 126 | ]), 127 | }); 128 | 129 | console.log("UserOp hash:", userOpHash); 130 | 131 | const {receipt} = await kernelClient.waitForUserOperationReceipt({ 132 | hash: userOpHash, 133 | }); 134 | console.log("UserOp completed!", receipt.transactionHash); 135 | }; 136 | 137 | const main = async () => { 138 | // The owner creates a session key and shares the serialized data with the agent 139 | const serializedSessionKey = await createSessionKey(); 140 | 141 | // The agent uses the serialized session key data to perform operations 142 | await useSessionKey(serializedSessionKey); 143 | }; 144 | 145 | main(); 146 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kerneljs-examples", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "dependencies": { 6 | "@zerodev/ecdsa-validator": "^5.4.8", 7 | "@zerodev/hooks": "^5.3.4", 8 | "@zerodev/intent": "^0.0.25", 9 | "@zerodev/multi-chain-ecdsa-validator": "^5.4.5", 10 | "@zerodev/permissions": "^5.5.8", 11 | "@zerodev/remote-signer": "^5.3.3", 12 | "@zerodev/sdk": "^5.4.36", 13 | "@zerodev/session-key": "^5.5.3", 14 | "@zerodev/weighted-ecdsa-validator": "^5.4.3", 15 | "@zerodev/weighted-validator": "^5.4.6", 16 | "viem": "^2.28.0" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20.17.30", 20 | "dotenv": "^16.5.0", 21 | "tslib": "^2.8.1", 22 | "typescript": "^5.8.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pay-gas-with-erc20/estimate-gas.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 3 | import { 4 | createKernelAccount, 5 | createKernelAccountClient, 6 | createZeroDevPaymasterClient, 7 | gasTokenAddresses, 8 | } from "@zerodev/sdk"; 9 | import { createPublicClient, http, zeroAddress } from "viem"; 10 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 11 | import { sepolia } from "viem/chains"; 12 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 13 | 14 | if (!process.env.ZERODEV_RPC) { 15 | throw new Error("ZERODEV_RPC is not set"); 16 | } 17 | 18 | const chain = sepolia; 19 | const publicClient = createPublicClient({ 20 | transport: http(process.env.ZERODEV_RPC), 21 | chain, 22 | }); 23 | 24 | const signer = privateKeyToAccount(generatePrivateKey()); 25 | const entryPoint = getEntryPoint("0.7"); 26 | 27 | const main = async () => { 28 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 29 | signer, 30 | entryPoint, 31 | kernelVersion: KERNEL_V3_1, 32 | }); 33 | 34 | const account = await createKernelAccount(publicClient, { 35 | plugins: { 36 | sudo: ecdsaValidator, 37 | }, 38 | entryPoint, 39 | kernelVersion: KERNEL_V3_1, 40 | }); 41 | 42 | const paymasterClient = createZeroDevPaymasterClient({ 43 | chain, 44 | transport: http(process.env.ZERODEV_RPC), 45 | }); 46 | 47 | const kernelClient = createKernelAccountClient({ 48 | account, 49 | chain, 50 | bundlerTransport: http(process.env.ZERODEV_RPC), 51 | paymaster: paymasterClient, 52 | paymasterContext: { token: gasTokenAddresses[chain.id]["USDC"] }, 53 | }); 54 | 55 | const userOperation = await kernelClient.prepareUserOperation({ 56 | callData: await account.encodeCalls([ 57 | { 58 | to: zeroAddress, 59 | value: BigInt(0), 60 | data: "0x", 61 | }, 62 | ]), 63 | }); 64 | 65 | const result = await paymasterClient.estimateGasInERC20({ 66 | userOperation, 67 | gasTokenAddress: gasTokenAddresses[chain.id]["USDC"], 68 | entryPoint: entryPoint.address, 69 | }); 70 | 71 | console.log(`fee: ${result.amount} test tokens`); 72 | }; 73 | 74 | main(); 75 | -------------------------------------------------------------------------------- /pay-gas-with-erc20/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | getERC20PaymasterApproveCall, 7 | gasTokenAddresses, 8 | } from "@zerodev/sdk"; 9 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 10 | import { 11 | http, 12 | Hex, 13 | createPublicClient, 14 | zeroAddress, 15 | encodeFunctionData, 16 | parseAbi, 17 | parseEther, 18 | } from "viem"; 19 | import { privateKeyToAccount } from "viem/accounts"; 20 | import { sepolia } from "viem/chains"; 21 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 22 | 23 | if ( 24 | !process.env.ZERODEV_RPC || 25 | !process.env.PRIVATE_KEY 26 | ) { 27 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 28 | } 29 | const chain = sepolia; 30 | const publicClient = createPublicClient({ 31 | transport: http(process.env.ZERODEV_RPC), 32 | chain, 33 | }); 34 | 35 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 36 | 37 | const TEST_ERC20_ABI = parseAbi([ 38 | "function mint(address to, uint256 amount) external", 39 | ]); 40 | const entryPoint = getEntryPoint("0.7"); 41 | 42 | const main = async () => { 43 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 44 | entryPoint, 45 | signer, 46 | kernelVersion: KERNEL_V3_1, 47 | }); 48 | 49 | const account = await createKernelAccount(publicClient, { 50 | entryPoint, 51 | plugins: { 52 | sudo: ecdsaValidator, 53 | }, 54 | kernelVersion: KERNEL_V3_1, 55 | }); 56 | 57 | const paymasterClient = createZeroDevPaymasterClient({ 58 | chain, 59 | transport: http(process.env.ZERODEV_RPC), 60 | }); 61 | 62 | const kernelClient = createKernelAccountClient({ 63 | account, 64 | chain, 65 | bundlerTransport: http(process.env.ZERODEV_RPC), 66 | paymaster: paymasterClient, 67 | paymasterContext: { 68 | token: gasTokenAddresses[sepolia.id]["USDC"], 69 | }, 70 | }); 71 | 72 | console.log("My account:", account.address); 73 | 74 | // You just need to make sure that the account has enough ERC20 tokens 75 | // and that it has approved the paymaster with enough tokens to pay for 76 | // the gas. 77 | 78 | // You can get testnet USDC from https://faucet.circle.com/ 79 | const userOpHash = await kernelClient.sendUserOperation({ 80 | callData: await account.encodeCalls([ 81 | await getERC20PaymasterApproveCall(paymasterClient, { 82 | gasToken: gasTokenAddresses[chain.id]["USDC"], 83 | approveAmount: parseEther("1"), 84 | entryPoint, 85 | }), 86 | { 87 | to: zeroAddress, 88 | value: BigInt(0), 89 | data: "0x", 90 | }, 91 | ]), 92 | }); 93 | 94 | console.log("UserOp hash:", userOpHash); 95 | 96 | const receipt = await kernelClient.waitForUserOperationReceipt({ 97 | hash: userOpHash, 98 | }); 99 | 100 | console.log("UserOp completed", receipt.receipt.transactionHash); 101 | }; 102 | 103 | main(); 104 | -------------------------------------------------------------------------------- /pay-gas-with-erc20/v1/test.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | Hex, 4 | encodeFunctionData, 5 | http, 6 | parseAbi, 7 | parseEther, 8 | zeroAddress, 9 | } from "viem"; 10 | import { privateKeyToAccount } from "viem/accounts"; 11 | import { sepolia } from "viem/chains"; 12 | import { 13 | ZeroDevPaymasterClient, 14 | createZeroDevPaymasterClient, 15 | gasTokenAddresses, 16 | getERC20PaymasterApproveCall, 17 | } from "@zerodev/sdk"; 18 | import { 19 | getKernelV1Account, 20 | getKernelV1AccountClient, 21 | getZeroDevERC20PaymasterClient, 22 | } from "../../utils"; 23 | import { getEntryPoint } from "@zerodev/sdk/constants"; 24 | 25 | const TEST_ERC20_ABI = parseAbi([ 26 | "function mint(address to, uint256 amount) external", 27 | ]); 28 | 29 | const entryPoint = getEntryPoint("0.6"); 30 | 31 | const main = async () => { 32 | const kernelAccount = await getKernelV1Account(); 33 | const zerodevPaymaster = getZeroDevERC20PaymasterClient(); 34 | const kernelClient = await getKernelV1AccountClient({ 35 | account: kernelAccount, 36 | paymaster: zerodevPaymaster, 37 | }); 38 | 39 | const paymasterClient = createZeroDevPaymasterClient({ 40 | chain: sepolia, 41 | transport: http(process.env.ZERODEV_RPC), 42 | }); 43 | 44 | console.log("My account:", kernelClient.account.address); 45 | 46 | // In this example, just for convenience, we mint and approve the test 47 | // tokens within the same batch, but you don't have to do that. 48 | // 49 | // You just need to make sure that the account has enough ERC20 tokens 50 | // and that it has approved the paymaster with enough tokens to pay for 51 | // the gas. 52 | const userOpHash = await kernelClient.sendUserOperation({ 53 | callData: await kernelClient.account.encodeCalls([ 54 | { 55 | to: gasTokenAddresses[sepolia.id]["6TEST"], 56 | data: encodeFunctionData({ 57 | abi: TEST_ERC20_ABI, 58 | functionName: "mint", 59 | args: [kernelClient.account.address, parseEther("0.9")], 60 | }), 61 | value: BigInt(0), 62 | }, 63 | await getERC20PaymasterApproveCall(paymasterClient, { 64 | gasToken: gasTokenAddresses[sepolia.id]["6TEST"], 65 | approveAmount: parseEther("0.9"), 66 | entryPoint, 67 | }), 68 | { 69 | to: zeroAddress, 70 | value: BigInt(0), 71 | data: "0x", 72 | }, 73 | ]), 74 | }); 75 | 76 | console.log("userOp hash:", userOpHash); 77 | 78 | await kernelClient.waitForUserOperationReceipt({ 79 | hash: userOpHash, 80 | }); 81 | 82 | console.log("UserOp completed"); 83 | }; 84 | 85 | main(); 86 | -------------------------------------------------------------------------------- /remote-signer/main.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 8 | import { toPermissionValidator } from "@zerodev/permissions"; 9 | import { toRemoteSigner, RemoteSignerMode } from "@zerodev/remote-signer"; 10 | import { toSudoPolicy } from "@zerodev/permissions/policies"; 11 | import { http, Hex, createPublicClient, zeroAddress } from "viem"; 12 | import { privateKeyToAccount } from "viem/accounts"; 13 | import { sepolia } from "viem/chains"; 14 | import { toECDSASigner } from "@zerodev/permissions/signers"; 15 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 16 | 17 | if ( 18 | !process.env.ZERODEV_RPC || 19 | !process.env.PRIVATE_KEY || 20 | !process.env.ZERODEV_API_KEY 21 | ) { 22 | throw new Error( 23 | "ZERODEV_RPC or PRIVATE_KEY or ZERODEV_API_KEY is not set" 24 | ); 25 | } 26 | const chain = sepolia; 27 | const publicClient = createPublicClient({ 28 | transport: http(process.env.ZERODEV_RPC), 29 | chain, 30 | }); 31 | 32 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 33 | 34 | const entryPoint = getEntryPoint("0.7"); 35 | const apiKey = process.env.ZERODEV_API_KEY; 36 | 37 | const main = async () => { 38 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 39 | signer, 40 | entryPoint, 41 | kernelVersion: KERNEL_V3_1, 42 | }); 43 | 44 | // first we create the remote signer in create mode 45 | const remoteSigner = await toRemoteSigner({ 46 | apiKey, 47 | mode: RemoteSignerMode.Create, 48 | }); 49 | 50 | // now we get the ecdsa signer using the remote signer 51 | const ecdsaSigner = await toECDSASigner({ signer: remoteSigner }); 52 | 53 | const permissionPlugin = await toPermissionValidator(publicClient, { 54 | entryPoint, 55 | signer: ecdsaSigner, 56 | policies: [toSudoPolicy({})], 57 | kernelVersion: KERNEL_V3_1, 58 | }); 59 | 60 | const account = await createKernelAccount(publicClient, { 61 | plugins: { 62 | sudo: ecdsaValidator, 63 | regular: permissionPlugin, 64 | }, 65 | entryPoint, 66 | kernelVersion: KERNEL_V3_1, 67 | }); 68 | console.log("My account:", account.address); 69 | 70 | const paymasterClient = createZeroDevPaymasterClient({ 71 | chain, 72 | transport: http(process.env.ZERODEV_RPC), 73 | }); 74 | 75 | const kernelClient = createKernelAccountClient({ 76 | account, 77 | chain, 78 | bundlerTransport: http(process.env.ZERODEV_RPC), 79 | paymaster: { 80 | getPaymasterData(userOperation) { 81 | return paymasterClient.sponsorUserOperation({ userOperation }); 82 | }, 83 | }, 84 | }); 85 | 86 | const txHash = await kernelClient.sendTransaction({ 87 | to: zeroAddress, 88 | value: BigInt(0), 89 | data: "0x", 90 | }); 91 | 92 | console.log("txHash hash:", txHash); 93 | 94 | // now we get the remote signer in get mode 95 | const remoteSignerWithGet = await toRemoteSigner({ 96 | apiKey, 97 | keyAddress: remoteSigner.address, // specify the account address to get 98 | mode: RemoteSignerMode.Get, 99 | }); 100 | 101 | const ecdsaSigner2 = await toECDSASigner({ signer: remoteSignerWithGet }); 102 | 103 | const permissionPlugin2 = await toPermissionValidator(publicClient, { 104 | entryPoint, 105 | signer: ecdsaSigner2, 106 | policies: [toSudoPolicy({})], 107 | kernelVersion: KERNEL_V3_1, 108 | }); 109 | 110 | const account2 = await createKernelAccount(publicClient, { 111 | plugins: { 112 | sudo: ecdsaValidator, 113 | regular: permissionPlugin2, 114 | }, 115 | entryPoint, 116 | kernelVersion: KERNEL_V3_1, 117 | }); 118 | 119 | console.log("My account2:", account2.address); 120 | 121 | const kernelClient2 = createKernelAccountClient({ 122 | account: account2, 123 | chain, 124 | bundlerTransport: http(process.env.ZERODEV_RPC), 125 | paymaster: { 126 | getPaymasterData(userOperation) { 127 | return paymasterClient.sponsorUserOperation({ userOperation }); 128 | }, 129 | }, 130 | }); 131 | 132 | const txHash2 = await kernelClient2.sendTransaction({ 133 | to: zeroAddress, 134 | value: BigInt(0), 135 | data: "0x", 136 | }); 137 | 138 | console.log("txHash hash:", txHash2); 139 | }; 140 | 141 | main(); 142 | -------------------------------------------------------------------------------- /send-transactions/send-txn.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { zeroAddress } from "viem"; 3 | import { getKernelClient } from "../utils"; 4 | import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; 5 | 6 | async function main() { 7 | const kernelClient = await getKernelClient("0.7", KERNEL_V3_1); 8 | 9 | console.log("Account address:", kernelClient.account.address); 10 | 11 | const txnHash = await kernelClient.sendTransaction({ 12 | to: zeroAddress, // use any address 13 | value: BigInt(0), // default to 0 14 | data: "0x", // default to 0x 15 | }); 16 | 17 | console.log("Txn hash:", txnHash); 18 | } 19 | 20 | main(); 21 | -------------------------------------------------------------------------------- /send-transactions/send-userop.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { zeroAddress } from "viem"; 3 | import { getKernelClient } from "../utils"; 4 | import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; 5 | 6 | async function main() { 7 | const kernelClient = await getKernelClient("0.7", KERNEL_V3_1); 8 | 9 | console.log("Account address:", kernelClient.account.address); 10 | 11 | const userOpHash = await kernelClient.sendUserOperation({ 12 | callData: await kernelClient.account.encodeCalls([ 13 | { 14 | to: zeroAddress, 15 | value: BigInt(0), 16 | data: "0x", 17 | }, 18 | ]), 19 | }); 20 | 21 | console.log("UserOp hash:", userOpHash); 22 | console.log("Waiting for UserOp to complete..."); 23 | 24 | await kernelClient.waitForUserOperationReceipt({ 25 | hash: userOpHash, 26 | }); 27 | 28 | console.log("UserOp completed"); 29 | 30 | process.exit(0); 31 | } 32 | 33 | main(); 34 | -------------------------------------------------------------------------------- /send-transactions/with-2d-nonce.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { zeroAddress } from "viem"; 3 | import { getKernelClient } from "../utils"; 4 | import { getCustomNonceKeyFromString } from "@zerodev/sdk"; 5 | import { KERNEL_V3_1 } from "@zerodev/sdk/constants"; 6 | 7 | async function main() { 8 | const kernelClient = await getKernelClient("0.7", KERNEL_V3_1); 9 | 10 | const customNonceKey1 = getCustomNonceKeyFromString( 11 | "Custom Nonce Key Example 1", 12 | "0.7" 13 | ); 14 | 15 | const customNonceKey2 = getCustomNonceKeyFromString( 16 | "Custom Nonce Key Example 2", 17 | "0.7" 18 | ); 19 | 20 | const nonce1 = await kernelClient.account.getNonce({ key: customNonceKey1 }); 21 | const nonce2 = await kernelClient.account.getNonce({ key: customNonceKey2 }); 22 | 23 | const [userOpHash1, userOpHash2] = await Promise.all([ 24 | kernelClient.sendUserOperation({ 25 | callData: await kernelClient.account.encodeCalls([ 26 | { 27 | to: zeroAddress, 28 | value: BigInt(0), 29 | data: "0x", 30 | }, 31 | ]), 32 | nonce: nonce1, 33 | }), 34 | kernelClient.sendUserOperation({ 35 | callData: await kernelClient.account.encodeCalls([ 36 | { 37 | to: zeroAddress, 38 | value: BigInt(0), 39 | data: "0x", 40 | }, 41 | ]), 42 | nonce: nonce2, 43 | }), 44 | ]); 45 | 46 | console.log("UserOp1 hash:", userOpHash1); 47 | console.log("UserOp2 hash:", userOpHash2); 48 | console.log("Waiting for UserOp to complete..."); 49 | 50 | await Promise.all([ 51 | kernelClient.waitForUserOperationReceipt({ hash: userOpHash1 }), 52 | kernelClient.waitForUserOperationReceipt({ hash: userOpHash2 }), 53 | ]); 54 | 55 | console.log("UserOp completed"); 56 | } 57 | 58 | main(); 59 | -------------------------------------------------------------------------------- /session-keys/1-click-trading.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 8 | import { 9 | http, 10 | Hex, 11 | createPublicClient, 12 | parseAbi, 13 | encodeFunctionData, 14 | zeroAddress, 15 | } from "viem"; 16 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 17 | import { sepolia } from "viem/chains"; 18 | import { toECDSASigner } from "@zerodev/permissions/signers"; 19 | import { 20 | ModularSigner, 21 | deserializePermissionAccount, 22 | serializePermissionAccount, 23 | toPermissionValidator, 24 | } from "@zerodev/permissions"; 25 | import { 26 | ParamCondition, 27 | toCallPolicy, 28 | toSudoPolicy, 29 | } from "@zerodev/permissions/policies"; 30 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 31 | 32 | if ( 33 | !process.env.ZERODEV_RPC || 34 | !process.env.PRIVATE_KEY 35 | ) { 36 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 37 | } 38 | 39 | const publicClient = createPublicClient({ 40 | chain: sepolia, 41 | transport: http(process.env.ZERODEV_RPC), 42 | }); 43 | 44 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 45 | 46 | const entryPoint = getEntryPoint("0.7"); 47 | const createSessionKey = async ( 48 | sessionKeySigner: ModularSigner, 49 | sessionPrivateKey: Hex 50 | ) => { 51 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 52 | entryPoint, 53 | signer, 54 | kernelVersion: KERNEL_V3_1, 55 | }); 56 | 57 | const masterAccount = await createKernelAccount(publicClient, { 58 | entryPoint, 59 | plugins: { 60 | sudo: ecdsaValidator, 61 | }, 62 | kernelVersion: KERNEL_V3_1, 63 | }); 64 | console.log("Account address:", masterAccount.address); 65 | 66 | const permissionPlugin = await toPermissionValidator(publicClient, { 67 | entryPoint, 68 | signer: sessionKeySigner, 69 | policies: [ 70 | // In this example, we are just using a sudo policy to allow everything. 71 | // In practice, you would want to set more restrictive policies. 72 | toSudoPolicy({}), 73 | ], 74 | kernelVersion: KERNEL_V3_1, 75 | }); 76 | 77 | const sessionKeyAccount = await createKernelAccount(publicClient, { 78 | entryPoint, 79 | plugins: { 80 | sudo: ecdsaValidator, 81 | regular: permissionPlugin, 82 | }, 83 | kernelVersion: KERNEL_V3_1, 84 | }); 85 | 86 | // Include the private key when you serialize the session key 87 | return await serializePermissionAccount(sessionKeyAccount, sessionPrivateKey); 88 | }; 89 | 90 | const useSessionKey = async (serializedSessionKey: string) => { 91 | const sessionKeyAccount = await deserializePermissionAccount( 92 | publicClient, 93 | entryPoint, 94 | KERNEL_V3_1, 95 | serializedSessionKey 96 | ); 97 | 98 | const kernelPaymaster = createZeroDevPaymasterClient({ 99 | chain: sepolia, 100 | transport: http(process.env.ZERODEV_RPC), 101 | }); 102 | const kernelClient = createKernelAccountClient({ 103 | account: sessionKeyAccount, 104 | chain: sepolia, 105 | bundlerTransport: http(process.env.ZERODEV_RPC), 106 | paymaster: { 107 | getPaymasterData(userOperation) { 108 | return kernelPaymaster.sponsorUserOperation({ userOperation }); 109 | }, 110 | }, 111 | }); 112 | 113 | const userOpHash = await kernelClient.sendUserOperation({ 114 | callData: await sessionKeyAccount.encodeCalls([ 115 | { 116 | to: zeroAddress, 117 | value: BigInt(0), 118 | data: "0x", 119 | }, 120 | ]), 121 | }); 122 | console.log("userOp hash:", userOpHash); 123 | 124 | await kernelClient.waitForUserOperationReceipt({ 125 | hash: userOpHash, 126 | }); 127 | console.log("UserOp completed!"); 128 | }; 129 | 130 | const main = async () => { 131 | const sessionPrivateKey = generatePrivateKey(); 132 | const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey); 133 | const sessionKeySigner = await toECDSASigner({ 134 | signer: sessionKeyAccount, 135 | }); 136 | 137 | // The owner creates a session key, serializes it, and shares it with the agent. 138 | const serializedSessionKey = await createSessionKey( 139 | sessionKeySigner, 140 | sessionPrivateKey 141 | ); 142 | 143 | // The agent reconstructs the session key using the serialized value 144 | await useSessionKey(serializedSessionKey); 145 | }; 146 | 147 | main(); 148 | -------------------------------------------------------------------------------- /session-keys/7702/1-click-trading.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { 8 | http, 9 | Hex, 10 | createPublicClient, 11 | zeroAddress, 12 | } from "viem"; 13 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 14 | import { sepolia } from "viem/chains"; 15 | import { toECDSASigner } from "@zerodev/permissions/signers"; 16 | import { 17 | ModularSigner, 18 | deserializePermissionAccount, 19 | serializePermissionAccount, 20 | toPermissionValidator, 21 | } from "@zerodev/permissions"; 22 | import { 23 | toSudoPolicy, 24 | } from "@zerodev/permissions/policies"; 25 | import { getEntryPoint, KERNEL_V3_3 } from "@zerodev/sdk/constants"; 26 | 27 | if ( 28 | !process.env.ZERODEV_RPC 29 | ) { 30 | throw new Error("ZERODEV_RPC is not set"); 31 | } 32 | 33 | const chain = sepolia; 34 | const ZERODEV_RPC = process.env.ZERODEV_RPC; 35 | const publicClient = createPublicClient({ 36 | chain, 37 | transport: http(ZERODEV_RPC), 38 | }); 39 | 40 | const privateKey = process.env.PRIVATE_KEY ? process.env.PRIVATE_KEY as Hex : generatePrivateKey(); 41 | const signer = privateKeyToAccount(privateKey); 42 | 43 | const entryPoint = getEntryPoint("0.7"); 44 | const kernelVersion = KERNEL_V3_3; 45 | 46 | 47 | const createSessionKey = async ( 48 | sessionKeySigner: ModularSigner, 49 | sessionPrivateKey: Hex 50 | ) => { 51 | 52 | const permissionPlugin = await toPermissionValidator(publicClient, { 53 | entryPoint, 54 | signer: sessionKeySigner, 55 | policies: [ 56 | // In this example, we are just using a sudo policy to allow everything. 57 | // In practice, you would want to set more restrictive policies. 58 | toSudoPolicy({}), 59 | ], 60 | kernelVersion, 61 | }); 62 | 63 | const sessionKeyAccount = await createKernelAccount(publicClient, { 64 | entryPoint, 65 | eip7702Account: signer, 66 | plugins: { 67 | regular: permissionPlugin, 68 | }, 69 | kernelVersion, 70 | address: signer.address, 71 | }); 72 | 73 | // Include the private key when you serialize the session key 74 | return await serializePermissionAccount(sessionKeyAccount, sessionPrivateKey); 75 | }; 76 | 77 | const useSessionKey = async (serializedSessionKey: string) => { 78 | const sessionKeyAccount = await deserializePermissionAccount( 79 | publicClient, 80 | entryPoint, 81 | KERNEL_V3_3, 82 | serializedSessionKey 83 | ); 84 | console.log("Session key account address:", sessionKeyAccount.address); 85 | 86 | const kernelPaymaster = createZeroDevPaymasterClient({ 87 | chain, 88 | transport: http(ZERODEV_RPC), 89 | }); 90 | const kernelClient = createKernelAccountClient({ 91 | account: sessionKeyAccount, 92 | chain, 93 | bundlerTransport: http(ZERODEV_RPC), 94 | paymaster: kernelPaymaster, 95 | }); 96 | 97 | const userOpHash = await kernelClient.sendUserOperation({ 98 | callData: await sessionKeyAccount.encodeCalls([ 99 | { 100 | to: zeroAddress, 101 | value: BigInt(0), 102 | data: "0x", 103 | }, 104 | ]), 105 | }); 106 | console.log("userOp hash:", userOpHash); 107 | 108 | const { receipt } = await kernelClient.waitForUserOperationReceipt({ 109 | hash: userOpHash, 110 | }); 111 | console.log( 112 | "UserOp completed", 113 | `${chain.blockExplorers.default.url}/tx/${receipt.transactionHash}` 114 | ); 115 | }; 116 | 117 | const main = async () => { 118 | const sessionPrivateKey = generatePrivateKey(); 119 | const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey); 120 | const sessionKeySigner = await toECDSASigner({ 121 | signer: sessionKeyAccount, 122 | }); 123 | 124 | // The owner creates a session key, serializes it, and shares it with the agent. 125 | const serializedSessionKey = await createSessionKey( 126 | sessionKeySigner, 127 | sessionPrivateKey 128 | ); 129 | 130 | // The agent reconstructs the session key using the serialized value 131 | await useSessionKey(serializedSessionKey); 132 | }; 133 | 134 | main().then(() => { 135 | console.log("Done"); 136 | process.exit(0); 137 | }); 138 | -------------------------------------------------------------------------------- /session-keys/7702/transaction-automation.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | addressToEmptyAccount, 7 | } from "@zerodev/sdk"; 8 | import { http, Hex, createPublicClient, Address, zeroAddress } from "viem"; 9 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 10 | import { sepolia } from "viem/chains"; 11 | import { toECDSASigner } from "@zerodev/permissions/signers"; 12 | import { toSudoPolicy } from "@zerodev/permissions/policies"; 13 | import { 14 | ModularSigner, 15 | deserializePermissionAccount, 16 | serializePermissionAccount, 17 | toPermissionValidator, 18 | } from "@zerodev/permissions"; 19 | import { getEntryPoint, KERNEL_V3_3 } from "@zerodev/sdk/constants"; 20 | 21 | if ( 22 | !process.env.ZERODEV_RPC || 23 | !process.env.PRIVATE_KEY 24 | ) { 25 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 26 | } 27 | 28 | const chain = sepolia; 29 | const ZERODEV_RPC = process.env.ZERODEV_RPC; 30 | 31 | const publicClient = createPublicClient({ 32 | transport: http(ZERODEV_RPC), 33 | chain, 34 | }); 35 | 36 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 37 | const entryPoint = getEntryPoint("0.7"); 38 | 39 | const getApproval = async (sessionKeyAddress: Address) => { 40 | 41 | // Create an "empty account" as the signer -- you only need the public 42 | // key (address) to do this. 43 | const emptyAccount = addressToEmptyAccount(sessionKeyAddress); 44 | const emptySessionKeySigner = await toECDSASigner({ signer: emptyAccount }); 45 | 46 | const permissionPlugin = await toPermissionValidator(publicClient, { 47 | entryPoint, 48 | signer: emptySessionKeySigner, 49 | policies: [ 50 | // In this example, we are just using a sudo policy to allow everything. 51 | // In practice, you would want to set more restrictive policies. 52 | toSudoPolicy({}), 53 | ], 54 | kernelVersion: KERNEL_V3_3, 55 | }); 56 | 57 | const sessionKeyAccount = await createKernelAccount(publicClient, { 58 | entryPoint, 59 | eip7702Account: signer, 60 | plugins: { 61 | regular: permissionPlugin, 62 | }, 63 | kernelVersion: KERNEL_V3_3, 64 | }); 65 | console.log("sessionKeyAccount", sessionKeyAccount.address) 66 | 67 | return await serializePermissionAccount(sessionKeyAccount); 68 | }; 69 | 70 | const useSessionKey = async ( 71 | approval: string, 72 | sessionKeySigner: ModularSigner 73 | ) => { 74 | const sessionKeyAccount = await deserializePermissionAccount( 75 | publicClient, 76 | entryPoint, 77 | KERNEL_V3_3, 78 | approval, 79 | sessionKeySigner 80 | ); 81 | console.log("sessionKeyAccount", sessionKeyAccount.address) 82 | const kernelPaymaster = createZeroDevPaymasterClient({ 83 | chain, 84 | transport: http(ZERODEV_RPC), 85 | }); 86 | const kernelClient = createKernelAccountClient({ 87 | account: sessionKeyAccount, 88 | chain, 89 | bundlerTransport: http(ZERODEV_RPC), 90 | paymaster: kernelPaymaster, 91 | }); 92 | 93 | const userOpHash = await kernelClient.sendUserOperation({ 94 | callData: await sessionKeyAccount.encodeCalls([ 95 | { 96 | to: zeroAddress, 97 | value: BigInt(0), 98 | data: "0x", 99 | }, 100 | ]), 101 | }); 102 | 103 | console.log("userOp hash:", userOpHash); 104 | 105 | const _receipt = await kernelClient.waitForUserOperationReceipt({ 106 | hash: userOpHash, 107 | }); 108 | console.log({ txHash: _receipt.receipt.transactionHash }); 109 | }; 110 | 111 | 112 | const main = async () => { 113 | // The agent creates a public-private key pair and sends 114 | // the public key (address) to the owner. 115 | const sessionPrivateKey = generatePrivateKey(); 116 | const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey); 117 | 118 | const sessionKeySigner = await toECDSASigner({ 119 | signer: sessionKeyAccount, 120 | }); 121 | 122 | // The owner approves the session key by signing its address and sending 123 | // back the signature 124 | const approval = await getApproval(sessionKeySigner.account.address); 125 | 126 | // The agent constructs a full session key 127 | await useSessionKey(approval, sessionKeySigner); 128 | 129 | }; 130 | 131 | main(); 132 | -------------------------------------------------------------------------------- /session-keys/revoke-session-key-with-session-key.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | addressToEmptyAccount, 7 | } from "@zerodev/sdk"; 8 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 9 | import { http, Hex, createPublicClient, Address, zeroAddress } from "viem"; 10 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 11 | import { sepolia } from "viem/chains"; 12 | import { toECDSASigner } from "@zerodev/permissions/signers"; 13 | import { toSudoPolicy } from "@zerodev/permissions/policies"; 14 | import { 15 | ModularSigner, 16 | deserializePermissionAccount, 17 | serializePermissionAccount, 18 | toPermissionValidator, 19 | } from "@zerodev/permissions"; 20 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 21 | 22 | if ( 23 | !process.env.ZERODEV_RPC || 24 | !process.env.PRIVATE_KEY 25 | ) { 26 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 27 | } 28 | 29 | const publicClient = createPublicClient({ 30 | transport: http(process.env.ZERODEV_RPC), 31 | chain: sepolia, 32 | }); 33 | 34 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 35 | const entryPoint = getEntryPoint("0.7"); 36 | 37 | const getApproval = async (sessionKeyAddress: Address) => { 38 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 39 | entryPoint, 40 | signer, 41 | kernelVersion: KERNEL_V3_1, 42 | }); 43 | 44 | // Create an "empty account" as the signer -- you only need the public 45 | // key (address) to do this. 46 | const emptyAccount = addressToEmptyAccount(sessionKeyAddress); 47 | const emptySessionKeySigner = await toECDSASigner({ signer: emptyAccount }); 48 | 49 | const permissionPlugin = await toPermissionValidator(publicClient, { 50 | entryPoint, 51 | signer: emptySessionKeySigner, 52 | policies: [ 53 | // In this example, we are just using a sudo policy to allow everything. 54 | // In practice, you would want to set more restrictive policies. 55 | toSudoPolicy({}), 56 | ], 57 | kernelVersion: KERNEL_V3_1, 58 | }); 59 | 60 | const sessionKeyAccount = await createKernelAccount(publicClient, { 61 | entryPoint, 62 | plugins: { 63 | sudo: ecdsaValidator, 64 | regular: permissionPlugin, 65 | }, 66 | kernelVersion: KERNEL_V3_1, 67 | }); 68 | 69 | return await serializePermissionAccount(sessionKeyAccount); 70 | }; 71 | 72 | const useSessionKey = async ( 73 | approval: string, 74 | sessionKeySigner: ModularSigner 75 | ) => { 76 | const sessionKeyAccount = await deserializePermissionAccount( 77 | publicClient, 78 | entryPoint, 79 | KERNEL_V3_1, 80 | approval, 81 | sessionKeySigner 82 | ); 83 | 84 | console.log("Session key account:", sessionKeyAccount.address); 85 | 86 | const kernelPaymaster = createZeroDevPaymasterClient({ 87 | chain: sepolia, 88 | transport: http(process.env.ZERODEV_RPC), 89 | }); 90 | const kernelClient = createKernelAccountClient({ 91 | account: sessionKeyAccount, 92 | chain: sepolia, 93 | bundlerTransport: http(process.env.ZERODEV_RPC), 94 | paymaster: { 95 | getPaymasterData(userOperation) { 96 | return kernelPaymaster.sponsorUserOperation({ userOperation }); 97 | }, 98 | }, 99 | }); 100 | 101 | const userOpHash = await kernelClient.sendUserOperation({ 102 | callData: await sessionKeyAccount.encodeCalls([ 103 | { 104 | to: zeroAddress, 105 | value: BigInt(0), 106 | data: "0x", 107 | }, 108 | ]), 109 | }); 110 | 111 | console.log("userOp hash:", userOpHash); 112 | 113 | const _receipt = await kernelClient.waitForUserOperationReceipt({ 114 | hash: userOpHash, 115 | timeout: 1000 * 60 * 5, 116 | }); 117 | console.log({ txHash: _receipt.receipt.transactionHash }); 118 | }; 119 | 120 | const revokeSessionKey = async ( 121 | sessionKeyAddress: Address, 122 | approval: string, 123 | sessionKeySigner: ModularSigner 124 | ) => { 125 | const sessionKeyAccount = await deserializePermissionAccount( 126 | publicClient, 127 | entryPoint, 128 | KERNEL_V3_1, 129 | approval, 130 | sessionKeySigner 131 | ); 132 | 133 | const kernelPaymaster = createZeroDevPaymasterClient({ 134 | chain: sepolia, 135 | transport: http(process.env.ZERODEV_RPC), 136 | }); 137 | const kernelClient = createKernelAccountClient({ 138 | account: sessionKeyAccount, 139 | chain: sepolia, 140 | bundlerTransport: http(process.env.ZERODEV_RPC), 141 | paymaster: { 142 | getPaymasterData(userOperation) { 143 | return kernelPaymaster.sponsorUserOperation({ userOperation }); 144 | }, 145 | }, 146 | }); 147 | 148 | const emptyAccount = addressToEmptyAccount(sessionKeyAddress); 149 | const emptySessionKeySigner = await toECDSASigner({ signer: emptyAccount }); 150 | 151 | const permissionPlugin = await toPermissionValidator(publicClient, { 152 | entryPoint, 153 | signer: emptySessionKeySigner, 154 | policies: [ 155 | // In this example, we are just using a sudo policy to allow everything. 156 | // In practice, you would want to set more restrictive policies. 157 | toSudoPolicy({}), 158 | ], 159 | kernelVersion: KERNEL_V3_1, 160 | }); 161 | 162 | const unInstallUserOpHash = await kernelClient.uninstallPlugin({ 163 | plugin: permissionPlugin, 164 | }); 165 | console.log("unInstallTx userOpHash:", unInstallUserOpHash); 166 | const receipt = await kernelClient.waitForUserOperationReceipt({ 167 | hash: unInstallUserOpHash, 168 | }); 169 | console.log("unInstallTx txHash:", receipt.receipt.transactionHash); 170 | }; 171 | 172 | const main = async () => { 173 | // The agent creates a public-private key pair and sends 174 | // the public key (address) to the owner. 175 | const sessionPrivateKey = generatePrivateKey(); 176 | const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey); 177 | 178 | const sessionKeySigner = await toECDSASigner({ 179 | signer: sessionKeyAccount, 180 | }); 181 | 182 | // The owner approves the session key by signing its address and sending 183 | // back the signature 184 | const approval = await getApproval(sessionKeySigner.account.address); 185 | 186 | // The agent constructs a full session key 187 | await useSessionKey(approval, sessionKeySigner); 188 | 189 | // revoke session key 190 | await revokeSessionKey( 191 | sessionKeySigner.account.address, 192 | approval, 193 | sessionKeySigner 194 | ); 195 | }; 196 | 197 | main(); 198 | -------------------------------------------------------------------------------- /session-keys/transaction-automation.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | addressToEmptyAccount, 7 | } from "@zerodev/sdk"; 8 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 9 | import { http, Hex, createPublicClient, Address, zeroAddress } from "viem"; 10 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 11 | import { sepolia } from "viem/chains"; 12 | import { toECDSASigner } from "@zerodev/permissions/signers"; 13 | import { toSudoPolicy } from "@zerodev/permissions/policies"; 14 | import { 15 | ModularSigner, 16 | deserializePermissionAccount, 17 | serializePermissionAccount, 18 | toPermissionValidator, 19 | } from "@zerodev/permissions"; 20 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 21 | 22 | if ( 23 | !process.env.ZERODEV_RPC || 24 | !process.env.PRIVATE_KEY 25 | ) { 26 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 27 | } 28 | 29 | const publicClient = createPublicClient({ 30 | transport: http(process.env.ZERODEV_RPC), 31 | chain: sepolia, 32 | }); 33 | 34 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 35 | const entryPoint = getEntryPoint("0.7"); 36 | 37 | const getApproval = async (sessionKeyAddress: Address) => { 38 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 39 | entryPoint, 40 | signer, 41 | kernelVersion: KERNEL_V3_1, 42 | }); 43 | 44 | // Create an "empty account" as the signer -- you only need the public 45 | // key (address) to do this. 46 | const emptyAccount = addressToEmptyAccount(sessionKeyAddress); 47 | const emptySessionKeySigner = await toECDSASigner({ signer: emptyAccount }); 48 | 49 | const permissionPlugin = await toPermissionValidator(publicClient, { 50 | entryPoint, 51 | signer: emptySessionKeySigner, 52 | policies: [ 53 | // In this example, we are just using a sudo policy to allow everything. 54 | // In practice, you would want to set more restrictive policies. 55 | toSudoPolicy({}), 56 | ], 57 | kernelVersion: KERNEL_V3_1, 58 | }); 59 | 60 | const sessionKeyAccount = await createKernelAccount(publicClient, { 61 | entryPoint, 62 | plugins: { 63 | sudo: ecdsaValidator, 64 | regular: permissionPlugin, 65 | }, 66 | kernelVersion: KERNEL_V3_1, 67 | }); 68 | 69 | return await serializePermissionAccount(sessionKeyAccount); 70 | }; 71 | 72 | const useSessionKey = async ( 73 | approval: string, 74 | sessionKeySigner: ModularSigner 75 | ) => { 76 | const sessionKeyAccount = await deserializePermissionAccount( 77 | publicClient, 78 | entryPoint, 79 | KERNEL_V3_1, 80 | approval, 81 | sessionKeySigner 82 | ); 83 | 84 | const kernelPaymaster = createZeroDevPaymasterClient({ 85 | chain: sepolia, 86 | transport: http(process.env.ZERODEV_RPC), 87 | }); 88 | const kernelClient = createKernelAccountClient({ 89 | account: sessionKeyAccount, 90 | chain: sepolia, 91 | bundlerTransport: http(process.env.ZERODEV_RPC), 92 | paymaster: { 93 | getPaymasterData(userOperation) { 94 | return kernelPaymaster.sponsorUserOperation({ userOperation }); 95 | }, 96 | }, 97 | }); 98 | 99 | const userOpHash = await kernelClient.sendUserOperation({ 100 | callData: await sessionKeyAccount.encodeCalls([ 101 | { 102 | to: zeroAddress, 103 | value: BigInt(0), 104 | data: "0x", 105 | }, 106 | ]), 107 | }); 108 | 109 | console.log("userOp hash:", userOpHash); 110 | 111 | const _receipt = await kernelClient.waitForUserOperationReceipt({ 112 | hash: userOpHash, 113 | }); 114 | console.log({ txHash: _receipt.receipt.transactionHash }); 115 | }; 116 | 117 | const revokeSessionKey = async (sessionKeyAddress: Address) => { 118 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 119 | entryPoint, 120 | signer, 121 | kernelVersion: KERNEL_V3_1, 122 | }); 123 | const sudoAccount = await createKernelAccount(publicClient, { 124 | plugins: { 125 | sudo: ecdsaValidator, 126 | }, 127 | entryPoint, 128 | kernelVersion: KERNEL_V3_1, 129 | }); 130 | 131 | const kernelPaymaster = createZeroDevPaymasterClient({ 132 | chain: sepolia, 133 | transport: http(process.env.ZERODEV_RPC), 134 | }); 135 | const sudoKernelClient = createKernelAccountClient({ 136 | account: sudoAccount, 137 | chain: sepolia, 138 | bundlerTransport: http(process.env.ZERODEV_RPC), 139 | paymaster: kernelPaymaster, 140 | }); 141 | 142 | const emptyAccount = addressToEmptyAccount(sessionKeyAddress); 143 | const emptySessionKeySigner = await toECDSASigner({ signer: emptyAccount }); 144 | 145 | const permissionPlugin = await toPermissionValidator(publicClient, { 146 | entryPoint, 147 | signer: emptySessionKeySigner, 148 | policies: [ 149 | // In this example, we are just using a sudo policy to allow everything. 150 | // In practice, you would want to set more restrictive policies. 151 | toSudoPolicy({}), 152 | ], 153 | kernelVersion: KERNEL_V3_1, 154 | }); 155 | 156 | const unInstallUserOpHash = await sudoKernelClient.uninstallPlugin({ 157 | plugin: permissionPlugin, 158 | }); 159 | console.log({ unInstallUserOpHash }); 160 | const txReceipt = await sudoKernelClient.waitForUserOperationReceipt({ 161 | hash: unInstallUserOpHash, 162 | }); 163 | console.log({ unInstallTxHash: txReceipt.receipt.transactionHash }); 164 | }; 165 | 166 | const main = async () => { 167 | // The agent creates a public-private key pair and sends 168 | // the public key (address) to the owner. 169 | const sessionPrivateKey = generatePrivateKey(); 170 | const sessionKeyAccount = privateKeyToAccount(sessionPrivateKey); 171 | 172 | const sessionKeySigner = await toECDSASigner({ 173 | signer: sessionKeyAccount, 174 | }); 175 | 176 | // The owner approves the session key by signing its address and sending 177 | // back the signature 178 | const approval = await getApproval(sessionKeySigner.account.address); 179 | 180 | // The agent constructs a full session key 181 | await useSessionKey(approval, sessionKeySigner); 182 | 183 | // revoke session key 184 | await revokeSessionKey(sessionKeySigner.account.address); 185 | 186 | process.exit(0); 187 | }; 188 | 189 | main(); 190 | -------------------------------------------------------------------------------- /session-keys/v2-old/agent-created.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | addressToEmptyAccount, 7 | } from "@zerodev/sdk"; 8 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 9 | import { 10 | signerToSessionKeyValidator, 11 | ParamOperator, 12 | serializeSessionKeyAccount, 13 | deserializeSessionKeyAccount, 14 | oneAddress, 15 | } from "@zerodev/session-key"; 16 | import { 17 | http, 18 | Hex, 19 | createPublicClient, 20 | parseAbi, 21 | encodeFunctionData, 22 | Address, 23 | } from "viem"; 24 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 25 | import { sepolia } from "viem/chains"; 26 | import { getEntryPoint, KERNEL_V2_4 } from "@zerodev/sdk/constants"; 27 | 28 | if ( 29 | !process.env.ZERODEV_RPC || 30 | !process.env.PRIVATE_KEY 31 | ) { 32 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 33 | } 34 | 35 | const publicClient = createPublicClient({ 36 | transport: http(process.env.ZERODEV_RPC), 37 | chain: sepolia, 38 | }); 39 | 40 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 41 | const contractAddress = "0x34bE7f35132E97915633BC1fc020364EA5134863"; 42 | const contractABI = parseAbi([ 43 | "function mint(address _to) public", 44 | "function balanceOf(address owner) external view returns (uint256 balance)", 45 | ]); 46 | const entryPoint = getEntryPoint("0.6"); 47 | 48 | const createSessionKey = async (sessionKeyAddress: Address) => { 49 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 50 | entryPoint, 51 | signer, 52 | kernelVersion: KERNEL_V2_4, 53 | }); 54 | 55 | const masterAccount = await createKernelAccount(publicClient, { 56 | entryPoint, 57 | plugins: { 58 | sudo: ecdsaValidator, 59 | }, 60 | kernelVersion: KERNEL_V2_4, 61 | }); 62 | console.log("Account address:", masterAccount.address); 63 | 64 | // Create an "empty account" as the signer -- you only need the public 65 | // key (address) to do this. 66 | const emptySessionKeySigner = addressToEmptyAccount(sessionKeyAddress); 67 | 68 | const sessionKeyValidator = await signerToSessionKeyValidator(publicClient, { 69 | entryPoint, 70 | signer: emptySessionKeySigner, 71 | validatorData: { 72 | paymaster: oneAddress, 73 | permissions: [ 74 | { 75 | target: contractAddress, 76 | // Maximum value that can be transferred. In this case we 77 | // set it to zero so that no value transfer is possible. 78 | valueLimit: BigInt(0), 79 | // Contract abi 80 | abi: contractABI, 81 | // Function name 82 | functionName: "mint", 83 | // An array of conditions, each corresponding to an argument for 84 | // the function. 85 | args: [ 86 | { 87 | // In this case, we are saying that the session key can only mint 88 | // NFTs to the account itself 89 | operator: ParamOperator.EQUAL, 90 | value: masterAccount.address, 91 | }, 92 | ], 93 | }, 94 | ], 95 | }, 96 | kernelVersion: KERNEL_V2_4, 97 | }); 98 | 99 | const sessionKeyAccount = await createKernelAccount(publicClient, { 100 | entryPoint, 101 | plugins: { 102 | sudo: ecdsaValidator, 103 | regular: sessionKeyValidator, 104 | }, 105 | kernelVersion: KERNEL_V2_4, 106 | }); 107 | 108 | return await serializeSessionKeyAccount(sessionKeyAccount); 109 | }; 110 | 111 | const useSessionKey = async ( 112 | serializedSessionKey: string, 113 | sessionKeySigner: any 114 | ) => { 115 | const sessionKeyAccount = await deserializeSessionKeyAccount( 116 | publicClient, 117 | entryPoint, 118 | KERNEL_V2_4, 119 | serializedSessionKey, 120 | sessionKeySigner 121 | ); 122 | 123 | const kernelPaymaster = createZeroDevPaymasterClient({ 124 | chain: sepolia, 125 | transport: http(process.env.ZERODEV_RPC), 126 | }); 127 | const kernelClient = createKernelAccountClient({ 128 | account: sessionKeyAccount, 129 | chain: sepolia, 130 | bundlerTransport: http(process.env.ZERODEV_RPC), 131 | paymaster: { 132 | getPaymasterData(userOperation) { 133 | return kernelPaymaster.sponsorUserOperation({ userOperation }); 134 | }, 135 | }, 136 | }); 137 | 138 | const userOpHash = await kernelClient.sendUserOperation({ 139 | callData: await sessionKeyAccount.encodeCalls([ 140 | { 141 | to: contractAddress, 142 | value: BigInt(0), 143 | data: encodeFunctionData({ 144 | abi: contractABI, 145 | functionName: "mint", 146 | args: [sessionKeyAccount.address], 147 | }), 148 | }, 149 | ]), 150 | }); 151 | 152 | console.log("userOp hash:", userOpHash); 153 | }; 154 | 155 | const main = async () => { 156 | // The agent creates a public-private key pair and sends 157 | // the public key (address) to the owner. 158 | const sessionPrivateKey = generatePrivateKey(); 159 | const sessionKeySigner = privateKeyToAccount(sessionPrivateKey); 160 | 161 | // The owner authorizes the public key by signing it and sending 162 | // back the signature 163 | const serializedSessionKey = await createSessionKey(sessionKeySigner.address); 164 | 165 | // The agent constructs a full session key 166 | await useSessionKey(serializedSessionKey, sessionKeySigner); 167 | }; 168 | 169 | main(); 170 | -------------------------------------------------------------------------------- /session-keys/v2-old/owner-created.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { 3 | createKernelAccount, 4 | createZeroDevPaymasterClient, 5 | createKernelAccountClient, 6 | } from "@zerodev/sdk"; 7 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 8 | import { 9 | signerToSessionKeyValidator, 10 | ParamOperator, 11 | serializeSessionKeyAccount, 12 | deserializeSessionKeyAccount, 13 | oneAddress, 14 | } from "@zerodev/session-key"; 15 | import { 16 | http, 17 | Hex, 18 | createPublicClient, 19 | parseAbi, 20 | encodeFunctionData, 21 | } from "viem"; 22 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 23 | import { sepolia } from "viem/chains"; 24 | import { getEntryPoint, KERNEL_V2_4 } from "@zerodev/sdk/constants"; 25 | 26 | if ( 27 | !process.env.ZERODEV_RPC || 28 | !process.env.PRIVATE_KEY 29 | ) { 30 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 31 | } 32 | 33 | const publicClient = createPublicClient({ 34 | chain: sepolia, 35 | transport: http(process.env.ZERODEV_RPC), 36 | }); 37 | 38 | const signer = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 39 | const contractAddress = "0x34bE7f35132E97915633BC1fc020364EA5134863"; 40 | const contractABI = parseAbi([ 41 | "function mint(address _to) public", 42 | "function balanceOf(address owner) external view returns (uint256 balance)", 43 | ]); 44 | const sessionPrivateKey = generatePrivateKey(); 45 | const sessionKeySigner = privateKeyToAccount(sessionPrivateKey); 46 | 47 | const entryPoint = getEntryPoint("0.6"); 48 | const createSessionKey = async () => { 49 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 50 | entryPoint, 51 | signer, 52 | kernelVersion: KERNEL_V2_4, 53 | }); 54 | 55 | const masterAccount = await createKernelAccount(publicClient, { 56 | entryPoint, 57 | plugins: { 58 | sudo: ecdsaValidator, 59 | }, 60 | kernelVersion: KERNEL_V2_4, 61 | }); 62 | console.log("Account address:", masterAccount.address); 63 | 64 | const sessionKeyValidator = await signerToSessionKeyValidator(publicClient, { 65 | entryPoint, 66 | signer: sessionKeySigner, 67 | validatorData: { 68 | paymaster: oneAddress, 69 | permissions: [ 70 | { 71 | target: contractAddress, 72 | // Maximum value that can be transferred. In this case we 73 | // set it to zero so that no value transfer is possible. 74 | valueLimit: BigInt(0), 75 | // Contract abi 76 | abi: contractABI, 77 | // Function name 78 | functionName: "mint", 79 | // An array of conditions, each corresponding to an argument for 80 | // the function. 81 | args: [ 82 | { 83 | // In this case, we are saying that the session key can only mint 84 | // NFTs to the account itself 85 | operator: ParamOperator.EQUAL, 86 | value: masterAccount.address, 87 | }, 88 | ], 89 | }, 90 | ], 91 | }, 92 | kernelVersion: KERNEL_V2_4, 93 | }); 94 | 95 | const sessionKeyAccount = await createKernelAccount(publicClient, { 96 | entryPoint, 97 | plugins: { 98 | sudo: ecdsaValidator, 99 | regular: sessionKeyValidator, 100 | }, 101 | kernelVersion: KERNEL_V2_4, 102 | }); 103 | 104 | // Include the private key when you serialize the session key 105 | return await serializeSessionKeyAccount(sessionKeyAccount, sessionPrivateKey); 106 | }; 107 | 108 | const useSessionKey = async (serializedSessionKey: string) => { 109 | const sessionKeyAccount = await deserializeSessionKeyAccount( 110 | publicClient, 111 | entryPoint, 112 | KERNEL_V2_4, 113 | serializedSessionKey 114 | ); 115 | 116 | const kernelPaymaster = createZeroDevPaymasterClient({ 117 | chain: sepolia, 118 | transport: http(process.env.ZERODEV_RPC), 119 | }); 120 | const kernelClient = createKernelAccountClient({ 121 | account: sessionKeyAccount, 122 | chain: sepolia, 123 | bundlerTransport: http(process.env.ZERODEV_RPC), 124 | paymaster: { 125 | getPaymasterData(userOperation) { 126 | return kernelPaymaster.sponsorUserOperation({ userOperation }); 127 | }, 128 | }, 129 | }); 130 | 131 | const userOpHash = await kernelClient.sendUserOperation({ 132 | callData: await sessionKeyAccount.encodeCalls([ 133 | { 134 | to: contractAddress, 135 | value: BigInt(0), 136 | data: encodeFunctionData({ 137 | abi: contractABI, 138 | functionName: "mint", 139 | args: [sessionKeyAccount.address], 140 | }), 141 | }, 142 | ]), 143 | }); 144 | 145 | console.log("userOp hash:", userOpHash); 146 | }; 147 | 148 | const main = async () => { 149 | // The owner creates a session key, serializes it, and shares it with the agent. 150 | const serializedSessionKey = await createSessionKey(); 151 | 152 | // The agent reconstructs the session key using the serialized value 153 | await useSessionKey(serializedSessionKey); 154 | }; 155 | 156 | main(); 157 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./lib", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "include": ["./**/*.ts"] 11 | } -------------------------------------------------------------------------------- /tutorial/completed.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { createPublicClient, encodeFunctionData, http, parseAbi } from "viem"; 3 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 4 | import { sepolia } from "viem/chains"; 5 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 6 | import { 7 | createKernelAccount, 8 | createKernelAccountClient, 9 | createZeroDevPaymasterClient, 10 | } from "@zerodev/sdk"; 11 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 12 | 13 | if (!process.env.ZERODEV_RPC) { 14 | throw new Error("ZERODEV_RPC is not set"); 15 | } 16 | 17 | const ZERODEV_RPC = process.env.ZERODEV_RPC 18 | 19 | // The NFT contract we will be interacting with 20 | const contractAddress = "0x34bE7f35132E97915633BC1fc020364EA5134863"; 21 | const contractABI = parseAbi([ 22 | "function mint(address _to) public", 23 | "function balanceOf(address owner) external view returns (uint256 balance)", 24 | ]); 25 | 26 | // Construct a public client 27 | const chain = sepolia; 28 | const publicClient = createPublicClient({ 29 | transport: http(ZERODEV_RPC), 30 | chain, 31 | }); 32 | const entryPoint = getEntryPoint("0.7"); 33 | 34 | const main = async () => { 35 | // Construct a signer 36 | const privateKey = generatePrivateKey(); 37 | const signer = privateKeyToAccount(privateKey); 38 | 39 | // Construct a validator 40 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 41 | signer, 42 | entryPoint, 43 | kernelVersion: KERNEL_V3_1, 44 | }); 45 | 46 | // Construct a Kernel account 47 | const account = await createKernelAccount(publicClient, { 48 | entryPoint, 49 | plugins: { 50 | sudo: ecdsaValidator, 51 | }, 52 | kernelVersion: KERNEL_V3_1, 53 | }); 54 | 55 | const zerodevPaymaster = createZeroDevPaymasterClient({ 56 | chain, 57 | transport: http(ZERODEV_RPC), 58 | }); 59 | 60 | // Construct a Kernel account client 61 | const kernelClient = createKernelAccountClient({ 62 | account, 63 | chain, 64 | bundlerTransport: http(ZERODEV_RPC), 65 | paymaster: { 66 | getPaymasterData(userOperation) { 67 | return zerodevPaymaster.sponsorUserOperation({ userOperation }); 68 | }, 69 | }, 70 | }); 71 | 72 | const accountAddress = kernelClient.account.address; 73 | console.log("My account:", accountAddress); 74 | 75 | // Send a UserOp 76 | const userOpHash = await kernelClient.sendUserOperation({ 77 | callData: await kernelClient.account.encodeCalls([ 78 | { 79 | to: contractAddress, 80 | value: BigInt(0), 81 | data: encodeFunctionData({ 82 | abi: contractABI, 83 | functionName: "mint", 84 | args: [accountAddress], 85 | }), 86 | }, 87 | ]), 88 | }); 89 | console.log("Submitted UserOp:", userOpHash); 90 | 91 | // Wait for the UserOp to be included on-chain 92 | const receipt = await kernelClient.waitForUserOperationReceipt({ 93 | hash: userOpHash, 94 | }); 95 | console.log("UserOp confirmed:", receipt.userOpHash); 96 | console.log("TxHash:", receipt.receipt.transactionHash); 97 | 98 | // Print NFT balance 99 | const nftBalance = await publicClient.readContract({ 100 | address: contractAddress, 101 | abi: contractABI, 102 | functionName: "balanceOf", 103 | args: [accountAddress], 104 | }); 105 | console.log(`NFT balance: ${nftBalance}`); 106 | 107 | process.exit(0); 108 | }; 109 | 110 | main(); 111 | -------------------------------------------------------------------------------- /tutorial/template.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { createPublicClient, encodeFunctionData, http, parseAbi } from "viem"; 3 | import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; 4 | import { sepolia } from "viem/chains"; 5 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 6 | import { 7 | createKernelAccount, 8 | createKernelAccountClient, 9 | createZeroDevPaymasterClient, 10 | } from "@zerodev/sdk"; 11 | import { getEntryPoint } from "@zerodev/sdk/constants"; 12 | 13 | if (!process.env.ZERODEV_RPC) { 14 | throw new Error("ZERODEV_RPC is not set"); 15 | } 16 | 17 | const ZERODEV_RPC = process.env.ZERODEV_RPC; 18 | 19 | // The NFT contract we will be interacting with 20 | const contractAddress = "0x34bE7f35132E97915633BC1fc020364EA5134863"; 21 | const contractABI = parseAbi([ 22 | "function mint(address _to) public", 23 | "function balanceOf(address owner) external view returns (uint256 balance)", 24 | ]); 25 | 26 | // Construct a public client 27 | const publicClient = createPublicClient({ 28 | transport: http(ZERODEV_RPC), 29 | }); 30 | 31 | const chain = sepolia; 32 | const entryPoint = getEntryPoint("0.7"); 33 | 34 | const main = async () => { 35 | // your code goes here 36 | }; 37 | 38 | main(); 39 | -------------------------------------------------------------------------------- /utils.ts: -------------------------------------------------------------------------------- 1 | // Utilities for examples 2 | 3 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 4 | import { 5 | KernelSmartAccountV1Implementation, createKernelAccount, 6 | createKernelAccountClient, 7 | createKernelAccountV1, 8 | createZeroDevPaymasterClient, 9 | getUserOperationGasPrice 10 | } from "@zerodev/sdk"; 11 | import { getEntryPoint } from "@zerodev/sdk/constants"; 12 | import { GetKernelVersion } from "@zerodev/sdk/types"; 13 | import { Hex, createPublicClient, http } from "viem"; 14 | import { privateKeyToAccount } from "viem/accounts"; 15 | import { sepolia } from "viem/chains"; 16 | import { 17 | EntryPointVersion, 18 | PaymasterActions, 19 | SmartAccount, 20 | } from "viem/account-abstraction"; 21 | 22 | const privateKey = process.env.PRIVATE_KEY; 23 | if (!process.env.ZERODEV_RPC || !privateKey) { 24 | throw new Error("ZERODEV_RPC or PRIVATE_KEY is not set"); 25 | } 26 | 27 | const signer = privateKeyToAccount(privateKey as Hex); 28 | const chain = sepolia; 29 | const publicClient = createPublicClient({ 30 | transport: http(process.env.ZERODEV_RPC), 31 | chain, 32 | }); 33 | 34 | export const getKernelClient = async < 35 | entryPointVersion extends EntryPointVersion 36 | >( 37 | entryPointVersion_: entryPointVersion, 38 | kernelVersion: GetKernelVersion 39 | ) => { 40 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 41 | signer, 42 | entryPoint: getEntryPoint(entryPointVersion_), 43 | kernelVersion, 44 | }); 45 | 46 | const account = await createKernelAccount(publicClient, { 47 | plugins: { 48 | sudo: ecdsaValidator, 49 | }, 50 | entryPoint: getEntryPoint(entryPointVersion_), 51 | kernelVersion, 52 | }); 53 | console.log("My account:", account.address); 54 | const paymasterClient = createZeroDevPaymasterClient({ 55 | chain, 56 | transport: http(process.env.ZERODEV_RPC), 57 | }); 58 | return createKernelAccountClient({ 59 | account, 60 | chain, 61 | bundlerTransport: http(process.env.ZERODEV_RPC), 62 | paymaster: { 63 | getPaymasterData: (userOperation) => { 64 | return paymasterClient.sponsorUserOperation({ 65 | userOperation, 66 | }) 67 | } 68 | }, 69 | client: publicClient, 70 | }); 71 | }; 72 | 73 | export const getKernelV1Account = async () => { 74 | const privateKey = process.env.PRIVATE_KEY as Hex; 75 | if (!privateKey) { 76 | throw new Error("PRIVATE_KEY environment variable not set"); 77 | } 78 | 79 | if (!process.env.ZERODEV_RPC) { 80 | throw new Error("ZERODEV_RPC environment variable not set"); 81 | } 82 | 83 | const publicClient = createPublicClient({ 84 | transport: http(process.env.ZERODEV_RPC), 85 | chain, 86 | }); 87 | const signer = privateKeyToAccount(privateKey); 88 | 89 | return createKernelAccountV1(publicClient, { 90 | signer, 91 | index: BigInt(0), 92 | entryPoint: getEntryPoint("0.6"), 93 | }); 94 | }; 95 | 96 | export const getKernelV1AccountClient = async ({ 97 | account, 98 | paymaster, 99 | }: { 100 | paymaster?: { 101 | /** Retrieves paymaster-related User Operation properties to be used for sending the User Operation. */ 102 | getPaymasterData?: PaymasterActions["getPaymasterData"] | undefined; 103 | /** Retrieves paymaster-related User Operation properties to be used for gas estimation. */ 104 | getPaymasterStubData?: PaymasterActions["getPaymasterStubData"] | undefined; 105 | }; 106 | account: SmartAccount; 107 | }) => { 108 | if (!process.env.ZERODEV_RPC) { 109 | throw new Error("ZERODEV_RPC environment variable not set"); 110 | } 111 | return createKernelAccountClient({ 112 | account, 113 | chain, 114 | bundlerTransport: http(process.env.ZERODEV_RPC), 115 | paymaster, 116 | }); 117 | }; 118 | 119 | export const getZeroDevPaymasterClient = () => { 120 | if (!process.env.ZERODEV_RPC) 121 | throw new Error("ZERODEV_RPC environment variable not set"); 122 | 123 | return createZeroDevPaymasterClient({ 124 | chain, 125 | transport: http(process.env.ZERODEV_RPC), 126 | }); 127 | }; 128 | 129 | export const getZeroDevERC20PaymasterClient = () => { 130 | if (!process.env.ZERODEV_RPC) 131 | throw new Error("ZERODEV_RPC environment variable not set"); 132 | 133 | return createZeroDevPaymasterClient({ 134 | chain, 135 | transport: http(process.env.ZERODEV_RPC), 136 | }); 137 | }; 138 | -------------------------------------------------------------------------------- /validate-signature/validate-signature.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants"; 3 | import { createKernelAccount, verifyEIP6492Signature } from "@zerodev/sdk"; 4 | import { createPublicClient, hashMessage, Hex, http } from "viem"; 5 | import { privateKeyToAccount } from "viem/accounts"; 6 | import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator"; 7 | 8 | const entryPoint = getEntryPoint("0.7"); 9 | 10 | const privateKey = process.env.PRIVATE_KEY; 11 | if (!privateKey || !process.env.ZERODEV_RPC) { 12 | throw new Error("PRIVATE_KEY or ZERODEV_RPC is not set"); 13 | } 14 | 15 | const signer = privateKeyToAccount(privateKey as Hex); 16 | 17 | const kernelVersion = KERNEL_V3_1; 18 | 19 | const publicClient = createPublicClient({ 20 | transport: http(process.env.ZERODEV_RPC), 21 | }); 22 | 23 | async function main() { 24 | const ecdsaValidator = await signerToEcdsaValidator(publicClient, { 25 | signer, 26 | entryPoint, 27 | kernelVersion, 28 | }); 29 | 30 | const account = await createKernelAccount(publicClient, { 31 | plugins: { 32 | sudo: ecdsaValidator, 33 | }, 34 | entryPoint, 35 | kernelVersion, 36 | }); 37 | 38 | console.log("Account address:", account.address); 39 | 40 | const signature = await account.signMessage({ 41 | message: "hello world", 42 | }); 43 | 44 | console.log( 45 | await verifyEIP6492Signature({ 46 | signer: account.address, // your smart account address 47 | hash: hashMessage("hello world"), 48 | signature: signature, 49 | client: publicClient, 50 | }) 51 | ); 52 | } 53 | 54 | main(); 55 | --------------------------------------------------------------------------------