Skip to content

Commit

Permalink
PRO-1851 - Support Multiple Wallet Factories (#40)
Browse files Browse the repository at this point in the history
* added multiple wallet factories

* updated package version and CHANGELOG file

* added examples

* changes as per feedback
  • Loading branch information
vignesha22 authored Oct 13, 2023
1 parent b11c30b commit 4128278
Show file tree
Hide file tree
Showing 19 changed files with 2,359 additions and 66 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Changelog
## [1.3.0] - 2023-10-12
### New
- Added latest zeroDev wallet Factory(0x5de4839a76cf55d0c90e2061ef4386d962E15ae3) and simpleAccount wallet factory(0x9406Cc6185a346906296840746125a0E44976454)
- Updated network config to include bundler urls deployed

## [1.2.11] - 2023-09-20
### Breaking Changes
- Removed paymaster initialisation from sdk init place and added to estimate step to specify how each userOp is submitted rather than global paymaster initialisation
Expand Down
File renamed without changes.
18 changes: 18 additions & 0 deletions examples/14-zeroDev-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Factory, PrimeSdk } from '../src';
import * as dotenv from 'dotenv';

dotenv.config();


async function main() {
// initializating sdk...
const primeSdk = new PrimeSdk({ privateKey: process.env.WALLET_PRIVATE_KEY }, { chainId: Number(process.env.CHAIN_ID), projectKey: '', factoryWallet: Factory.ZERO_DEV })

// get ZeroDev address...
const address: string = await primeSdk.getCounterFactualAddress();
console.log('\x1b[33m%s\x1b[0m', `ZeroDev address: ${address}`);
}

main()
.catch(console.error)
.finally(() => process.exit());
18 changes: 18 additions & 0 deletions examples/15-simpleAccount-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Factory, PrimeSdk } from '../src';
import * as dotenv from 'dotenv';

dotenv.config();


async function main() {
// initializating sdk...
const primeSdk = new PrimeSdk({ privateKey: process.env.WALLET_PRIVATE_KEY }, { chainId: Number(process.env.CHAIN_ID), projectKey: '', factoryWallet: Factory.SIMPLE_ACCOUNT })

// get SimpleAccount address...
const address: string = await primeSdk.getCounterFactualAddress();
console.log('\x1b[33m%s\x1b[0m', `SimpleAccount address: ${address}`);
}

