Skip to content

Commit

Permalink
API updates (eth-infinitism#12)
Browse files Browse the repository at this point in the history
* remove runtime 'hardhat' dependency

* paymaster API

added to provider's ClientConfig
- receive preVerificationGas

* removed chainId config param

* v0.2.1

* fix bundler errors

* added getUserOpReceipt

* runop (SimpleWalletAPI) : get "create2" address also afrter creation

* disable validate-chain test

* v0.2.3
  • Loading branch information
drortirosh authored Oct 11, 2022
1 parent 9b0c62d commit d5071db
Show file tree
Hide file tree
Showing 24 changed files with 167 additions and 44 deletions.
17 changes: 17 additions & 0 deletions .idea/aa-bundler.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dockers/bundler/dbuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cd `cd \`dirname $0\`;pwd`
#need to preprocess first to have the Version.js
test -z $NOBUILD && yarn preprocess

test -z "$VERSION" && VERSION=`node -e "console.log(require('../../packages/common/dist/src/Version.js').erc4337RuntimeVersion)"`
test -z "$VERSION" && VERSION=`jq -r .version ../../packages/utils/package.json`
echo version=$VERSION

IMAGE=accountabstraction/bundler
Expand Down
2 changes: 1 addition & 1 deletion dockers/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ services:
bundler:
container_name: bundler
ports: [ '3000:3000' ]
image: accountabstraction/bundler:0.2.0
image: accountabstraction/bundler:0.2.1
restart: on-failure

volumes:
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.2.0",
"version": "0.2.3",
"npmClient": "yarn",
"useWorkspaces": true
}
6 changes: 3 additions & 3 deletions packages/bundler/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@account-abstraction/bundler",
"version": "0.2.0",
"version": "0.2.3",
"license": "MIT",
"private": true,
"files": [
Expand All @@ -25,7 +25,7 @@
},
"dependencies": {
"@account-abstraction/contracts": "^0.2.0",
"@account-abstraction/utils": "^0.2.0",
"@account-abstraction/utils": "^0.2.3",
"@ethersproject/abi": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"@types/cors": "^2.8.12",
Expand All @@ -39,7 +39,7 @@
"source-map-support": "^0.5.21"
},
"devDependencies": {
"@account-abstraction/sdk": "^0.2.0",
"@account-abstraction/sdk": "^0.2.3",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.3",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^1.0.2",
Expand Down
3 changes: 1 addition & 2 deletions packages/bundler/src/BundlerServer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import bodyParser from 'body-parser'
import cors from 'cors'
import express, { Express, Response, Request } from 'express'
import { JsonRpcRequest } from 'hardhat/types'
import { Provider } from '@ethersproject/providers'
import { Wallet, utils } from 'ethers'
import { hexlify, parseEther } from 'ethers/lib/utils'
Expand Down Expand Up @@ -75,7 +74,7 @@ export class BundlerServer {
params,
jsonrpc,
id
}: JsonRpcRequest = req.body
} = req.body
try {
const result = await this.handleMethod(method, params)
console.log('sent', method, '-', result)
Expand Down
5 changes: 3 additions & 2 deletions packages/bundler/src/UserOpMethodHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UserOperationStruct } from './types/contracts/BundlerHelper'
import { hexValue, resolveProperties } from 'ethers/lib/utils'
import { rethrowError } from '@account-abstraction/utils'
import { calcPreVerificationGas } from '@account-abstraction/sdk/dist/src/calcPreVerificationGas'
import { postExecutionDump } from '@account-abstraction/utils/dist/src/postExecCheck'
// import { postExecutionDump } from '@account-abstraction/utils/dist/src/postExecCheck'

export class UserOpMethodHandler {
constructor (
Expand Down Expand Up @@ -55,9 +55,10 @@ export class UserOpMethodHandler {
}

const gasLimit = undefined
console.log('using gasLimit=', gasLimit)
await this.entryPoint.handleOps([userOp], beneficiary, { gasLimit }).catch(rethrowError)

await postExecutionDump(this.entryPoint, requestId)
// await postExecutionDump(this.entryPoint, requestId)
return requestId
}
}
19 changes: 14 additions & 5 deletions packages/bundler/src/runner/runop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Runner {
}

async getAddress (): Promise<string> {
return await this.walletApi.getWalletAddress()
return await this.walletApi.getCreate2Address()
}

