diff --git a/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/.env.example b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/.env.example
new file mode 100644
index 0000000..4a2ed1c
--- /dev/null
+++ b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/.env.example
@@ -0,0 +1,8 @@
+#goerli chain example values
+CHAIN_ID=5
+BUNDLER_URL=https://goerli.test.voltaire.candidewallet.com/rpc
+ENTRYPOINT_ADDRESS=0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
+JSON_RPC_NODE_PROVIDER=https://ethereum-goerli.publicnode.com
+
+#account signer privatekey
+PRIVATE_KEY=
\ No newline at end of file
diff --git a/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/README.md b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/README.md
new file mode 100644
index 0000000..3541e63
--- /dev/null
+++ b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/README.md
@@ -0,0 +1,35 @@
+
+
+
+
CreateAccountAndSendBatchTransactions Example - AbstractionKit - Account Abstraction SDK by Candide
+
+
+
+
+
+
+# About
+
+CreateAccountAndSendBatchTransactions Example - AbstractionKit - Account Abstraction SDK by Candide
+
+This example is in the Goerli chain.
+
+In this example you will need to fund the new account address(sender) with Goerli eth first.
+
+# How to use this example
+
+### copy .env.example and add the paymaster values and a privatekey for signer
+```
+cp .env.example .env
+```
+
+### install dependencies, build and run
+```
+yarn install
+yarn build
+node dist/index.js
+```
+
+## License
+
+MIT
diff --git a/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/package.json b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/package.json
new file mode 100644
index 0000000..d97c3f3
--- /dev/null
+++ b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "CreateAccountAndSendBatchTransactions",
+ "version": "1.0.0",
+ "description": "create a new account and send a useroperation with multiple transactions.",
+ "main": "dist/index.js",
+ "scripts": {
+ "build": "rm -rf dist && tsc",
+ "clean": "rm -rf dist",
+ "format": "prettier --write src/**/*.ts",
+ "lint": "eslint src/**/*.ts"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "abstractionkit": "^0.0.7",
+ "dotenv": "^16.3.1",
+ "ethers": "^6.6.7",
+ "typescript": "^5.1.6"
+ },
+ "devDependencies": {
+ "@types/ws": "^8.5.6"
+ }
+}
diff --git a/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/src/index.ts b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/src/index.ts
new file mode 100644
index 0000000..0b20b06
--- /dev/null
+++ b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/src/index.ts
@@ -0,0 +1,139 @@
+import * as dotenv from 'dotenv'
+import {Wallet, JsonRpcProvider, getBytes, id, ZeroAddress } from "ethers"
+
+import {
+ Bundler,
+ CandideAccount,
+ GasEstimationResult,
+ UserOperation,
+ getUserOperationHash,
+ UserOperationDummyValues,
+ getCallData,
+ MetaTransaction,
+ getFunctionSelector,
+ Operation
+} from "abstractionkit";
+
+
+async function main(): Promise {
+ //get vlues from .env
+ dotenv.config()
+ const chainId = process.env.CHAIN_ID as string
+ const bundlerUrl = process.env.BUNDLER_URL as string
+ const jsonRpcNodeProvider = process.env.JSON_RPC_NODE_PROVIDER as string
+ const entrypointAddress = process.env.ENTRYPOINT_ADDRESS as string
+ const privateKey = process.env.PRIVATE_KEY as string
+
+ let bundler: Bundler = new Bundler(
+ bundlerUrl,
+ entrypointAddress
+ );
+
+ let eoaSigner = new Wallet(privateKey);
+
+ let smartAccount = new CandideAccount()
+
+ //create a new smart account, only needed for the first useroperation for a new account
+ let [newAccountAddress, initCode] = smartAccount.createNewAccount([eoaSigner.address])
+
+ console.log("Account address(sender) : " + newAccountAddress)
+
+ // 1st transction - create callData to deposit eth and get wEth in return
+ const wEthTokenAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6";
+ const depositFunctionSignature = 'deposit()';
+ const depositFunctionSelector = getFunctionSelector(depositFunctionSignature);
+ const depositTransactionCallData = getCallData(depositFunctionSelector, [], []);
+
+ const tx1 :MetaTransaction ={
+ to: wEthTokenAddress,
+ value: 10, //amount to deposit
+ data: depositTransactionCallData,
+ operation: Operation.Call
+ }
+
+ // 2nd transaction - approve allowance to uniswap router
+ const uniswapV2RouterAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D";
+ const approveFunctionSignature = 'approve(address,uint256)';
+ const approveFunctionSelector = getFunctionSelector(approveFunctionSignature);
+ const approveTransactionCallData = getCallData(
+ approveFunctionSelector,
+ ["address", "uint256"],
+ [uniswapV2RouterAddress, 10]
+ );
+
+ const tx2 :MetaTransaction ={
+ to: wEthTokenAddress,
+ value: 0,
+ data: approveTransactionCallData,
+ operation: Operation.Call
+ }
+
+ // 3rd transaction - swap weth for usdc
+ const token0 = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"; //weth
+ const token1 = "0x9B2660A7BEcd0Bf3d90401D1C214d2CD36317da5"; //usdc
+ const swapFunctionSignature = 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)';
+ const swapFunctionSelector = getFunctionSelector(swapFunctionSignature);
+ const swapTransactionCallData = getCallData(swapFunctionSelector,
+ ["uint256","uint256","address[]","address", "uint256"], //amountIn, amountOutMin, path, to, deadline
+ [
+ 5, //amountIn
+ 1, //amountOutMin
+ [token0, token1], //path
+ newAccountAddress, //to
+ Math.floor(Date.now() / 1000) + 60 * 20, // deadline - 20 minutes from the current Unix time
+ ]
+ );
+ const tx3 :MetaTransaction ={
+ to: uniswapV2RouterAddress,
+ value: 0,
+ data: swapTransactionCallData,
+ operation: Operation.Call
+ }
+
+ let callData = smartAccount.createCallDataBatchTransaction([tx1, tx2, tx3]);
+
+ const provider = new JsonRpcProvider(jsonRpcNodeProvider);
+
+ let user_operation :UserOperation={
+ ...UserOperationDummyValues,
+ sender:newAccountAddress,
+ nonce: "0x00",
+ initCode:initCode,//only needed for the first useroperation for a new account
+ callData:callData
+ }
+
+ //fetch gas price - use your prefered source
+ const feeData = await provider.getFeeData()
+ user_operation.maxFeePerGas = "0x" + Math.ceil(Number(feeData.maxFeePerGas)*1.5).toString(16)//convert to hex format
+ user_operation.maxPriorityFeePerGas = "0x" + Math.ceil(Number(feeData.maxPriorityFeePerGas)*1.5).toString(16)//convert to hex format
+
+ let estimation = await bundler.estimateUserOperationGas(user_operation)
+ console.log("estimation")
+ console.log(estimation)
+ if("code" in estimation){
+ return
+ }
+ //either multiply gas limit with a factor to compensate for the missing paymasterAndData and signature during gas estimation
+ //or supply dummy values that will not cause the useroperation to revert
+ //for the most accurate values, estimate gas again after acquiring the initial gas limits
+ //and a valide paymasterAndData and signature
+ estimation = estimation as GasEstimationResult
+ user_operation.preVerificationGas = "0x" + Math.ceil(Number(estimation.preVerificationGas)*1.2).toString(16)
+ user_operation.verificationGasLimit = "0x" + Math.ceil(Number(estimation.verificationGasLimit)*1.5).toString(16)
+ user_operation.callGasLimit = "0x" + Math.ceil(Number(estimation.callGasLimit)*1.2).toString(16)
+
+ //sign the user operation hash
+ let user_operation_hash = getUserOperationHash(
+ user_operation, entrypointAddress, chainId
+ )
+ user_operation.signature = await eoaSigner.signMessage(getBytes(user_operation_hash))
+
+ //send the user operation to the bundler
+ let bundlerResponse = await bundler.sendUserOperation(user_operation)
+ console.log(bundlerResponse)
+ if("message" in bundlerResponse && bundlerResponse.message as string == "AA21 didn't pay prefund"){
+ console.log("Please fund the new account address with some eth to pay for gas : " + newAccountAddress)
+ }
+}
+
+main()
\ No newline at end of file
diff --git a/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/tsconfig.json b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/tsconfig.json
new file mode 100644
index 0000000..93157c6
--- /dev/null
+++ b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ "module": "commonjs" /* Specify what module code is generated. */,
+ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ "declaration": true,
+ "declarationMap": true,
+ "removeComments": true,
+ "allowSyntheticDefaultImports": true,
+ "sourceMap": false,
+ "outDir": "./dist",
+ "baseUrl": "./",
+ "incremental": true,
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+
+ "strict": true /* Enable all strict type-checking options. */,
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "test", "lib", "**/*spec.ts"]
+}
diff --git a/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/yarn.lock b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/yarn.lock
new file mode 100644
index 0000000..f0a7cfc
--- /dev/null
+++ b/examples/CandideAccountExamples/CreateAccountAndSendBatchTransactions/yarn.lock
@@ -0,0 +1,119 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@adraffy/ens-normalize@1.9.2":
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz#60111a5d9db45b2e5cbb6231b0bb8d97e8659316"
+ integrity sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg==
+
+"@noble/hashes@1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183"
+ integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==
+
+"@noble/secp256k1@1.7.1":
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
+ integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==
+
+"@types/node@*":
+ version "20.7.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.2.tgz#0bdc211f8c2438cfadad26dc8c040a874d478aed"
+ integrity sha512-RcdC3hOBOauLP+r/kRt27NrByYtDjsXyAuSbR87O6xpsvi763WI+5fbSIvYJrXnt9w4RuxhV6eAXfIs7aaf/FQ==
+
+"@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==
+
+"@types/ws@^8.5.6":
+ version "8.5.6"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.6.tgz#e9ad51f0ab79b9110c50916c9fcbddc36d373065"
+ integrity sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==
+ dependencies:
+ "@types/node" "*"
+
+abstractionkit@^0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/abstractionkit/-/abstractionkit-0.0.7.tgz#56885deb583b9d15d08b9869a8ed699fc45ec4b0"
+ integrity sha512-GQ8gIzIeu2f1G8lCNvS57ZGYeU8cG9AIgn/zAgmUxx9D1nsyBDJTTSpzW0X+6sJIdrW98pqS6zkai2RPBl7WCw==
+ dependencies:
+ ethers "^6.6.4"
+ isomorphic-unfetch "^3.1.0"
+
+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.3.1:
+ version "16.3.1"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
+ integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
+
+ethers@^6.6.4, ethers@^6.6.7:
+ version "6.7.1"
+ resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.7.1.tgz#9c65e8b5d8e9ad77b7e8cf1c46099892cfafad49"
+ integrity sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==
+ dependencies:
+ "@adraffy/ens-normalize" "1.9.2"
+ "@noble/hashes" "1.1.2"
+ "@noble/secp256k1" "1.7.1"
+ "@types/node" "18.15.13"
+ aes-js "4.0.0-beta.5"
+ tslib "2.4.0"
+ ws "8.5.0"
+
+isomorphic-unfetch@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f"
+ integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==
+ dependencies:
+ node-fetch "^2.6.1"
+ unfetch "^4.2.0"
+
+node-fetch@^2.6.1:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+ integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+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.1.6:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
+ integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
+
+unfetch@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"
+ integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+ws@8.5.0:
+ version "8.5.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
+ integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
diff --git a/examples/CandideAccountExamples/CreateAccountAndSendTransaction/src/index.ts b/examples/CandideAccountExamples/CreateAccountAndSendTransaction/src/index.ts
index a5d877b..72914ab 100644
--- a/examples/CandideAccountExamples/CreateAccountAndSendTransaction/src/index.ts
+++ b/examples/CandideAccountExamples/CreateAccountAndSendTransaction/src/index.ts
@@ -1,5 +1,5 @@
import * as dotenv from 'dotenv'
-import {Wallet, JsonRpcProvider, getBytes, BigNumberish } from "ethers"
+import {Wallet, JsonRpcProvider, getBytes, id, ZeroAddress } from "ethers"
import {
Bundler,
@@ -7,9 +7,14 @@ import {
GasEstimationResult,
UserOperation,
getUserOperationHash,
- UserOperationDummyValues
+ UserOperationDummyValues,
+ getCallData,
+ MetaTransaction,
+ getFunctionSelector,
+ Operation
} from "abstractionkit";
+
async function main(): Promise {
//get vlues from .env
dotenv.config()
@@ -33,11 +38,19 @@ async function main(): Promise {
console.log("Account address(sender) : " + newAccountAddress)
- //send 5 wei to 0x1a02592A3484c2077d2E5D24482497F85e1980C6
- let callData = smartAccount.createSendEthCallData(
- "0x1a02592A3484c2077d2E5D24482497F85e1980C6",
- 5
- )
+ // create callData to deposit eth and get wEth in return
+ const wEthTokenAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6";
+ const depositFunctionSignature = 'deposit()';
+ const depositFunctionSelector = getFunctionSelector(depositFunctionSignature);
+ const depositTransactionCallData = getCallData(depositFunctionSelector, [], []);
+
+ const tx :MetaTransaction ={
+ to: wEthTokenAddress,
+ value: 10, //amount to deposit
+ data: depositTransactionCallData,
+ operation: Operation.Call
+ }
+ let callData = smartAccount.createCallDataSingleTransaction(tx);
const provider = new JsonRpcProvider(jsonRpcNodeProvider);
@@ -51,10 +64,9 @@ async function main(): Promise {
//fetch gas price - use your prefered source
const feeData = await provider.getFeeData()
- user_operation.maxFeePerGas = "0x" + feeData.maxFeePerGas?.toString(16)//convert to hex format
- user_operation.maxPriorityFeePerGas = "0x" + feeData.maxPriorityFeePerGas?.toString(16)//convert to hex format
+ user_operation.maxFeePerGas = "0x" + Math.ceil(Number(feeData.maxFeePerGas)*1.5).toString(16)//convert to hex format
+ user_operation.maxPriorityFeePerGas = "0x" + Math.ceil(Number(feeData.maxPriorityFeePerGas)*1.5).toString(16)//convert to hex format
-
let estimation = await bundler.estimateUserOperationGas(user_operation)
console.log(estimation)
if("code" in estimation){
diff --git a/src/abstractionkit.ts b/src/abstractionkit.ts
index 66ae5a4..269487a 100644
--- a/src/abstractionkit.ts
+++ b/src/abstractionkit.ts
@@ -1,5 +1,7 @@
export { SmartAccount } from "./account/SmartAccount";
-export { CandideAccount } from "./account/CandideAccount";
+export { CandideAccount } from "./account/Candide/CandideAccount";
+export { MetaTransaction } from "./account/Candide/types";
+
export { SimpleAccount } from "./account/SimpleAccount";
export { SmartAccountFactory } from "./factory/SmartAccountFactory";
@@ -10,10 +12,12 @@ export { Bundler } from "./Bundler";
export {CandideValidationPaymaster} from "./paymaster/CandideValidationPaymaster"
-export { getUserOperationHash } from "./utils";
+export { getUserOperationHash, getCallData, getFunctionSelector } from "./utils";
export { UserOperationEmptyValues, UserOperationDummyValues } from "./constants";
+export { Operation } from "./types";
+
export type {
UserOperation,
AbiInputValue,
diff --git a/src/account/CandideAccount.ts b/src/account/Candide/CandideAccount.ts
similarity index 72%
rename from src/account/CandideAccount.ts
rename to src/account/Candide/CandideAccount.ts
index e029435..0eb5811 100644
--- a/src/account/CandideAccount.ts
+++ b/src/account/Candide/CandideAccount.ts
@@ -1,14 +1,18 @@
-import { SmartAccount } from "./SmartAccount";
+import { SmartAccount } from "../SmartAccount";
import type { BigNumberish, BytesLike } from "ethers";
-import type { Operation } from "../types";
+import { Operation } from "../../types";
+import { getCallData } from "../../utils";
import {
ZeroAddress,
keccak256,
solidityPacked,
solidityPackedKeccak256,
} from "ethers";
-import { CandideAccountFactory } from "../factory/CandideAccountFactory";
-import { SmartAccountFactory } from "../factory/SmartAccountFactory";
+import { CandideAccountFactory } from "../../factory/CandideAccountFactory";
+import { SmartAccountFactory } from "../../factory/SmartAccountFactory";
+import { MetaTransaction } from "./types";
+import { encodeMultiSendCallData } from "./multisend";
+
export class CandideAccount extends SmartAccount {
readonly entrypointAddress: string;
@@ -116,6 +120,7 @@ export class CandideAccount extends SmartAccount {
createSendEthCallData(to: string, value: BigNumberish): BytesLike {
return this.createCallData(to, value, "0x", 0, ZeroAddress, ZeroAddress, 0);
}
+
createCallData(
to: string,
value: BigNumberish,
@@ -162,4 +167,65 @@ export class CandideAccount extends SmartAccount {
).slice(-40);
return "0x" + proxyAdd;
}
+
+ createCallDataSingleTransactionWithPaymaster(
+ tx: MetaTransaction,
+ paymaster: string,
+ approveToken: string,
+ approveAmount: BigNumberish,
+ ): BytesLike {
+ const executorFunctionCallData = this.getExecutorCallData([
+ tx.to,
+ tx.value,
+ tx.data,
+ tx.operation,
+ paymaster,
+ approveToken,
+ approveAmount,
+ ]);
+ return executorFunctionCallData;
+ }
+
+ createCallDataSingleTransaction(
+ tx: MetaTransaction
+ ): BytesLike{
+ return this.createCallDataSingleTransactionWithPaymaster(
+ tx, ZeroAddress, ZeroAddress, 0);
+ }
+
+ createCallDataBatchTransactionWithPaymaster(
+ txs: MetaTransaction[],
+ paymaster: string,
+ approveToken: string,
+ approveAmount: BigNumberish,
+ ): BytesLike {
+ const multisendContractAddress: string = "0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526"
+ const multiData = encodeMultiSendCallData(txs);
+
+ const mutisendSelector = "0x8d80ff0a";
+ const multiSendCallData = getCallData(
+ mutisendSelector,
+ ["bytes"],
+ [multiData]
+ );
+
+ const executorFunctionCallData = this.getExecutorCallData([
+ multisendContractAddress,
+ 0,
+ multiSendCallData,
+ Operation.Delegate,
+ paymaster,
+ approveToken,
+ approveAmount,
+ ]);
+
+ return executorFunctionCallData;
+ }
+
+ createCallDataBatchTransaction(
+ txs: MetaTransaction[]
+ ): BytesLike{
+ return this.createCallDataBatchTransactionWithPaymaster(
+ txs, ZeroAddress, ZeroAddress, 0);
+ }
}
diff --git a/src/account/Candide/multisend.ts b/src/account/Candide/multisend.ts
new file mode 100644
index 0000000..31b6215
--- /dev/null
+++ b/src/account/Candide/multisend.ts
@@ -0,0 +1,15 @@
+import { ethers } from "ethers";
+import { MetaTransaction } from "./types";
+
+function encodeMultiSendTransaction(tx: MetaTransaction): string {
+ const data = ethers.getBytes(tx.data);
+ const encoded = ethers.solidityPacked(
+ ["uint8", "address", "uint256", "uint256", "bytes"],
+ [tx.operation, tx.to, tx.value, data.length, data],
+ );
+ return encoded.slice(2);
+}
+
+export function encodeMultiSendCallData(txs: MetaTransaction[]): string {
+ return "0x" + txs.map((tx) => encodeMultiSendTransaction(tx)).join("");
+}
\ No newline at end of file
diff --git a/src/account/Candide/types.ts b/src/account/Candide/types.ts
new file mode 100644
index 0000000..24176ec
--- /dev/null
+++ b/src/account/Candide/types.ts
@@ -0,0 +1,9 @@
+import { BigNumberish, BytesLike } from "ethers";
+import type { Operation } from "../../types";
+
+export interface MetaTransaction {
+ to: string;
+ value: BigNumberish;
+ data: BytesLike;
+ operation: Operation;
+}
\ No newline at end of file
diff --git a/src/utils.ts b/src/utils.ts
index 0dcf625..721b24b 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -5,6 +5,7 @@ import { AbiCoder, keccak256 } from "ethers";
import type { BytesLike, BigNumberish } from "ethers";
import type { AbiInputValue, UserOperation, JsonRpcResponse, JsonRpcParam } from "./types";
+import { id } from "ethers";
export function getUserOperationHash(
useroperation: UserOperation,
@@ -99,4 +100,11 @@ export async function sendJsonRpcRequest(
const response = await fetch(rpcUrl, requestOptions);
return await response.json() as JsonRpcResponse;
+}
+
+
+export function getFunctionSelector(
+ functionSignature: string,
+): string {
+ return id(functionSignature).slice(0,10);
}
\ No newline at end of file