main()
.catch(console.error)
.finally(() => process.exit());
4 changes: 2 additions & 2 deletions package-lock.json

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

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@etherspot/prime-sdk",
"version": "1.2.11",
"version": "1.3.0",
"description": "Etherspot Prime (Account Abstraction) SDK",
"keywords": [
"ether",
Expand Down Expand Up @@ -32,6 +32,9 @@
"10-advance-routes-lifi": "./node_modules/.bin/ts-node ./examples/10-advance-routes-lifi",
"11-cross-chain-quotes": "./node_modules/.bin/ts-node ./examples/11-cross-chain-quotes",
"12-add-guardians": "./node_modules/.bin/ts-node ./examples/12-add-guardians",
"13-paymaster": "./node_modules/.bin/ts-node ./examples/13-paymaster",
"14-zeroDev-address": "./node_modules/.bin/ts-node ./examples/14-zeroDev-address",
"15-simpleAccount-address": "./node_modules/.bin/ts-node ./examples/15-simpleAccount-address",
"format": "prettier --write \"{src,test,examples}/**/*.ts\"",
"lint": "eslint \"{src,test}/**/*.ts\"",
"lint-fix": "npm run lint -- --fix",
Expand Down
36 changes: 15 additions & 21 deletions src/sdk/base/EtherspotWalletAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigNumber, BigNumberish } from 'ethers';
import { BigNumber, BigNumberish, Contract } from 'ethers';
import {
EtherspotWallet,
EtherspotWallet__factory,
Expand Down Expand Up @@ -45,7 +45,7 @@ export class EtherspotWalletAPI extends BaseAccountAPI {
this.index = params.index ?? 0;
}

async _getAccountContract(): Promise<EtherspotWallet> {
async _getAccountContract(): Promise<EtherspotWallet | Contract> {
if (this.accountContract == null) {
this.accountContract = EtherspotWallet__factory.connect(await this.getAccountAddress(), this.provider);
}
Expand All @@ -57,16 +57,14 @@ export class EtherspotWalletAPI extends BaseAccountAPI {
* this value holds the "factory" address, followed by this account's information
*/
async getAccountInitCode(): Promise<string> {
if (this.factory == null) {
if (this.factoryAddress != null && this.factoryAddress !== '') {
this.factory = EtherspotWalletFactory__factory.connect(this.factoryAddress, this.provider);
} else {
throw new Error('no factory to get initCode');
}
if (this.factoryAddress != null && this.factoryAddress !== '') {
this.factory = EtherspotWalletFactory__factory.connect(this.factoryAddress, this.provider);
} else {
throw new Error('no factory to get initCode');
}

return hexConcat([
this.factory.address,
this.factoryAddress,
this.factory.interface.encodeFunctionData('createAccount', [
this.services.walletService.walletAddress,
this.index,
Expand All @@ -75,11 +73,11 @@ export class EtherspotWalletAPI extends BaseAccountAPI {
}

async getCounterFactualAddress(): Promise<string> {
this.factory = EtherspotWalletFactory__factory.connect(this.factoryAddress, this.provider);
this.accountAddress = await this.factory.getAddress(
this.services.walletService.walletAddress,
this.index,
);
this.factory = EtherspotWalletFactory__factory.connect(this.factoryAddress, this.provider);
this.accountAddress = await this.factory.getAddress(
this.services.walletService.walletAddress,
this.index,
);
return this.accountAddress;
}

Expand All @@ -89,7 +87,7 @@ export class EtherspotWalletAPI extends BaseAccountAPI {
return BigNumber.from(0);
}
const accountContract = await this._getAccountContract();
return await accountContract.getNonce();
return accountContract.getNonce();
}

/**
Expand All @@ -104,14 +102,10 @@ export class EtherspotWalletAPI extends BaseAccountAPI {
}

async signUserOpHash(userOpHash: string): Promise<string> {
return await this.services.walletService.signMessage(arrayify(userOpHash));
const signature = await this.services.walletService.signMessage(arrayify(userOpHash));
return signature;
}

// async updateEntryPoint(newEntryPoint: string) {
// const accountContract = await this._getAccountContract();
// return accountContract.updateEntryPoint(newEntryPoint);
// }

get epView() {
return this.entryPointView;
}
Expand Down
118 changes: 118 additions & 0 deletions src/sdk/base/SimpleAccountWalletAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { BigNumber, BigNumberish, Contract, ethers } from 'ethers';
import {
EntryPoint__factory,
} from '../contracts';
import { arrayify, hexConcat } from 'ethers/lib/utils';
import { BaseApiParams, BaseAccountAPI } from './BaseAccountAPI';
import { SimpleAccountAbi } from '../contracts/SimpleAccount/SimpleAccountAbi';
import { SimpleAccountFactoryAbi } from '../contracts/SimpleAccount/SimpleAccountFactoryAbi';

/**
* constructor params, added no top of base params:
* @param owner the signer object for the account owner
* @param factoryAddress address of contract "factory" to deploy new contracts (not needed if account already deployed)
* @param index nonce value used when creating multiple accounts for the same owner
*/
export interface SimpleAccountApiParams extends BaseApiParams {
factoryAddress?: string;
index?: number;
}

/**
* An implementation of the BaseAccountAPI using the SimpleAccountWallet contract.
* - contract deployer gets "entrypoint", "owner" addresses and "index" nonce
* - owner signs requests using normal "Ethereum Signed Message" (ether's signer.signMessage())
* - nonce method is "nonce()"
* - execute method is "execFromEntryPoint()"
*/
export class SimpleAccountAPI extends BaseAccountAPI {
factoryAddress?: string;
index: number;
accountAddress?: string;

/**
* our account contract.
* should support the "execFromEntryPoint" and "nonce" methods
*/
accountContract?: Contract;

factory?: Contract;

constructor(params: SimpleAccountApiParams) {
super(params);
this.factoryAddress = params.factoryAddress;
this.index = params.index ?? 0;
}

async _getAccountContract(): Promise<Contract> {
this.accountContract = new ethers.Contract(this.accountAddress, SimpleAccountAbi, this.provider);
return this.accountContract;
}

/**
* return the value to put into the "initCode" field, if the account is not yet deployed.
* this value holds the "factory" address, followed by this account's information
*/
async getAccountInitCode(): Promise<string> {
this.factory = new ethers.Contract(this.factoryAddress, SimpleAccountFactoryAbi, this.provider);

return hexConcat([
this.factoryAddress,
this.factory.interface.encodeFunctionData('createAccount', [
this.services.walletService.walletAddress,
this.index,
]),
]);
}

async getCounterFactualAddress(): Promise<string> {
try {
const initCode = await this.getAccountInitCode();
const entryPoint = EntryPoint__factory.connect(this.entryPointAddress, this.provider);
await entryPoint.callStatic.getSenderAddress(initCode);

throw new Error("getSenderAddress: unexpected result");
} catch (error: any) {
const addr = error?.errorArgs?.sender;
if (!addr) throw error;
if (addr === ethers.constants.AddressZero) throw new Error('Unsupported chain_id');
this.accountContract = new ethers.Contract(addr, SimpleAccountAbi, this.provider);
this.accountAddress = addr;
}
return this.accountAddress;
}

async getNonce(): Promise<BigNumber> {
console.log('checking nonce...');
if (await this.checkAccountPhantom()) {
return BigNumber.from(0);
}
const accountContract = await this._getAccountContract();
return accountContract.getNonce();
}

/**
* encode a method call from entryPoint to our contract
* @param target
* @param value
* @param data
*/
async encodeExecute(target: string, value: BigNumberish, data: string): Promise<string> {
const accountContract = await this._getAccountContract();
return accountContract.interface.encodeFunctionData('execute', [target, value, data]);
}

async signUserOpHash(userOpHash: string): Promise<string> {
const signature = await this.services.walletService.signMessage(arrayify(userOpHash));
return signature;
}

get epView() {
return this.entryPointView;
}

async encodeBatch(targets: string[], values: BigNumberish[], datas: string[]): Promise<string> {
const accountContract = await this._getAccountContract();
return accountContract.interface.encodeFunctionData('executeBatch', [targets, datas]);
}
}
Loading

0 comments on commit 4128278

Please sign in to comment.