Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for EP7.0 and Etherspot's Modular accounts #126

Merged
Merged
10 changes: 5 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ jobs:
name: Publish package to npm
command: |
cd ~/etherspot-prime-sdk
npm publish
npm publish --tag v2-modular
- run:
name: Announce Publish
command: |
chmod +x .circleci/announcePublish.sh
.circleci/announcePublish.sh "Etherspot Prime SDK" "$(node -e "console.log(require('./package.json').version)")"
.circleci/announcePublish.sh "Etherspot Prime SDK Modular" "$(node -e "console.log(require('./package.json').version)")"
publish-github-release:
docker:
- image: ardd97/ghr
Expand All @@ -127,7 +127,7 @@ jobs:
command: |
PACKAGE_VERSION="$(jq .version package.json -r)"
echo $PACKAGE_VERSION
ghr -t "${GITHUB_TOKEN}" -u "${CIRCLE_PROJECT_USERNAME}" -r "${CIRCLE_PROJECT_REPONAME}" -c "${CIRCLE_SHA1}" "$PACKAGE_VERSION"
ghr -t "${GITHUB_TOKEN}" -u "${CIRCLE_PROJECT_USERNAME}" -r "${CIRCLE_PROJECT_REPONAME}" -c "${CIRCLE_SHA1}" -n "V2 Modular" "$PACKAGE_VERSION"

workflows:
version: 2.1
Expand All @@ -150,12 +150,12 @@ workflows:
filters:
branches:
only:
- master
- v2-modular
- publish-github-release:
context: general-vars
requires:
- publish-npm-package
filters:
branches:
only:
- master
- v2-modular
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# Changelog
## [1.8.2] - 2024-05-28
### New
- Added support for EP7.0 and Etherspot's Modular accounts
- Added `installModule` function to install module
- Added `uninstallModule` function to remove module

## [1.8.1] - 2024-05-14
### New
- Added signTypedData method to all providers supported
Expand Down
6 changes: 4 additions & 2 deletions examples/02-transfer-funds.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers } from 'ethers';
import { BigNumber, ethers } from 'ethers';
import { EtherspotBundler, PrimeSdk } from '../src';
import { printOp } from '../src/sdk/common/OperationUtils';
import * as dotenv from 'dotenv';
Expand Down Expand Up @@ -33,7 +33,9 @@ async function main() {
console.log('balances: ', balance);

// estimate transactions added to the batch and get the fee data for the UserOp
const op = await primeSdk.estimate();
const op = await primeSdk.estimate({
key: BigNumber.from('0x1E714c551Fe6234B6eE406899Ec3Be9234Ad2124') // multipleOwnerECDSAValidator address
});
console.log(`Estimate UserOp: ${await printOp(op)}`);

// sign the UserOp and sending to the bundler...
Expand Down
6 changes: 4 additions & 2 deletions examples/03-transfer-erc20.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ethers } from 'ethers';
import { BigNumber, ethers } from 'ethers';
import { EtherspotBundler, PrimeSdk } from '../src';
import { printOp } from '../src/sdk/common/OperationUtils';
import { ERC20_ABI } from '../src/sdk/helpers/abi/ERC20_ABI';
Expand Down Expand Up @@ -41,7 +41,9 @@ async function main() {
console.log('transactions: ', userOpsBatch);

// estimate transactions added to the batch and get the fee data for the UserOp
const op = await primeSdk.estimate();
const op = await primeSdk.estimate({
key: BigNumber.from('0x1E714c551Fe6234B6eE406899Ec3Be9234Ad2124') // multipleOwnerECDSAValidator address
kaushalrajbacancy marked this conversation as resolved.
Show resolved Hide resolved
});
console.log(`Estimate UserOp: ${await printOp(op)}`);

// sign the UserOp and sending to the bundler...
Expand Down
60 changes: 60 additions & 0 deletions examples/26-install-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { BigNumber, ethers } from 'ethers';
import { EtherspotBundler, PrimeSdk } from '../src';
import { printOp } from '../src/sdk/common/OperationUtils';
import * as dotenv from 'dotenv';
import { MODULE_TYPE, sleep } from '../src/sdk/common';

dotenv.config();

async function main() {
const bundlerApiKey = 'eyJvcmciOiI2NTIzZjY5MzUwOTBmNzAwMDFiYjJkZWIiLCJpZCI6IjMxMDZiOGY2NTRhZTRhZTM4MGVjYjJiN2Q2NDMzMjM4IiwiaCI6Im11cm11cjEyOCJ9';
lbw33 marked this conversation as resolved.
Show resolved Hide resolved

// initializating sdk...
const primeSdk = new PrimeSdk({ privateKey: process.env.WALLET_PRIVATE_KEY }, { chainId: Number(process.env.CHAIN_ID), bundlerProvider: new EtherspotBundler(Number(process.env.CHAIN_ID), bundlerApiKey) })

console.log('address: ', primeSdk.state.EOAAddress);

// get address of EtherspotWallet
const address: string = await primeSdk.getCounterFactualAddress();

console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);

const initData = ethers.utils.defaultAbiCoder.encode(
["address", "bytes"],
['0x0000000000000000000000000000000000000001', '0x00']
);

// get installData of module
const installData = await primeSdk.installModule(MODULE_TYPE.VALIDATOR, '0x6a00da4DEEf677Ad854B7c14F17Ed9312c2B5fDf', initData);

// clear the transaction batch
await primeSdk.clearUserOpsFromBatch();

// add transactions to the batch
const userOpsBatch = await primeSdk.addUserOpsToBatch({ to: address, data: installData });
console.log('transactions: ', userOpsBatch);

// sign transactions added to the batch
const op = await primeSdk.estimate({
key: BigNumber.from('0x1E714c551Fe6234B6eE406899Ec3Be9234Ad2124') // multipleOwnerECDSAValidator address
});
console.log(`Estimated UserOp: ${await printOp(op)}`);

// sign the userOps and sending to the bundler...
const uoHash = await primeSdk.send(op);
console.log(`UserOpHash: ${uoHash}`);

// get transaction hash...
console.log('Waiting for transaction...');
let userOpsReceipt = null;
const timeout = Date.now() + 60000; // 1 minute timeout
while (userOpsReceipt == null && Date.now() < timeout) {
await sleep(2);
userOpsReceipt = await primeSdk.getUserOpReceipt(uoHash);
}
console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
}

