From 0129eb124cb401a20b3a40d0ae65b310f222c2ec Mon Sep 17 00:00:00 2001 From: Natalie Bravo Date: Fri, 19 Jul 2024 15:58:39 -0300 Subject: [PATCH] add first steps for the zksync minimal account abstraction impl --- .gitmodules | 6 ++ README.md | 8 +- foundry.toml | 7 +- lib/foundry-devops | 1 + lib/foundry-era-contracts | 1 + package.json | 16 +++ src/zksync/zkMinimalAccount.sol | 164 +++++++++++++++++++++++++++++ test/zksync/ZkMinimalAccount.t.sol | 121 +++++++++++++++++++++ ts-scripts/DeployZkMinimal.ts | 87 +++++++++++++++ ts-scripts/EncryptKey.ts | 18 ++++ ts-scripts/SendAATx.ts | 144 +++++++++++++++++++++++++ yarn.lock | 123 ++++++++++++++++++++++ 12 files changed, 694 insertions(+), 2 deletions(-) create mode 160000 lib/foundry-devops create mode 160000 lib/foundry-era-contracts create mode 100644 package.json create mode 100644 src/zksync/zkMinimalAccount.sol create mode 100644 test/zksync/ZkMinimalAccount.t.sol create mode 100644 ts-scripts/DeployZkMinimal.ts create mode 100644 ts-scripts/EncryptKey.ts create mode 100644 ts-scripts/SendAATx.ts create mode 100644 yarn.lock diff --git a/.gitmodules b/.gitmodules index 1616988..432340a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/foundry-era-contracts"] + path = lib/foundry-era-contracts + url = https://github.com/Cyfrin/foundry-era-contracts +[submodule "lib/foundry-devops"] + path = lib/foundry-devops + url = https://github.com/Cyfrin/foundry-devops diff --git a/README.md b/README.md index 311c8b4..9003361 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,10 @@ 1. Create a basic AA on Eth 2. Create a basic AA on zksync -3. deploy, and send a userOp tx \ No newline at end of file + +PS.: Foundry solidity scripts for zksync does not work 100%, therefore the `ts-scripts` was created. + +## TODO +- [ ] add paymaster logic +- [ ] add spend threshold +- [ ] sign the tx with github/google session key diff --git a/foundry.toml b/foundry.toml index 8739436..26c71de 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,5 +3,10 @@ src = "src" out = "out" libs = ["lib"] remappings = ["@openzeppelin/contracts=lib/openzeppelin-contracts/contracts"] - +is-system = true +via-ir = true +fs_permissions = [ + { access = "read", path = "./broadcast" }, + { access = "read", path = "./reports" }, +] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/foundry-devops b/lib/foundry-devops new file mode 160000 index 0000000..df9f90b --- /dev/null +++ b/lib/foundry-devops @@ -0,0 +1 @@ +Subproject commit df9f90b490423578142b5dd50752db9427efb2ac diff --git a/lib/foundry-era-contracts b/lib/foundry-era-contracts new file mode 160000 index 0000000..3f99de4 --- /dev/null +++ b/lib/foundry-era-contracts @@ -0,0 +1 @@ +Subproject commit 3f99de4a37b126c5cb0466067f37be0c932167b2 diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a572c3 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "@types/fs-extra": "^11.0.4", + "dotenv": "^16.4.5", + "ethers": "6", + "fs-extra": "^11.2.0", + "typescript": "^5.4.5", + "zksync-ethers": "^6.8.0" + }, + "scripts": { + "encryptKey": "ts-node ts-scripts/EncryptKey.ts", + "deploy": "ts-node ts-scripts/DeployZkMinimal.ts", + "sendTx": "ts-node ts-scripts/SendAATx.ts", + "compile": "forge build --zksync" + } +} \ No newline at end of file diff --git a/src/zksync/zkMinimalAccount.sol b/src/zksync/zkMinimalAccount.sol new file mode 100644 index 0000000..d8f1010 --- /dev/null +++ b/src/zksync/zkMinimalAccount.sol @@ -0,0 +1,164 @@ +// SPDX-Licence-Identifier: MIT +pragma solidity 0.8.24; + +// zkSync Era Imports +import { + IAccount, + ACCOUNT_VALIDATION_SUCCESS_MAGIC +} from "lib/foundry-era-contracts/src/system-contracts/contracts/interfaces/IAccount.sol"; +import { + Transaction, + MemoryTransactionHelper +} from "lib/foundry-era-contracts/src/system-contracts/contracts/libraries/MemoryTransactionHelper.sol"; +import {SystemContractsCaller} from + "lib/foundry-era-contracts/src/system-contracts/contracts/libraries/SystemContractsCaller.sol"; +import { + NONCE_HOLDER_SYSTEM_CONTRACT, + BOOTLOADER_FORMAL_ADDRESS, + DEPLOYER_SYSTEM_CONTRACT +} from "lib/foundry-era-contracts/src/system-contracts/contracts/Constants.sol"; +import {INonceHolder} from "lib/foundry-era-contracts/src/system-contracts/contracts/interfaces/INonceHolder.sol"; +import {Utils} from "lib/foundry-era-contracts/src/system-contracts/contracts/libraries/Utils.sol"; + +// OZ Imports +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * Lifecycle of a type 113 (0x71) transaction + * msg.sender is the bootloader system contract + * + * Phase 1 Validation (light node) + * 1. The user sends the transaction to the "zkSync API client" (sort of a "light node") + * 2. The zkSync API client checks to see the the nonce is unique by querying the NonceHolder system contract + * 3. The zkSync API client calls validateTransaction, which MUST update the nonce + * 4. The zkSync API client checks the nonce is updated + * 5. The zkSync API client calls payForTransaction, or prepareForPaymaster & validateAndPayForPaymasterTransaction + * 6. The zkSync API client verifies that the bootloader gets paid + * + * Phase 2 Execution (main node) + * 7. The zkSync API client passes the validated transaction to the main node / sequencer (as of today, they are the same) + * 8. The main node calls executeTransaction + * 9. If a paymaster was used, the postTransaction is called + */ +contract ZkMinimalAccount is IAccount, Ownable { + using MemoryTransactionHelper for Transaction; + + //////////////////// ERRORS //////////////////////// + error ZkMinimalAccount__NotEnoughBalance(); + error ZkMinimalAccount__NotFromBootLoader(); + error ZkMinimalAccount__ExecutionFailed(); + error ZkMinimalAccount__NotFromBootLoaderOrOwner(); + error ZkMinimalAccount__FailedToPay(); + error ZkMinimalAccount__InvalidSignature(); + + + //////////////////// MODIFIERS //////////////////////// + modifier requireFromBootLoader() { + if (msg.sender != BOOTLOADER_FORMAL_ADDRESS) { + revert ZkMinimalAccount__NotFromBootLoader(); + } + _; + } + + modifier requireFromBootLoaderOrOwner() { + if (msg.sender != BOOTLOADER_FORMAL_ADDRESS && msg.sender != owner()) { + revert ZkMinimalAccount__NotFromBootLoaderOrOwner(); + } + _; + } + + + //////////////////// EXTERNAL FUNCTIONS //////////////////////// + constructor() Ownable(msg.sender) {} + + receive() external payable {} + + /** + * @notice Since this version does not have a paymaster, we need to check if there's enough balance in our account + */ + function validateTransaction(bytes32 /*_txHash*/, bytes32 /*_suggestedSignedHash*/, Transaction memory _transaction) external payable requireFromBootLoader returns (bytes4 magic){ + return _validateTransaction(_transaction); + } + + /** + * @notice only the bootloader can call this function. The paymaster is going to pay for the gas. + */ + function executeTransaction(bytes32 /*_txHash*/, bytes32 /*_suggestedSignedHash*/, Transaction memory _transaction) external requireFromBootLoaderOrOwner payable { + return _executeTransaction(_transaction); + } + + /** + * @notice we can sign a transaction and allow anyone to call this function. Who is calling is going to pay for the gas. + */ + function executeTransactionFromOutside(Transaction memory _transaction) external payable { + bytes4 magic = _validateTransaction(_transaction); + if (magic != ACCOUNT_VALIDATION_SUCCESS_MAGIC) { + revert ZkMinimalAccount__InvalidSignature(); + } + _executeTransaction(_transaction); + } + + function payForTransaction(bytes32 /*_txHash*/, bytes32 /*_suggestedSignedHash*/, Transaction memory _transaction) external payable { + bool success = _transaction.payToTheBootloader(); + if (!success) { + revert ZkMinimalAccount__FailedToPay(); + } + } + + function prepareForPaymaster(bytes32 _txHash, bytes32 _possibleSignedHash, Transaction memory _transaction) external payable{ + + } + + //////////////////// INTERNAL FUNCTIONS //////////////////////// + + function _validateTransaction(Transaction memory _transaction) internal returns (bytes4 magic){ + // zksync system call simulation, this is used to call the NonceHolder system contract + SystemContractsCaller.systemCallWithPropagatedRevert( + uint32(gasleft()), + address(NONCE_HOLDER_SYSTEM_CONTRACT), + 0, + abi.encodeCall(INonceHolder.incrementMinNonceIfEquals, (_transaction.nonce)) + ); + + // check the fee to pay + uint256 totalRequiredBalance = _transaction.totalRequiredBalance(); + if(totalRequiredBalance > address(this).balance){ + // TODO: add logic for paymaster + revert ZkMinimalAccount__NotEnoughBalance(); + } + + // check the signature + bytes32 txHash = _transaction.encodeHash(); + address signer = ECDSA.recover(txHash, _transaction.signature); + + bool isValidSigner = signer == owner(); + if (isValidSigner) { + magic = ACCOUNT_VALIDATION_SUCCESS_MAGIC; + } else { + magic = bytes4(0); + } + return magic; + } + + function _executeTransaction(Transaction memory _transaction) internal { + address to = address(uint160(_transaction.to)); + uint128 value = Utils.safeCastToU128(_transaction.value); + bytes memory data = _transaction.data; + + if(to == address(DEPLOYER_SYSTEM_CONTRACT)){ + uint32 gas = Utils.safeCastToU32(gasleft()); + SystemContractsCaller.systemCallWithPropagatedRevert(gas, to, value, data); + }else{ + bool success; + assembly { + // (success,) = payable(msg.sender).call{value:missingAccountFunds, gas: type(uint256).max}(""); + success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0) + } + if(!success){ + revert ZkMinimalAccount__ExecutionFailed(); + } + } + } +} \ No newline at end of file diff --git a/test/zksync/ZkMinimalAccount.t.sol b/test/zksync/ZkMinimalAccount.t.sol new file mode 100644 index 0000000..93673d3 --- /dev/null +++ b/test/zksync/ZkMinimalAccount.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test, console2} from "forge-std/Test.sol"; +import {ZkMinimalAccount} from "src/zksync/ZkMinimalAccount.sol"; +import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; + +// Era Imports +import { + Transaction, + MemoryTransactionHelper +} from "lib/foundry-era-contracts/src/system-contracts/contracts/libraries/MemoryTransactionHelper.sol"; +import {BOOTLOADER_FORMAL_ADDRESS} from "lib/foundry-era-contracts/src/system-contracts/contracts/Constants.sol"; +import {ACCOUNT_VALIDATION_SUCCESS_MAGIC} from + "lib/foundry-era-contracts/src/system-contracts/contracts/interfaces/IAccount.sol"; + +// OZ Imports +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +// Foundry Devops +import {ZkSyncChainChecker} from "lib/foundry-devops/src/ZkSyncChainChecker.sol"; + +contract ZkMinimalAccountTest is Test, ZkSyncChainChecker { + using MessageHashUtils for bytes32; + + ZkMinimalAccount minimalAccount; + ERC20Mock usdc; + bytes4 constant EIP1271_SUCCESS_RETURN_VALUE = 0x1626ba7e; + + uint256 constant AMOUNT = 1e18; + bytes32 constant EMPTY_BYTES32 = bytes32(0); + address constant ANVIL_DEFAULT_ACCOUNT = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + uint256 constant ANVIL_DEFAULT_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + + function setUp() public { + minimalAccount = new ZkMinimalAccount(); + minimalAccount.transferOwnership(ANVIL_DEFAULT_ACCOUNT); + usdc = new ERC20Mock(); + vm.deal(address(minimalAccount), AMOUNT); + } + + // forge test --mt testZkOwnerCanExecuteCommands + function testZkOwnerCanExecuteCommands() public { + // Arrange + address dest = address(usdc); + uint256 value = 0; + bytes memory functionData = abi.encodeWithSelector(ERC20Mock.mint.selector, address(minimalAccount), AMOUNT); + + Transaction memory transaction = + _createUnsignedTransaction(minimalAccount.owner(), 113, dest, value, functionData); + + // Act + vm.prank(minimalAccount.owner()); + minimalAccount.executeTransaction(EMPTY_BYTES32, EMPTY_BYTES32, transaction); + + // Assert + assertEq(usdc.balanceOf(address(minimalAccount)), AMOUNT); + } + + // forge test --mt testZkValidateTransaction --zksync --system-mode=true + function testZkValidateTransaction() public onlyZkSync { + // Arrange + address dest = address(usdc); + uint256 value = 0; + bytes memory functionData = abi.encodeWithSelector(ERC20Mock.mint.selector, address(minimalAccount), AMOUNT); + Transaction memory transaction = + _createUnsignedTransaction(minimalAccount.owner(), 113, dest, value, functionData); + transaction = _signTransaction(transaction); + + // Act + vm.prank(BOOTLOADER_FORMAL_ADDRESS); + bytes4 magic = minimalAccount.validateTransaction(EMPTY_BYTES32, EMPTY_BYTES32, transaction); + + // Assert + assertEq(magic, ACCOUNT_VALIDATION_SUCCESS_MAGIC); + } + + /*////////////////////////////////////////////////////////////// + HELPERS + //////////////////////////////////////////////////////////////*/ + function _signTransaction(Transaction memory transaction) internal view returns (Transaction memory) { + bytes32 unsignedTransactionHash = MemoryTransactionHelper.encodeHash(transaction); + + uint8 v; + bytes32 r; + bytes32 s; + (v, r, s) = vm.sign(ANVIL_DEFAULT_KEY, unsignedTransactionHash); + Transaction memory signedTransaction = transaction; + signedTransaction.signature = abi.encodePacked(r, s, v); + return signedTransaction; + } + + function _createUnsignedTransaction( + address from, + uint8 transactionType, + address to, + uint256 value, + bytes memory data + ) internal view returns (Transaction memory) { + uint256 nonce = vm.getNonce(address(minimalAccount)); // At this moment, this does not work 100% with zksync + bytes32[] memory factoryDeps = new bytes32[](0); + return Transaction({ + txType: transactionType, // type 113 (0x71). + from: uint256(uint160(from)), + to: uint256(uint160(to)), + gasLimit: 16777216, + gasPerPubdataByteLimit: 16777216, + maxFeePerGas: 16777216, + maxPriorityFeePerGas: 16777216, + paymaster: 0, + nonce: nonce, + value: value, + reserved: [uint256(0), uint256(0), uint256(0), uint256(0)], + data: data, + signature: hex"", + factoryDeps: factoryDeps, + paymasterInput: hex"", + reservedDynamic: hex"" + }); + } +} \ No newline at end of file diff --git a/ts-scripts/DeployZkMinimal.ts b/ts-scripts/DeployZkMinimal.ts new file mode 100644 index 0000000..e179f6f --- /dev/null +++ b/ts-scripts/DeployZkMinimal.ts @@ -0,0 +1,87 @@ +import * as fs from "fs-extra" +import { + utils, + Wallet, + Provider, + EIP712Signer, + types, + Contract, + ContractFactory, +} from "zksync-ethers" +import "dotenv/config" + +async function main() { + // Local net - comment to unuse + // let provider = new Provider("http://127.0.0.1:8011") + // let wallet = new Wallet(process.env.PRIVATE_KEY!) + + // Sepolia - uncomment to use + let provider = new Provider(process.env.ZKSYNC_SEPOLIA_RPC_URL!) + const encryptedJson = fs.readFileSync(".encryptedKey.json", "utf8") + let wallet = Wallet.fromEncryptedJsonSync( + encryptedJson, + process.env.PRIVATE_KEY_PASSWORD! + ) + + // // Mainnet - uncomment to use + // let provider = new Provider(process.env.ZKSYNC_RPC_URL!) + // const encryptedJson = fs.readFileSync(".encryptedKey.json", "utf8") + // let wallet = Wallet.fromEncryptedJsonSync( + // encryptedJson, + // process.env.PRIVATE_KEY_PASSWORD! + // ) + + wallet = wallet.connect(provider) + console.log(`Working with wallet: ${await wallet.getAddress()}`) + const abi = JSON.parse( + fs.readFileSync("./out/ZkMinimalAccount.sol/ZkMinimalAccount.json", "utf8") + )["abi"] + const bytecode = JSON.parse( + fs.readFileSync( + "./zkout/ZkMinimalAccount.sol/ZkMinimalAccount.json", + "utf8" + ) + )["bytecode"]["object"] + + const factoryDeps = [bytecode] // We can skip this, but this is what's happening + const zkMinimalAccountFactory = new ContractFactory( + abi, + bytecode, + wallet, + "createAccount" + ) + + // const deployOptions = { + // customData: { + // salt: ethers.ZeroHash, + // // What if we don't do factoryDeps? + // // factoryDeps, + // // factoryDeps: factoryDeps + // // Ah! The ContractFactory automatically adds it in! + // }, + // } + + const zkMinimalAccount = await zkMinimalAccountFactory.deploy() + + // The above should send the following calldata: + // 0xecf95b8a0000000000000000000000000000000000000000000000000000000000000000010006ddf1eae1b53a0a62fab1fc8b4fd95c8a6f4d5fe540bf109f17bae0a431000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000 + // + // If you pop it into `calldata-decode` you'd see that the inputs to the `createAccount` are correct + // cast calldata-decode "createAccount(bytes32,bytes32,bytes,uint8)" + + console.log( + `zkMinimalAccount deployed to: ${await zkMinimalAccount.getAddress()}` + ) + console.log( + `With transaction hash: ${ + (await zkMinimalAccount.deploymentTransaction())!.hash + }` + ) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/ts-scripts/EncryptKey.ts b/ts-scripts/EncryptKey.ts new file mode 100644 index 0000000..e2af054 --- /dev/null +++ b/ts-scripts/EncryptKey.ts @@ -0,0 +1,18 @@ +import { ethers } from "ethers" +import * as fs from "fs-extra" +import "dotenv/config" + +async function main() { + const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!) + const encryptedJsonKey = await wallet.encrypt( + process.env.PRIVATE_KEY_PASSWORD! + ) + fs.writeFileSync("./.encryptedKey.json", encryptedJsonKey) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/ts-scripts/SendAATx.ts b/ts-scripts/SendAATx.ts new file mode 100644 index 0000000..1b480d8 --- /dev/null +++ b/ts-scripts/SendAATx.ts @@ -0,0 +1,144 @@ +import * as fs from "fs-extra" +import { + utils, + Wallet, + Provider, + EIP712Signer, + types, + Contract, + ContractFactory, +} from "zksync-ethers" +import * as ethers from "ethers" +import "dotenv/config" + +// Mainnet +// const ZK_MINIMAL_ADDRESS = "" + +// Sepolia +// const ZK_MINIMAL_ADDRESS = "" + +// Local +// Update this! +const ZK_MINIMAL_ADDRESS = "0x19a519025994A1F32188dE1F0E11014A791fB358" + +// Update this too! +const RANDOM_APPROVER = "0x9EA9b0cc1919def1A3CfAEF4F7A66eE3c36F86fC" + +// Mainnet +// const USDC_ZKSYNC = "0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4" +// Sepolia +const USDC_ZKSYNC = "0x5249Fd99f1C1aE9B04C65427257Fc3B8cD976620" + +// Local +// let USDC_ZKSYNC = "" + +const AMOUNT_TO_APPROVE = "1000000" + +async function main() { + console.log("Let's do this!") + + // Local net + // let provider = new Provider("http://127.0.0.1:8011") + // let wallet = new Wallet(process.env.PRIVATE_KEY!) + + // // Sepolia - Uncomment to use + let provider = new Provider(process.env.ZKSYNC_SEPOLIA_RPC_URL!) + const encryptedJson = fs.readFileSync(".encryptedKey.json", "utf8") + let wallet = Wallet.fromEncryptedJsonSync( + encryptedJson, + process.env.PRIVATE_KEY_PASSWORD! + ) + + // // Mainnet - Uncomment to use + // let provider = new Provider(process.env.ZKSYNC_RPC_URL!) + // const encryptedJson = fs.readFileSync(".encryptedKey.json", "utf8") + // let wallet = Wallet.fromEncryptedJsonSync( + // encryptedJson, + // process.env.PRIVATE_KEY_PASSWORD! + // ) + + wallet = wallet.connect(provider) + + const abi = JSON.parse( + fs.readFileSync("./out/ZkMinimalAccount.sol/ZkMinimalAccount.json", "utf8") + )["abi"] + console.log("Setting up contract details...") + const zkMinimalAccount = new Contract(ZK_MINIMAL_ADDRESS, abi, provider) + + // If this doesn't log the owner, you have an issue! + console.log( + `The owner of this minimal account is: `, + await zkMinimalAccount.owner() + ) + const usdcAbi = JSON.parse( + fs.readFileSync("./out/ERC20/IERC20.sol/IERC20.json", "utf8") + )["abi"] + const usdcContract = new Contract(USDC_ZKSYNC, usdcAbi, provider) + + console.log("Populating transaction...") + let approvalData = await usdcContract.approve.populateTransaction( + RANDOM_APPROVER, + AMOUNT_TO_APPROVE + ) + + let aaTx = approvalData + + const gasLimit = await provider.estimateGas({ + ...aaTx, + from: wallet.address, + }) + const gasPrice = (await provider.getFeeData()).gasPrice! + + aaTx = { + ...aaTx, + from: ZK_MINIMAL_ADDRESS, + gasLimit: gasLimit, + gasPrice: gasPrice, + chainId: (await provider.getNetwork()).chainId, + nonce: await provider.getTransactionCount(ZK_MINIMAL_ADDRESS), + type: 113, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + } as types.Eip712Meta, + value: 0n, + } + const signedTxHash = EIP712Signer.getSignedDigest(aaTx) + + console.log("Signing transaction...") + const signature = ethers.concat([ + ethers.Signature.from(wallet.signingKey.sign(signedTxHash)).serialized, + ]) + console.log(signature) + + aaTx.customData = { + ...aaTx.customData, + customSignature: signature, + } + + console.log( + `The minimal account nonce before the first tx is ${await provider.getTransactionCount( + ZK_MINIMAL_ADDRESS + )}` + ) + + const sentTx = await provider.broadcastTransaction( + types.Transaction.from(aaTx).serialized + ) + + console.log(`Transaction sent from minimal account with hash ${sentTx.hash}`) + await sentTx.wait() + + // Checking that the nonce for the account has increased + console.log( + `The account's nonce after the first tx is ${await provider.getTransactionCount( + ZK_MINIMAL_ADDRESS + )}` + ) +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..732134b --- /dev/null +++ b/yarn.lock @@ -0,0 +1,123 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + +"@types/fs-extra@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" + integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== + dependencies: + "@types/jsonfile" "*" + "@types/node" "*" + +"@types/jsonfile@*": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702" + integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "20.14.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.11.tgz#09b300423343460455043ddd4d0ded6ac579b74b" + integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA== + dependencies: + undici-types "~5.26.4" + +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +ethers@6: + version "6.13.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.1.tgz#2b9f9c7455cde9d38b30fe6589972eb083652961" + integrity sha512-hdJ2HOxg/xx97Lm9HdCWk949BfYqYWpyw4//78SiwOLgASyfrNszfMUNB2joKjvGUdwhHfaiMMFFwacVVoLR9A== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.17.1" + +fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +typescript@^5.4.5: + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + +zksync-ethers@^6.8.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-6.10.0.tgz#d14f2ef905e012f923d76b9252fe4b20069ab854" + integrity sha512-1KkFPYdanH3G9LiMkiJSTTCKpbaHnuahwXAXSkcnSRUp9acpLABSnlvtyyxAp5LiQfL/GRPQAuOJlWjshUt9HA==