async init (deploymentSigner?: Signer): Promise<this> {
Expand Down Expand Up @@ -88,7 +88,9 @@ class Runner {
data
})
try {
await this.bundlerProvider.sendUserOpToBundler(userOp)
const requestId = await this.bundlerProvider.sendUserOpToBundler(userOp)
const txid = await this.walletApi.getUserOpReceipt(requestId)
console.log('reqId', requestId, 'txid=', txid)
} catch (e: any) {
throw this.parseExpectedGas(e)
}
Expand All @@ -110,17 +112,22 @@ async function main (): Promise<void> {
let signer: Signer
const deployDeployer: boolean = opts.deployDeployer
if (opts.mnemonic != null) {
signer = Wallet.fromMnemonic(fs.readFileSync(opts.mnemonic, 'ascii').trim())
signer = Wallet.fromMnemonic(fs.readFileSync(opts.mnemonic, 'ascii').trim()).connect(provider)
} else {
try {
const accounts = await provider.listAccounts()
if (accounts.length === 0) {
console.log('fatal: no account. use --mnemonic (needed to fund wallet)')
process.exit(1)
}
// for hardhat/node, use account[0]
signer = provider.getSigner()
// deployDeployer = true
} catch (e) {
throw new Error('must specify --mnemonic')
}
}
const walletOwner = new Wallet('0x'.padEnd(66, '1'))
const walletOwner = new Wallet('0x'.padEnd(66, '7'))

const client = await new Runner(provider, opts.bundlerUrl, walletOwner).init(deployDeployer ? signer : undefined)

Expand All @@ -138,12 +145,14 @@ async function main (): Promise<void> {
console.log('wallet address', addr, 'deployed=', await isDeployed(addr), 'bal=', formatEther(bal))
// TODO: actual required val
const requiredBalance = parseEther('0.1')
if (bal.lt(requiredBalance)) {
if (bal.lt(requiredBalance.div(2))) {
console.log('funding wallet to', requiredBalance)
await signer.sendTransaction({
to: addr,
value: requiredBalance.sub(bal)
})
} else {
console.log('not funding wallet. balance is enough')
}

const dest = addr
Expand Down
5 changes: 1 addition & 4 deletions packages/bundler/test/Flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ describe('Flow', function () {
let entryPointAddress: string
let sampleRecipientAddress: string
let signer: Signer
let chainId: number
before(async function () {
signer = await hre.ethers.provider.getSigner()
chainId = await hre.ethers.provider.getNetwork().then(net => net.chainId)
const beneficiary = await signer.getAddress()

const sampleRecipientFactory = await ethers.getContractFactory('SampleRecipient')
Expand Down Expand Up @@ -80,8 +78,7 @@ describe('Flow', function () {
it('should send transaction and make profit', async function () {
const config: ClientConfig = {
entryPointAddress,
bundlerUrl: 'http://localhost:5555/rpc',
chainId
bundlerUrl: 'http://localhost:5555/rpc'
}

// use this as signer (instead of node's first account)
Expand Down
1 change: 1 addition & 0 deletions packages/bundler/test/UserOpMethodHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe('UserOpMethodHandler', function () {

let walletDeployerAddress: string
before(async function () {
DeterministicDeployer.init(ethers.provider)
walletDeployerAddress = await DeterministicDeployer.deploy(SimpleWalletDeployer__factory.bytecode)

const smartWalletAPI = new SimpleWalletAPI({
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@account-abstraction/sdk",
"version": "0.2.0",
"version": "0.2.3",
"main": "./dist/src/index.js",
"license": "MIT",
"files": [
Expand All @@ -18,7 +18,7 @@
},
"dependencies": {
"@account-abstraction/contracts": "^0.2.0",
"@account-abstraction/utils": "^0.2.0",
"@account-abstraction/utils": "^0.2.3",
"@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/networks": "^5.7.0",
Expand Down
35 changes: 34 additions & 1 deletion packages/sdk/src/BaseWalletAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export interface BaseApiParams {
paymasterAPI?: PaymasterAPI
}

export interface UserOpResult {
transactionHash: string
success: boolean
}

/**
* Base class for all Smart Wallet ERC-4337 Clients to implement.
* Subclass should inherit 5 methods to support a specific wallet contract:
Expand Down Expand Up @@ -249,7 +254,16 @@ export abstract class BaseWalletAPI {
maxPriorityFeePerGas
}

partialUserOp.paymasterAndData = this.paymasterAPI == null ? '0x' : await this.paymasterAPI.getPaymasterAndData(partialUserOp)
let paymasterAndData: string | undefined
if (this.paymasterAPI != null) {
// fill (partial) preVerificationGas (all except the cost of the generated paymasterAndData)
const userOpForPm = {
...partialUserOp,
preVerificationGas: this.getPreVerificationGas(partialUserOp)
}
paymasterAndData = await this.paymasterAPI.getPaymasterAndData(userOpForPm)
}
partialUserOp.paymasterAndData = paymasterAndData ?? '0x'
return {
...partialUserOp,
preVerificationGas: this.getPreVerificationGas(partialUserOp),
Expand Down Expand Up @@ -277,4 +291,23 @@ export abstract class BaseWalletAPI {
async createSignedUserOp (info: TransactionDetailsForUserOp): Promise<UserOperationStruct> {
return await this.signUserOp(await this.createUnsignedUserOp(info))
}

/**
* get the transaction that has this requestId mined, or null if not found
* @param requestId returned by sendUserOpToBundler (or by getRequestId..)
* @param timeout stop waiting after this timeout
* @param interval time to wait between polls.
* @return the transactionHash this userOp was mined, or null if not found.
*/
async getUserOpReceipt (requestId: string, timeout = 30000, interval = 5000): Promise<string | null> {
const endtime = Date.now() + timeout
while (Date.now() < endtime) {
const events = await this.entryPointView.queryFilter(this.entryPointView.filters.UserOperationEvent(requestId))
if (events.length > 0) {
return events[0].transactionHash
}
await new Promise(resolve => setTimeout(resolve, interval))
}
return null
}
}
10 changes: 4 additions & 6 deletions packages/sdk/src/ClientConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PaymasterAPI } from './PaymasterAPI'

/**
* configuration params for wrapProvider
*/
Expand All @@ -10,18 +12,14 @@ export interface ClientConfig {
* url to the bundler
*/
bundlerUrl: string
/**
* chainId of current network. used to validate against the bundler's chainId
*/
chainId: number
/**
* if set, use this pre-deployed wallet.
* (if not set, use getSigner().getAddress() to query the "counterfactual" address of wallet.
* you may need to fund this address so the wallet can pay for its own creation)
*/
walletAddres?: string
/**
* if set, use this paymaster
* if set, call just before signing.
*/
paymasterAddress?: string
paymasterAPI?: PaymasterAPI
}
17 changes: 14 additions & 3 deletions packages/sdk/src/DeterministicDeployer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ethers } from 'hardhat'
import { BigNumber, BigNumberish } from 'ethers'
import { hexConcat, hexlify, hexZeroPad, keccak256 } from 'ethers/lib/utils'
import { TransactionRequest } from '@ethersproject/abstract-provider'
import { JsonRpcProvider } from '@ethersproject/providers'

/**
* wrapper class for Arachnid's deterministic deployer
Expand Down Expand Up @@ -34,7 +34,7 @@ export class DeterministicDeployer {
deploymentGasPrice = 100e9
deploymentGasLimit = 100000

constructor (readonly provider = ethers.provider) {
constructor (readonly provider: JsonRpcProvider) {
}

async isContractDeployed (address: string): Promise<boolean> {
Expand Down Expand Up @@ -98,5 +98,16 @@ export class DeterministicDeployer {
return addr
}

static instance = new DeterministicDeployer()
private static _instance?: DeterministicDeployer

static init (provider: JsonRpcProvider): void {
this._instance = new DeterministicDeployer(provider)
}

static get instance (): DeterministicDeployer {
if (this._instance == null) {
throw new Error('must call "DeterministicDeployer.init(ethers.provider)" first')
}
return this._instance
}
}
12 changes: 9 additions & 3 deletions packages/sdk/src/ERC4337EthersProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class ERC4337EthersProvider extends BaseProvider {
readonly signer: ERC4337EthersSigner

constructor (
readonly chainId: number,
readonly config: ClientConfig,
readonly originalSigner: Signer,
readonly originalProvider: BaseProvider,
Expand All @@ -28,12 +29,17 @@ export class ERC4337EthersProvider extends BaseProvider {
) {
super({
name: 'ERC-4337 Custom Network',
chainId: config.chainId
chainId
})
this.signer = new ERC4337EthersSigner(config, originalSigner, this, httpRpcClient, smartWalletAPI)
}

/**
* finish intializing the provider.
* MUST be called after construction, before using the provider.
*/
async init (): Promise<this> {
// await this.httpRpcClient.validateChainId()
this.initializedBlockNumber = await this.originalProvider.getBlockNumber()
await this.smartWalletAPI.init()
// await this.signer.init()
Expand Down Expand Up @@ -85,7 +91,7 @@ export class ERC4337EthersProvider extends BaseProvider {
// fabricate a response in a format usable by ethers users...
async constructUserOpTransactionResponse (userOp1: UserOperationStruct): Promise<TransactionResponse> {
const userOp = await resolveProperties(userOp1)
const requestId = getRequestId(userOp, this.config.entryPointAddress, this.config.chainId)
const requestId = getRequestId(userOp, this.config.entryPointAddress, this.chainId)
const waitPromise = new Promise<TransactionReceipt>((resolve, reject) => {
new UserOperationEventListener(
resolve, reject, this.entryPoint, userOp.sender, requestId, userOp.nonce
Expand All @@ -99,7 +105,7 @@ export class ERC4337EthersProvider extends BaseProvider {
gasLimit: BigNumber.from(userOp.callGasLimit), // ??
value: BigNumber.from(0),
data: hexValue(userOp.callData), // should extract the actual called method from this "execFromEntryPoint()" call
chainId: this.config.chainId,
chainId: this.chainId,
wait: async (confirmations?: number): Promise<TransactionReceipt> => {
const transactionReceipt = await waitPromise
if (userOp.initCode.length !== 0) {
Expand Down
7 changes: 6 additions & 1 deletion packages/sdk/src/HttpRpcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ export class HttpRpcClient {
}
}

async sendUserOpToBundler (userOp1: UserOperationStruct): Promise<any> {
/**
* send a UserOperation to the bundler
* @param userOp1
* @return requestId the id of this operation, for getUserOperationTransaction
*/
async sendUserOpToBundler (userOp1: UserOperationStruct): Promise<string> {
await this.initializing
const userOp = await resolveProperties(userOp1)
const hexifiedUserOp: any =
Expand Down
Loading

0 comments on commit d5071db

Please sign in to comment.