main()
.catch(console.error)
.finally(() => process.exit());
60 changes: 60 additions & 0 deletions examples/27-uninstall-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { BigNumber, ethers } from 'ethers';
import { EtherspotBundler, PrimeSdk } from '../src';
import { printOp } from '../src/sdk/common/OperationUtils';
import * as dotenv from 'dotenv';
import { MODULE_TYPE, sleep } from '../src/sdk/common';

dotenv.config();

async function main() {
const bundlerApiKey = 'eyJvcmciOiI2NTIzZjY5MzUwOTBmNzAwMDFiYjJkZWIiLCJpZCI6IjMxMDZiOGY2NTRhZTRhZTM4MGVjYjJiN2Q2NDMzMjM4IiwiaCI6Im11cm11cjEyOCJ9';

// initializating sdk...
const primeSdk = new PrimeSdk({ privateKey: process.env.WALLET_PRIVATE_KEY }, { chainId: Number(process.env.CHAIN_ID), bundlerProvider: new EtherspotBundler(Number(process.env.CHAIN_ID), bundlerApiKey) })

console.log('address: ', primeSdk.state.EOAAddress);

// get address of EtherspotWallet
const address: string = await primeSdk.getCounterFactualAddress();

console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);

const deInitData = ethers.utils.defaultAbiCoder.encode(
["address", "bytes"],
['0x0000000000000000000000000000000000000001', '0x00']
);

// get uninstallData of module
const uninstallData = await primeSdk.uninstallModule(MODULE_TYPE.VALIDATOR, '0x6a00da4DEEf677Ad854B7c14F17Ed9312c2B5fDf', deInitData);

// clear the transaction batch
await primeSdk.clearUserOpsFromBatch();

// add transactions to the batch
const userOpsBatch = await primeSdk.addUserOpsToBatch({ to: address, data: uninstallData });
console.log('transactions: ', userOpsBatch);

// sign transactions added to the batch
const op = await primeSdk.estimate({
key: BigNumber.from('0x1E714c551Fe6234B6eE406899Ec3Be9234Ad2124') // multipleOwnerECDSAValidator address
});
console.log(`Estimated UserOp: ${await printOp(op)}`);

// sign the userOps and sending to the bundler...
const uoHash = await primeSdk.send(op);
console.log(`UserOpHash: ${uoHash}`);

// get transaction hash...
console.log('Waiting for transaction...');
let userOpsReceipt = null;
const timeout = Date.now() + 60000; // 1 minute timeout
while (userOpsReceipt == null && Date.now() < timeout) {
await sleep(2);
userOpsReceipt = await primeSdk.getUserOpReceipt(uoHash);
}
console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
}

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.

