Skip to content

Commit

Permalink
add batch transactions to CandideAccount
Browse files Browse the repository at this point in the history
  • Loading branch information
sherifahmed990 committed Sep 30, 2023
1 parent 9298bbd commit 947779d
Show file tree
Hide file tree
Showing 12 changed files with 475 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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=
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!-- PROJECT LOGO -->

<div align="center">
<h1 align="center">CreateAccountAndSendBatchTransactions Example - AbstractionKit - Account Abstraction SDK by Candide</h2>
</div>

<div align="center">
<img src="https://user-images.githubusercontent.com/7014833/203773780-04a0c8c0-93a6-43a4-bb75-570cb951dfa0.png" height =200>
</div>

# 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 -->
## License

MIT
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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<void> {
//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()
Original file line number Diff line number Diff line change
@@ -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"]
}
Original file line number Diff line number Diff line change
@@ -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==
Loading

0 comments on commit 947779d

Please sign in to comment.