mirror of
https://github.com/getwax/bundler.git
synced 2026-01-09 15:47:56 -05:00
Publish sdk (#9)
* cleanups, comments * rename "erc4337/common" to "account-abstraction/utils" * added debug messages, comments, renamed newProvider to wrapProvider
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,20 +6,17 @@ typechain
|
||||
packages/bundler/src/typechain-types
|
||||
cache
|
||||
artifacts
|
||||
/packages/hardhat/types/
|
||||
.DS_Store
|
||||
/.idea/bundler.iml
|
||||
/.idea/modules.xml
|
||||
/packages/bundler/tsconfig.tsbuildinfo
|
||||
/packages/hardhat/deployments/
|
||||
tsconfig.tsbuildinfo
|
||||
/packages/common/src/types/
|
||||
/packages/utils/src/types/
|
||||
/.idea/codeStyles/Project.xml
|
||||
/.idea/codeStyles/codeStyleConfig.xml
|
||||
/.idea/inspectionProfiles/Project_Default.xml
|
||||
/packages/bundler/typechain-types/
|
||||
/packages/bundler/deployments/
|
||||
**/dist/
|
||||
/packages/aactf/src/types/
|
||||
/packages/bundler/src/types/
|
||||
yarn-error.log
|
||||
|
||||
10
README.md
10
README.md
@@ -9,7 +9,6 @@ A basic eip4337 "bundler"
|
||||
- eth_supportedEntryPoints to report the bundler's supported entry points
|
||||
- eth_chainId
|
||||
|
||||
|
||||
usage:
|
||||
1. start hardhat-node with `yarn hardhat-node` or geth
|
||||
In another Window:
|
||||
@@ -24,5 +23,12 @@ In another Window:
|
||||
- sends a transaction (which also creates the wallet)
|
||||
- sends another transaction, on this existing wallet
|
||||
- (uses account[0] or mnemonic file for funding, and creating deployer if needed)
|
||||
```
|
||||
|
||||
## sdk
|
||||
|
||||
SDK to create and send UserOperations
|
||||
see [SDK Readme](./packages/sdk/README.md)
|
||||
|
||||
## utils
|
||||
|
||||
internal utility methods/test contracts, used by other packages.
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
// TODO: get hardhat types from '@account-abstraction' and '@erc43337/common' package directly
|
||||
// only to import the file in hardhat compilation
|
||||
import "@erc4337/common/contracts/test/SampleRecipient.sol";
|
||||
import "@erc4337/common/contracts/test/SingletonFactory.sol";
|
||||
// import contracts to get their type info.
|
||||
import "@account-abstraction/utils/contracts/test/SampleRecipient.sol";
|
||||
import "@account-abstraction/utils/contracts/test/SingletonFactory.sol";
|
||||
import "@account-abstraction/contracts/samples/SimpleWalletDeployer.sol";
|
||||
|
||||
contract Import {
|
||||
SampleRecipient sampleRecipient;
|
||||
SingletonFactory singletonFactory;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "@account-abstraction/bundler",
|
||||
"version": "0.2.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"files": [
|
||||
"dist/src/",
|
||||
"dist/index.js",
|
||||
@@ -24,7 +25,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@account-abstraction/contracts": "^0.2.0",
|
||||
"@erc4337/common": "0.2.0",
|
||||
"@account-abstraction/utils": "0.2.0",
|
||||
"@ethersproject/abi": "^5.7.0",
|
||||
"@ethersproject/providers": "^5.7.0",
|
||||
"@types/cors": "^2.8.12",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Provider } from '@ethersproject/providers'
|
||||
import { Wallet, utils } from 'ethers'
|
||||
import { hexlify, parseEther } from 'ethers/lib/utils'
|
||||
|
||||
import { erc4337RuntimeVersion } from '@erc4337/common'
|
||||
import { erc4337RuntimeVersion } from '@account-abstraction/utils'
|
||||
|
||||
import { BundlerConfig } from './BundlerConfig'
|
||||
import { UserOpMethodHandler } from './UserOpMethodHandler'
|
||||
|
||||
@@ -5,9 +5,9 @@ import { BundlerConfig } from './BundlerConfig'
|
||||
import { EntryPoint } from './types'
|
||||
import { UserOperationStruct } from './types/contracts/BundlerHelper'
|
||||
import { hexValue, resolveProperties } from 'ethers/lib/utils'
|
||||
import { rethrowError } from '@erc4337/common'
|
||||
import { rethrowError } from '@account-abstraction/utils'
|
||||
import { calcPreVerificationGas } from '@account-abstraction/sdk/dist/src/calcPreVerificationGas'
|
||||
import { postExecutionDump } from '@erc4337/common/dist/src/postExecCheck'
|
||||
import { postExecutionDump } from '@account-abstraction/utils/dist/src/postExecCheck'
|
||||
|
||||
export class UserOpMethodHandler {
|
||||
constructor (
|
||||
|
||||
@@ -2,7 +2,7 @@ import ow from 'ow'
|
||||
import fs from 'fs'
|
||||
|
||||
import { Command } from 'commander'
|
||||
import { erc4337RuntimeVersion } from '@erc4337/common'
|
||||
import { erc4337RuntimeVersion } from '@account-abstraction/utils'
|
||||
import { ethers, Wallet } from 'ethers'
|
||||
import { BaseProvider } from '@ethersproject/providers'
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { SimpleWalletDeployer__factory } from '@account-abstraction/contracts'
|
||||
import { formatEther, keccak256, parseEther } from 'ethers/lib/utils'
|
||||
import { Command } from 'commander'
|
||||
import { erc4337RuntimeVersion } from '@erc4337/common'
|
||||
import { erc4337RuntimeVersion } from '@account-abstraction/utils'
|
||||
import fs from 'fs'
|
||||
import { HttpRpcClient } from '@account-abstraction/sdk/dist/src/HttpRpcClient'
|
||||
import { SimpleWalletAPI } from '@account-abstraction/sdk'
|
||||
|
||||
@@ -4,10 +4,10 @@ import hre, { ethers } from 'hardhat'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import * as SampleRecipientArtifact
|
||||
from '@erc4337/common/artifacts/contracts/test/SampleRecipient.sol/SampleRecipient.json'
|
||||
from '@account-abstraction/utils/artifacts/contracts/test/SampleRecipient.sol/SampleRecipient.json'
|
||||
|
||||
import { BundlerConfig } from '../src/BundlerConfig'
|
||||
import { ERC4337EthersProvider, ERC4337EthersSigner, ClientConfig, newProvider } from '@account-abstraction/sdk'
|
||||
import { ERC4337EthersProvider, ERC4337EthersSigner, ClientConfig, wrapProvider } from '@account-abstraction/sdk'
|
||||
import { Signer, Wallet } from 'ethers'
|
||||
import { runBundler } from '../src/runBundler'
|
||||
import { BundlerServer } from '../src/BundlerServer'
|
||||
@@ -86,7 +86,7 @@ describe('Flow', function () {
|
||||
|
||||
// use this as signer (instead of node's first account)
|
||||
const ownerAccount = Wallet.createRandom()
|
||||
erc4337Provider = await newProvider(
|
||||
erc4337Provider = await wrapProvider(
|
||||
ethers.provider,
|
||||
// new JsonRpcProvider('http://localhost:8545/'),
|
||||
config,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { SimpleWalletAPI } from '@account-abstraction/sdk'
|
||||
import { DeterministicDeployer } from '@account-abstraction/sdk/src/DeterministicDeployer'
|
||||
import { Wallet } from 'ethers'
|
||||
import { postExecutionDump } from '@erc4337/common/dist/src/postExecCheck'
|
||||
import { postExecutionDump } from '@account-abstraction/utils/dist/src/postExecCheck'
|
||||
|
||||
describe('UserOpMethodHandler', function () {
|
||||
const helloWorld = 'hello world'
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// define the same export types as used by export typechain/ethers
|
||||
import { BigNumberish } from 'ethers'
|
||||
import { BytesLike } from '@ethersproject/bytes'
|
||||
|
||||
export type address = string
|
||||
|
||||
export type uint256 = BigNumberish
|
||||
export type uint64 = BigNumberish
|
||||
|
||||
export type bytes = BytesLike
|
||||
export type bytes32 = BytesLike
|
||||
61
packages/sdk/README.md
Normal file
61
packages/sdk/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# SDK to create and send UserOperation
|
||||
|
||||
This package provides 2 APIs for using UserOperations:
|
||||
|
||||
- Low-level "walletAPI"
|
||||
- High-level Provider
|
||||
|
||||
|
||||
## LowLevel API:
|
||||
|
||||
### BaseWalletAPI
|
||||
|
||||
An abstract base-class to create UserOperation for a contract wallet.
|
||||
|
||||
### SimpleWalletAPI
|
||||
|
||||
An implementation of the BaseWalletAPi, for the SimpleWallet sample of account-abstraction.
|
||||
|
||||
```typescript
|
||||
owner = provider.getSigner()
|
||||
const walletAPI = new SimpleWalletAPI({
|
||||
provider,
|
||||
entryPointAddress,
|
||||
owner,
|
||||
factoryAddress
|
||||
})
|
||||
const op = await walletAPi.createSignedUserOp({
|
||||
target: recipient.address,
|
||||
data: recipient.interface.encodeFunctionData('something', ['hello'])
|
||||
})
|
||||
```
|
||||
|
||||
## High-Level Provider API
|
||||
|
||||
A simplified mode that doesn't require a different wallet extension.
|
||||
Instead, the current provider's account is used as wallet owner by calling its "Sign Message" operation.
|
||||
|
||||
This can only work for wallets that use an EIP-191 ("Ethereum Signed Message") signatures (like our sample SimpleWallet)
|
||||
Also, the UX is not great (the user is asked to sign a hash, and even the wallet address is not mentioned, only the signer)
|
||||
|
||||
```typescript
|
||||
import { wrapProvider } from '@account-abstraction/sdk'
|
||||
|
||||
//use this account as wallet-owner (which will be used to sign the requests)
|
||||
const signer = provider.getSigner()
|
||||
const config = {
|
||||
chainId: await provider.getNetwork().then(net => net.chainId),
|
||||
entryPointAddress,
|
||||
bundlerUrl: 'http://localhost:3000/rpc'
|
||||
}
|
||||
const aaProvider = await wrapProvider(provider, config, aasigner)
|
||||
const walletAddress = await aaProvider.getSigner().getAddress()
|
||||
|
||||
// send some eth to the wallet Address: wallet should have some balance to pay for its own creation, and for calling methods.
|
||||
|
||||
const myContract = new Contract(abi, aaProvider)
|
||||
|
||||
// this method will get called from the wallet address, through account-abstraction EntryPoint
|
||||
await myContract.someMethod()
|
||||
```
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"main": "./dist/src/index.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist/*",
|
||||
"dist/src",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
@@ -18,12 +18,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@account-abstraction/contracts": "^0.2.0",
|
||||
"@erc4337/common": "0.2.0",
|
||||
"@account-abstraction/utils": "0.2.0",
|
||||
"@ethersproject/abstract-provider": "^5.7.0",
|
||||
"@ethersproject/abstract-signer": "^5.7.0",
|
||||
"@ethersproject/networks": "^5.7.0",
|
||||
"@ethersproject/properties": "^5.7.0",
|
||||
"@ethersproject/providers": "^5.7.0",
|
||||
"@types/debug": "^4.1.7",
|
||||
"debug": "^4.3.4",
|
||||
"ethers": "^5.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { TransactionDetailsForUserOp } from './TransactionDetailsForUserOp'
|
||||
import { resolveProperties } from 'ethers/lib/utils'
|
||||
import { PaymasterAPI } from './PaymasterAPI'
|
||||
import { getRequestId, NotPromise, packUserOp } from '@erc4337/common'
|
||||
import { getRequestId, NotPromise, packUserOp } from '@account-abstraction/utils'
|
||||
import { calcPreVerificationGas, GasOverheads } from './calcPreVerificationGas'
|
||||
|
||||
export interface BaseApiParams {
|
||||
@@ -60,6 +60,10 @@ export abstract class BaseWalletAPI {
|
||||
}
|
||||
|
||||
async init (): Promise<this> {
|
||||
if (await this.provider.getCode(this.entryPointAddress) === '0x') {
|
||||
throw new Error(`entryPoint not deployed at ${this.entryPointAddress}`)
|
||||
}
|
||||
|
||||
await this.getWalletAddress()
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
/**
|
||||
* configuration params for wrapProvider
|
||||
*/
|
||||
export interface ClientConfig {
|
||||
paymasterAddress?: string
|
||||
/**
|
||||
* the entry point to use
|
||||
*/
|
||||
entryPointAddress: string
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
paymasterAddress?: string
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ import { ERC4337EthersSigner } from './ERC4337EthersSigner'
|
||||
import { UserOperationEventListener } from './UserOperationEventListener'
|
||||
import { HttpRpcClient } from './HttpRpcClient'
|
||||
import { EntryPoint, UserOperationStruct } from '@account-abstraction/contracts'
|
||||
import { getRequestId } from '@erc4337/common'
|
||||
import { getRequestId } from '@account-abstraction/utils'
|
||||
import { BaseWalletAPI } from './BaseWalletAPI'
|
||||
import Debug from 'debug'
|
||||
const debug = Debug('aa.provider')
|
||||
|
||||
export class ERC4337EthersProvider extends BaseProvider {
|
||||
initializedBlockNumber!: number
|
||||
@@ -43,6 +45,7 @@ export class ERC4337EthersProvider extends BaseProvider {
|
||||
}
|
||||
|
||||
async perform (method: string, params: any): Promise<any> {
|
||||
debug('perform', method, params)
|
||||
if (method === 'sendTransaction' || method === 'getTransactionReceipt') {
|
||||
// TODO: do we need 'perform' method to be available at all?
|
||||
// there is nobody out there to use it for ERC-4337 methods yet, we have nothing to override in fact.
|
||||
|
||||
@@ -21,6 +21,8 @@ export class ERC4337EthersSigner extends Signer {
|
||||
defineReadOnly(this, 'provider', erc4337provider)
|
||||
}
|
||||
|
||||
address?: string
|
||||
|
||||
// This one is called by Contract. It signs the request and passes in to Provider to be sent.
|
||||
async sendTransaction (transaction: Deferrable<TransactionRequest>): Promise<TransactionResponse> {
|
||||
const tx: TransactionRequest = await this.populateTransaction(transaction)
|
||||
@@ -78,7 +80,10 @@ export class ERC4337EthersSigner extends Signer {
|
||||
}
|
||||
|
||||
async getAddress (): Promise<string> {
|
||||
return await this.erc4337provider.getSenderWalletAddress()
|
||||
if (this.address == null) {
|
||||
this.address = await this.erc4337provider.getSenderWalletAddress()
|
||||
}
|
||||
return this.address
|
||||
}
|
||||
|
||||
async signMessage (message: Bytes | string): Promise<string> {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { ethers } from 'ethers'
|
||||
import { hexValue, resolveProperties } from 'ethers/lib/utils'
|
||||
|
||||
import { UserOperationStruct } from '@account-abstraction/contracts'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('aa.rpc')
|
||||
|
||||
export class HttpRpcClient {
|
||||
private readonly userOpJsonRpcProvider: JsonRpcProvider
|
||||
@@ -42,7 +44,10 @@ export class HttpRpcClient {
|
||||
}
|
||||
return [key, val]
|
||||
})
|
||||
.reduce((set, [k, v]) => ({ ...set, [k]: v }), {})
|
||||
.reduce((set, [k, v]) => ({
|
||||
...set,
|
||||
[k]: v
|
||||
}), {})
|
||||
|
||||
const jsonRequestData: [UserOperationStruct, string] = [hexifiedUserOp, this.entryPointAddress]
|
||||
await this.printUserOperation(jsonRequestData)
|
||||
@@ -52,7 +57,7 @@ export class HttpRpcClient {
|
||||
|
||||
private async printUserOperation ([userOp1, entryPointAddress]: [UserOperationStruct, string]): Promise<void> {
|
||||
const userOp = await resolveProperties(userOp1)
|
||||
console.log('sending eth_sendUserOperation', {
|
||||
debug('sending eth_sendUserOperation', {
|
||||
...userOp
|
||||
// initCode: (userOp.initCode ?? '').length,
|
||||
// callData: (userOp.callData ?? '').length
|
||||
|
||||
@@ -8,8 +8,17 @@ import { ERC4337EthersProvider } from './ERC4337EthersProvider'
|
||||
import { HttpRpcClient } from './HttpRpcClient'
|
||||
import { DeterministicDeployer } from './DeterministicDeployer'
|
||||
import { Signer } from '@ethersproject/abstract-signer'
|
||||
import Debug from 'debug'
|
||||
|
||||
export async function newProvider (
|
||||
const debug = Debug('aa.wrapProvider')
|
||||
|
||||
/**
|
||||
* wrap an existing provider to tunnel requests through Account Abstraction.
|
||||
* @param originalProvider the normal provider
|
||||
* @param config see ClientConfig for more info
|
||||
* @param originalSigner use this signer as the owner. of this wallet. By default, use the provider's signer
|
||||
*/
|
||||
export async function wrapProvider (
|
||||
originalProvider: JsonRpcProvider,
|
||||
config: ClientConfig,
|
||||
originalSigner: Signer = originalProvider.getSigner()
|
||||
@@ -24,6 +33,7 @@ export async function newProvider (
|
||||
factoryAddress: simpleWalletDeployer
|
||||
})
|
||||
const httpRpcClient = new HttpRpcClient(config.bundlerUrl, config.entryPointAddress, 31337)
|
||||
debug('config=', config)
|
||||
return await new ERC4337EthersProvider(
|
||||
config,
|
||||
originalSigner,
|
||||
|
||||
@@ -2,6 +2,9 @@ import { BigNumberish, Event } from 'ethers'
|
||||
import { TransactionReceipt } from '@ethersproject/providers'
|
||||
import { EntryPoint } from '@account-abstraction/contracts'
|
||||
import { defaultAbiCoder } from 'ethers/lib/utils'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('aa.listener')
|
||||
|
||||
const DEFAULT_TRANSACTION_TIMEOUT: number = 10000
|
||||
|
||||
@@ -64,7 +67,7 @@ export class UserOperationEventListener {
|
||||
|
||||
const transactionReceipt = await event.getTransactionReceipt()
|
||||
transactionReceipt.transactionHash = this.requestId
|
||||
console.log('got event with status=', event.args.success, 'gasUsed=', transactionReceipt.gasUsed)
|
||||
debug('got event with status=', event.args.success, 'gasUsed=', transactionReceipt.gasUsed)
|
||||
|
||||
// before returning the receipt, update the status from the event.
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UserOperationStruct } from '@account-abstraction/contracts'
|
||||
import { NotPromise, packUserOp } from '@erc4337/common'
|
||||
import { NotPromise, packUserOp } from '@account-abstraction/utils'
|
||||
import { arrayify, hexlify } from 'ethers/lib/utils'
|
||||
|
||||
export interface GasOverheads {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export { SimpleWalletAPI } from './SimpleWalletAPI'
|
||||
export { PaymasterAPI } from './PaymasterAPI'
|
||||
export { newProvider } from './Provider'
|
||||
export { wrapProvider } from './Provider'
|
||||
export { ERC4337EthersSigner } from './ERC4337EthersSigner'
|
||||
export { ERC4337EthersProvider } from './ERC4337EthersProvider'
|
||||
export { ClientConfig } from './ClientConfig'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { SampleRecipient__factory } from '@erc4337/common/dist/src/types'
|
||||
import { SampleRecipient__factory } from '@account-abstraction/utils/dist/src/types'
|
||||
import { ethers } from 'hardhat'
|
||||
import { hexValue } from 'ethers/lib/utils'
|
||||
import { DeterministicDeployer } from '../src/DeterministicDeployer'
|
||||
|
||||
@@ -10,9 +10,9 @@ import { expect } from 'chai'
|
||||
import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'
|
||||
import { ethers } from 'hardhat'
|
||||
import { SimpleWalletAPI } from '../src'
|
||||
import { SampleRecipient, SampleRecipient__factory } from '@erc4337/common/dist/src/types'
|
||||
import { SampleRecipient, SampleRecipient__factory } from '@account-abstraction/utils/dist/src/types'
|
||||
import { DeterministicDeployer } from '../src/DeterministicDeployer'
|
||||
import { rethrowError } from '@erc4337/common'
|
||||
import { rethrowError } from '@account-abstraction/utils'
|
||||
|
||||
const provider = ethers.provider
|
||||
const signer = provider.getSigner()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SampleRecipient, SampleRecipient__factory } from '@erc4337/common/dist/src/types'
|
||||
import { SampleRecipient, SampleRecipient__factory } from '@account-abstraction/utils/dist/src/types'
|
||||
import { ethers } from 'hardhat'
|
||||
import { ClientConfig, ERC4337EthersProvider, newProvider } from '../src'
|
||||
import { ClientConfig, ERC4337EthersProvider, wrapProvider } from '../src'
|
||||
import { EntryPoint, EntryPoint__factory } from '@account-abstraction/contracts'
|
||||
import { expect } from 'chai'
|
||||
import { parseEther } from 'ethers/lib/utils'
|
||||
@@ -23,7 +23,7 @@ describe('ERC4337EthersSigner, Provider', function () {
|
||||
bundlerUrl: ''
|
||||
}
|
||||
const aasigner = Wallet.createRandom()
|
||||
aaProvider = await newProvider(provider, config, aasigner)
|
||||
aaProvider = await wrapProvider(provider, config, aasigner)
|
||||
|
||||
const beneficiary = provider.getSigner().getAddress()
|
||||
// for testing: bypass sending through a bundler, and send directly to our entrypoint..
|
||||
|
||||
4
packages/utils/README.md
Normal file
4
packages/utils/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
account-abstraction utils
|
||||
|
||||
methods for processing UserOperations
|
||||
@@ -15,8 +15,8 @@ contract SampleRecipient {
|
||||
emit Sender(tx.origin, msg.sender, message);
|
||||
}
|
||||
|
||||
// solhint-disable-next-line
|
||||
function reverting() public {
|
||||
(this); // make it non-pure..
|
||||
revert( "test revert");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@erc4337/common",
|
||||
"name": "@account-abstraction/utils",
|
||||
"version": "0.2.0",
|
||||
"main": "./dist/src/index.js",
|
||||
"license": "MIT",
|
||||
@@ -18,7 +18,6 @@
|
||||
"dependencies": {
|
||||
"@account-abstraction/contracts": "^0.2.0",
|
||||
"@ethersproject/abi": "^5.7.0",
|
||||
"@ethersproject/bytes": "^5.7.0",
|
||||
"@ethersproject/providers": "^5.7.0",
|
||||
"@openzeppelin/contracts": "^4.7.3",
|
||||
"ethers": "^5.7.0"
|
||||
@@ -1,4 +1,4 @@
|
||||
import { arrayify, defaultAbiCoder, hexConcat, keccak256 } from 'ethers/lib/utils'
|
||||
import { defaultAbiCoder, hexConcat, keccak256 } from 'ethers/lib/utils'
|
||||
import { UserOperationStruct } from '@account-abstraction/contracts'
|
||||
import { abi as entryPointAbi } from '@account-abstraction/contracts/artifacts/IEntryPoint.json'
|
||||
import { ethers } from 'ethers'
|
||||
@@ -17,6 +17,12 @@ function encode (typevalues: Array<{ type: string, val: any }>, forSignature: bo
|
||||
return defaultAbiCoder.encode(types, values)
|
||||
}
|
||||
|
||||
/**
|
||||
* pack the userOperation
|
||||
* @param op
|
||||
* @param forSignature "true" if the hash is needed to calculate the getRequestId()
|
||||
* "false" to pack entire UserOp, for calculating the calldata cost of putting it on-chain.
|
||||
*/
|
||||
export function packUserOp (op: NotPromise<UserOperationStruct>, forSignature = true): string {
|
||||
if (forSignature) {
|
||||
// lighter signature scheme (must match UserOperation#pack): do encode a zero-length signature, but strip afterwards the appended zero-length value
|
||||
@@ -138,6 +144,15 @@ export function packUserOp (op: NotPromise<UserOperationStruct>, forSignature =
|
||||
return encode(typevalues, forSignature)
|
||||
}
|
||||
|
||||
/**
|
||||
* calculate the requestId of a given userOperation.
|
||||
* The requestId is a hash of all UserOperation fields, except the "signature" field.
|
||||
* The entryPoint uses this value in the emitted UserOperationEvent.
|
||||
* A wallet may use this value as the hash to sign (the SampleWallet uses this method)
|
||||
* @param op
|
||||
* @param entryPoint
|
||||
* @param chainId
|
||||
*/
|
||||
export function getRequestId (op: NotPromise<UserOperationStruct>, entryPoint: string, chainId: number): string {
|
||||
const userOpHash = keccak256(packUserOp(op, true))
|
||||
const enc = defaultAbiCoder.encode(
|
||||
@@ -146,10 +161,6 @@ export function getRequestId (op: NotPromise<UserOperationStruct>, entryPoint: s
|
||||
return keccak256(enc)
|
||||
}
|
||||
|
||||
export function getRequestIdForSigning (op: NotPromise<UserOperationStruct>, entryPoint: string, chainId: number): Uint8Array {
|
||||
return arrayify(getRequestId(op, entryPoint, chainId))
|
||||
}
|
||||
|
||||
const ErrorSig = keccak256(Buffer.from('Error(string)')).slice(0, 10) // 0x08c379a0
|
||||
const FailedOpSig = keccak256(Buffer.from('FailedOp(uint256,address,string)')).slice(0, 10) // 0x00fa072b
|
||||
|
||||
@@ -159,11 +170,6 @@ interface DecodedError {
|
||||
paymaster?: string
|
||||
}
|
||||
|
||||
function dump<T> (x: T): T {
|
||||
console.log('dump=', x)
|
||||
return x
|
||||
}
|
||||
|
||||
/**
|
||||
* decode bytes thrown by revert as Error(message) or FailedOp(opIndex,paymaster,message)
|
||||
*/
|
||||
@@ -171,7 +177,7 @@ export function decodeErrorReason (error: string): DecodedError | undefined {
|
||||
console.log('decoding', error)
|
||||
if (error.startsWith(ErrorSig)) {
|
||||
const [message] = defaultAbiCoder.decode(['string'], '0x' + error.substring(10))
|
||||
return dump({ message })
|
||||
return { message }
|
||||
} else if (error.startsWith(FailedOpSig)) {
|
||||
let [opIndex, paymaster, message] = defaultAbiCoder.decode(['uint256', 'address', 'string'], '0x' + error.substring(10))
|
||||
message = `FailedOp: ${message as string}`
|
||||
@@ -180,11 +186,11 @@ export function decodeErrorReason (error: string): DecodedError | undefined {
|
||||
} else {
|
||||
paymaster = undefined
|
||||
}
|
||||
return dump({
|
||||
return {
|
||||
message,
|
||||
opIndex,
|
||||
paymaster
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -2102,6 +2102,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
|
||||
integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
|
||||
|
||||
"@types/debug@^4.1.7":
|
||||
version "4.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
|
||||
integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==
|
||||
dependencies:
|
||||
"@types/ms" "*"
|
||||
|
||||
"@types/eslint-scope@^3.7.3":
|
||||
version "3.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
|
||||
@@ -2214,6 +2221,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4"
|
||||
integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==
|
||||
|
||||
"@types/ms@*":
|
||||
version "0.7.31"
|
||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||
|
||||
"@types/node@*", "@types/node@>=12.0.0":
|
||||
version "18.7.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154"
|
||||
|
||||
Reference in New Issue
Block a user