4 changes: 3 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.8.1",
"version": "2.0.0",
"description": "Etherspot Prime (Account Abstraction) SDK",
"keywords": [
"ether",
Expand Down Expand Up @@ -43,6 +43,8 @@
"21-get-multiple-accounts": "./node_modules/.bin/ts-node ./examples/21-get-multiple-accounts",
"22-concurrent-userops": "./node_modules/.bin/ts-node ./examples/22-concurrent-userops",
"25-get-quotes": "./node_modules/.bin/ts-node ./examples/25-get-quotes",
"26-install-module": "./node_modules/.bin/ts-node ./examples/26-install-module",
"27-uninstall-module": "./node_modules/.bin/ts-node ./examples/27-uninstall-module",
"format": "prettier --write \"{src,test,examples}/**/*.ts\"",
"lint": "eslint \"{src,test,examples}/**/*.ts\"",
"lint-fix": "npm run lint -- --fix",
Expand Down
23 changes: 11 additions & 12 deletions src/sdk/base/BaseAccountAPI.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ethers, BigNumber, BigNumberish, TypedDataField } from 'ethers';
import { BehaviorSubject } from 'rxjs';
import { Provider } from '@ethersproject/providers';
import { EntryPoint, EntryPoint__factory, INonceManager, INonceManager__factory } from '../contracts';
import { IEntryPoint, EntryPoint__factory, INonceManager, INonceManager__factory } from '../contracts';
import { UserOperationStruct } from '../contracts/account-abstraction/contracts/core/BaseAccount';
import { TransactionDetailsForUserOp } from './TransactionDetailsForUserOp';
import { resolveProperties } from 'ethers/lib/utils';
import { PaymasterAPI } from './PaymasterAPI';
import { ErrorSubject, Exception, getUserOpHash, NotPromise, packUserOp } from '../common';
import { ErrorSubject, Exception, getUserOpHash, NotPromise, packUserOp, UserOperation } from '../common';
import { calcPreVerificationGas, GasOverheads } from './calcPreVerificationGas';
import { Factory, isWalletProvider, Network, NetworkNames, NetworkService, SdkOptions, SignMessageDto, State, StateService, validateDto, WalletProviderLike, WalletService } from '..';
import { Context } from '../context';
Expand Down Expand Up @@ -48,7 +48,7 @@ export abstract class BaseAccountAPI {
context: Context;

// entryPoint connected to "zero" address. allowed to make static calls (e.g. to getSenderAddress)
protected readonly entryPointView: EntryPoint;
protected readonly entryPointView: IEntryPoint;
protected readonly nonceManager: INonceManager;

provider: Provider;
Expand Down Expand Up @@ -239,7 +239,7 @@ export abstract class BaseAccountAPI {
/**
* return current account's nonce.
*/
protected abstract getNonce(key?: number): Promise<BigNumber>;
protected abstract getNonce(key?: BigNumber): Promise<BigNumber>;

/**
* encode the call from entryPoint through our account to the target contract.
Expand Down Expand Up @@ -365,7 +365,7 @@ export abstract class BaseAccountAPI {
* This value matches entryPoint.getUserOpHash (calculated off-chain, to avoid a view call)
* @param userOp userOperation, (signature field ignored)
*/
async getUserOpHash(userOp: UserOperationStruct): Promise<string> {
async getUserOpHash(userOp: UserOperation): Promise<string> {
const op = await resolveProperties(userOp);
const provider = this.services.walletService.getWalletProvider();
const chainId = await provider.getNetwork().then((net) => net.chainId);
Expand Down Expand Up @@ -401,11 +401,11 @@ export abstract class BaseAccountAPI {
* - if gas or nonce are missing, read them from the chain (note that we can't fill gaslimit before the account is created)
* @param info
*/
async createUnsignedUserOp(info: TransactionDetailsForUserOp, key = 0): Promise<UserOperationStruct> {
async createUnsignedUserOp(info: TransactionDetailsForUserOp, key = BigNumber.from(0)): Promise<any> {
const { callData, callGasLimit } = await this.encodeUserOpCallDataAndGasLimit(info);
const initCode = await this.getInitCode();
const factoryData = await this.getInitCode();

const initGas = await this.estimateCreationGas(initCode);
const initGas = await this.estimateCreationGas(factoryData);
const verificationGasLimit = BigNumber.from(await this.getVerificationGasLimit()).add(initGas);

let { maxFeePerGas, maxPriorityFeePerGas } = info;
Expand Down Expand Up @@ -433,7 +433,7 @@ export abstract class BaseAccountAPI {
const partialUserOp: any = {
sender: await this.getAccountAddress(),
nonce: await this.getNonce(key),
initCode,
factoryData : '0x' + factoryData.substring(42),
callData,
callGasLimit,
verificationGasLimit,
Expand Down Expand Up @@ -466,10 +466,9 @@ export abstract class BaseAccountAPI {
* Sign the filled userOp.
* @param userOp the UserOperation to sign (with signature field ignored)
*/
async signUserOp(userOp: UserOperationStruct): Promise<UserOperationStruct> {
async signUserOp(userOp: UserOperation): Promise<UserOperation> {
if (this.paymasterAPI != null) {
const paymasterAndData = await this.paymasterAPI.getPaymasterAndData(userOp);
userOp.paymasterAndData = paymasterAndData.result.paymasterAndData;
userOp.verificationGasLimit = paymasterAndData.result.verificationGasLimit;
userOp.preVerificationGas = paymasterAndData.result.preVerificationGas;
userOp.callGasLimit = paymasterAndData.result.callGasLimit;
Expand All @@ -486,7 +485,7 @@ export abstract class BaseAccountAPI {
* helper method: create and sign a user operation.
* @param info transaction details for the userOp
*/
async createSignedUserOp(info: TransactionDetailsForUserOp, key = 0): Promise<UserOperationStruct> {
async createSignedUserOp(info: TransactionDetailsForUserOp, key = BigNumber.from(0)): Promise<UserOperation> {
return await this.signUserOp(await this.createUnsignedUserOp(info, key));
}

Expand Down
Loading