├── .npmignore
├── .github-pages
├── files
│ └── CNAME
└── theme
│ ├── templates
│ ├── index.hbs
│ └── reflection.hbs
│ ├── assets
│ └── images
│ │ ├── icons.png
│ │ ├── widgets.png
│ │ ├── icons@2x.png
│ │ └── widgets@2x.png
│ ├── partials
│ ├── toc.hbs
│ ├── breadcrumb.hbs
│ ├── member.signatures.hbs
│ ├── member.reference.hbs
│ ├── typeParameters.hbs
│ ├── toc.root.hbs
│ ├── hierarchy.hbs
│ ├── analytics.hbs
│ ├── members.hbs
│ ├── members.group.hbs
│ ├── comment.hbs
│ ├── member.hbs
│ ├── footer.hbs
│ ├── member.sources.hbs
│ ├── navigation.hbs
│ ├── member.signature.title.hbs
│ ├── member.declaration.hbs
│ ├── member.getterSetter.hbs
│ ├── index.hbs
│ ├── header.hbs
│ ├── typeAndParent.hbs
│ ├── member.signature.body.hbs
│ └── parameter.hbs
│ └── layouts
│ └── default.hbs
├── src
├── index.ts
├── sdk
│ ├── dto
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ └── validate-dto.ts
│ │ ├── index.ts
│ │ ├── sign-message.dto.ts
│ │ └── validators
│ │ │ ├── index.ts
│ │ │ ├── is-hex32.validator.ts
│ │ │ ├── is-address.validator.ts
│ │ │ ├── is-url.validator.ts
│ │ │ ├── is-hex.validator.ts
│ │ │ ├── is-big-numberish.validator.ts
│ │ │ └── is-bytes-like.validator.ts
│ ├── common
│ │ ├── transformers
│ │ │ ├── index.ts
│ │ │ └── transform-big-number.ts
│ │ ├── exceptions
│ │ │ ├── exception.ts
│ │ │ ├── index.ts
│ │ │ ├── interfaces.ts
│ │ │ └── validation.exception.ts
│ │ ├── classes
│ │ │ ├── index.ts
│ │ │ ├── synchronized.ts
│ │ │ ├── base-class.ts
│ │ │ └── pagination-result.ts
│ │ ├── utils
│ │ │ ├── sleep.ts
│ │ │ ├── addresses-equal.ts
│ │ │ ├── parse-json.ts
│ │ │ ├── is-url.ts
│ │ │ ├── index.ts
│ │ │ ├── json-utils.ts
│ │ │ ├── get-bytes.ts
│ │ │ ├── bignumber-utils.ts
│ │ │ ├── userop-utils.ts
│ │ │ ├── deep-compare.ts
│ │ │ └── hashing-utils.ts
│ │ ├── rxjs
│ │ │ ├── index.ts
│ │ │ ├── unique.subject.ts
│ │ │ ├── distinct-unique-key.operator.ts
│ │ │ ├── synchronized.subject.ts
│ │ │ ├── object.subject.ts
│ │ │ └── error.subject.ts
│ │ ├── interfaces.ts
│ │ ├── index.ts
│ │ ├── constants.ts
│ │ ├── types.ts
│ │ ├── OperationUtils.ts
│ │ ├── getInitData.ts
│ │ ├── getGasFee.ts
│ │ ├── service.ts
│ │ ├── getInstalledModules.ts
│ │ └── abis.ts
│ ├── bundler
│ │ ├── index.ts
│ │ ├── providers
│ │ │ ├── index.ts
│ │ │ ├── GenericBundler.ts
│ │ │ └── EtherspotBundler.ts
│ │ └── interface.ts
│ ├── network
│ │ ├── utils
│ │ │ ├── index.ts
│ │ │ ├── network-name-to-chain-id.ts
│ │ │ └── prepare-network-name.ts
│ │ ├── index.ts
│ │ ├── interfaces.ts
│ │ └── network.service.ts
│ ├── wallet
│ │ ├── providers
│ │ │ ├── utils
│ │ │ │ ├── index.ts
│ │ │ │ ├── is-wallet-connect-provider.ts
│ │ │ │ └── is-wallet-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── dynamic.wallet-provider.ts
│ │ │ ├── interfaces.ts
│ │ │ └── walletClient.provider.ts
│ │ ├── index.ts
│ │ └── interfaces.ts
│ ├── SessionKeyValidator
│ │ ├── index.ts
│ │ ├── constants.ts
│ │ └── interfaces.ts
│ ├── pulse
│ │ ├── index.ts
│ │ ├── constants.ts
│ │ ├── interfaces.ts
│ │ └── utils.ts
│ ├── types
│ │ ├── viem-rpc.ts
│ │ └── user-operation-types.ts
│ ├── index.ts
│ ├── base
│ │ ├── index.ts
│ │ ├── EtherspotWalletFactory.ts
│ │ ├── TransactionDetailsForUserOp.ts
│ │ ├── ClientConfig.ts
│ │ ├── PaymasterAPI.ts
│ │ ├── Bootstrap.ts
│ │ └── calcPreVerificationGas.ts
│ ├── helpers
│ │ └── abi
│ │ │ └── ERC20_ABI.ts
│ ├── errorHandler
│ │ └── errorHandler.service.ts
│ ├── interfaces.ts
│ └── context.ts
├── node.ts
└── browser.ts
├── example.env
├── bun.lockb
├── funding.json
├── .eslintignore
├── .prettierrc
├── tsconfig
├── tsconfig.esm.json
├── tsconfig.cjs.json
├── tsconfig.types.json
├── tsconfig.default.json
└── tsconfig.base.json
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── feature_request.md
│ └── bug_report.yml
├── pull_request_template.md
└── workflows
│ ├── deploy-docs.yml
│ └── check-package-version.yml
├── typedoc.json
├── examples
├── scripts
│ ├── commands
│ │ ├── address.ts
│ │ ├── transfer.ts
│ │ ├── NFTTransfer.ts
│ │ ├── erc20Transfer.ts
│ │ ├── erc20Approve.ts
│ │ ├── index.ts
│ │ └── batchErc20Transfer.ts
│ └── init.ts
├── basics
│ ├── get-nonce.ts
│ ├── get-address.ts
│ ├── get-gas-fees.ts
│ ├── get-counterfactual-address.ts
│ ├── get-multiple-accounts.ts
│ ├── transfer-funds.ts
│ ├── callGasLimit.ts
│ ├── transfer-nft.ts
│ ├── verify-signatures.ts
│ ├── add-guardians.ts
│ ├── transfer-erc20.ts
│ ├── custom-chain.ts
│ └── concurrent-userops.ts
├── turnkey
│ ├── create-wallet.ts
│ ├── get-counterfactual-address.ts
│ └── transfer-funds.ts
├── modules
│ ├── list-modules.ts
│ ├── is-module-initialised.ts
│ ├── is-module-installed.ts
│ ├── get-previous-address.ts
│ ├── list-paginated-modules.ts
│ ├── install-module.ts
│ ├── generate-module-uninstall-deinitdata.ts
│ ├── uninstall-module.ts
│ └── install-hook.ts
├── helpers
│ └── sdk-helper.ts
├── sessionkeys
│ ├── get-associated-session-keys.ts
│ ├── disable-sessionkey-module.ts
│ ├── rotate-sessionkey-module.ts
│ ├── enable-sessionkey-module.ts
│ └── transfer-erc20-session-key.ts
├── pulse
│ ├── utils.ts
│ ├── install-pulse-modules.ts
│ └── install-latest-pulse-modules.ts
└── paymaster
│ └── paymaster.ts
├── .gitignore
├── SECURITY.md
├── .eslintrc.js
├── .circleci
└── announcePublish.sh
├── LICENSE
├── DEPLOYMENT_COSTS.md
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | /*
2 | !/dist
3 | !/LICENSE
--------------------------------------------------------------------------------
/.github-pages/files/CNAME:
--------------------------------------------------------------------------------
1 | sdk.etherspot.io
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './node.js';
2 |
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | WALLET_PRIVATE_KEY=0x...
2 | CHAIN_ID=
--------------------------------------------------------------------------------
/src/sdk/dto/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './validate-dto.js';
2 |
--------------------------------------------------------------------------------
/src/node.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 |
3 | export * from './sdk/index.js';
4 |
--------------------------------------------------------------------------------
/src/sdk/common/transformers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './transform-big-number.js';
2 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etherspot/etherspot-modular-sdk/HEAD/bun.lockb
--------------------------------------------------------------------------------
/src/browser.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 |
3 | export * from './sdk/index.js';
4 |
--------------------------------------------------------------------------------
/src/sdk/bundler/index.ts:
--------------------------------------------------------------------------------
1 | export * from './interface.js';
2 | export * from './providers/index.js';
--------------------------------------------------------------------------------
/src/sdk/common/exceptions/exception.ts:
--------------------------------------------------------------------------------
1 | export class Exception extends Error {
2 | //
3 | }
4 |
--------------------------------------------------------------------------------
/src/sdk/common/classes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './synchronized.js';
2 | export * from './base-class.js';
--------------------------------------------------------------------------------
/src/sdk/dto/index.ts:
--------------------------------------------------------------------------------
1 | export * from './sign-message.dto.js';
2 | export * from './utils/index.js';
3 |
--------------------------------------------------------------------------------
/src/sdk/bundler/providers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './GenericBundler.js';
2 | export * from './EtherspotBundler.js';
--------------------------------------------------------------------------------
/src/sdk/network/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './prepare-network-name.js';
2 | export * from './network-name-to-chain-id.js';
3 |
--------------------------------------------------------------------------------
/src/sdk/wallet/providers/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './is-wallet-provider.js';
2 | export * from './is-wallet-connect-provider.js';
3 |
--------------------------------------------------------------------------------
/.github-pages/theme/templates/index.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#markdown}}{{{model.readme}}}{{/markdown}}
3 |
--------------------------------------------------------------------------------
/funding.json:
--------------------------------------------------------------------------------
1 | {
2 | "opRetro": {
3 | "projectId": "0x79e8bf2d699790b563aa57fd34da1722bea3191ab5ff0601ba56020687702ab9"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/sdk/wallet/index.ts:
--------------------------------------------------------------------------------
1 | export * from './interfaces.js';
2 | export * from './providers/index.js';
3 | export * from './wallet.service.js';
4 |
--------------------------------------------------------------------------------
/src/sdk/common/classes/synchronized.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | */
4 | export abstract class Synchronized {
5 | synchronizedAt?: Date;
6 | }
7 |
--------------------------------------------------------------------------------
/.github-pages/theme/assets/images/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etherspot/etherspot-modular-sdk/HEAD/.github-pages/theme/assets/images/icons.png
--------------------------------------------------------------------------------
/src/sdk/SessionKeyValidator/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SessionKeyValidator.js';
2 | export * from './interfaces.js';
3 | export * from './constants.js';
--------------------------------------------------------------------------------
/.github-pages/theme/assets/images/widgets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etherspot/etherspot-modular-sdk/HEAD/.github-pages/theme/assets/images/widgets.png
--------------------------------------------------------------------------------
/src/sdk/common/exceptions/index.ts:
--------------------------------------------------------------------------------
1 | export * from './exception.js';
2 | export * from './interfaces.js';
3 | export * from './validation.exception.js';
4 |
--------------------------------------------------------------------------------
/.github-pages/theme/assets/images/icons@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etherspot/etherspot-modular-sdk/HEAD/.github-pages/theme/assets/images/icons@2x.png
--------------------------------------------------------------------------------
/.github-pages/theme/assets/images/widgets@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etherspot/etherspot-modular-sdk/HEAD/.github-pages/theme/assets/images/widgets@2x.png
--------------------------------------------------------------------------------
/src/sdk/bundler/interface.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface BundlerProvider {
3 | readonly url: string;
4 | }
5 |
6 | export type BundlerProviderLike = BundlerProvider;
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | coverage
4 | __snapshots__
5 | src/sdk/contracts/**/*.ts
6 | src/sdk/base/HttpRpcClient.ts
7 | src/sdk/state/state.service.ts
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 120,
5 | "jsxBracketSameLine": false,
6 | "arrowParens": "always"
7 | }
--------------------------------------------------------------------------------
/src/sdk/network/index.ts:
--------------------------------------------------------------------------------
1 | export * from './interfaces.js';
2 | export * from './network.service.js';
3 | export * from './utils/index.js';
4 | export * from './constants.js';
5 |
--------------------------------------------------------------------------------
/src/sdk/pulse/index.ts:
--------------------------------------------------------------------------------
1 | export { Pulse } from './pulse.js';
2 | export * from './interfaces.js';
3 | export { getHookMultiPlexerInitData, HookMultiplexer } from './utils.js';
4 |
--------------------------------------------------------------------------------
/src/sdk/pulse/constants.ts:
--------------------------------------------------------------------------------
1 | export enum HookType {
2 | GLOBAL = '0x00',
3 | DELEGATECALL = '0x01',
4 | VALUE = '0x02',
5 | SIG = '0x03',
6 | TARGET_SIG = '0x04',
7 | }
8 |
--------------------------------------------------------------------------------
/src/sdk/SessionKeyValidator/constants.ts:
--------------------------------------------------------------------------------
1 | export const PERMISSIONS_URL = 'https://qa-permissions.etherspot.io';
2 |
3 | export enum KeyStore {
4 | AWS = 'AWS',
5 | TEE = 'TEE',
6 | }
--------------------------------------------------------------------------------
/src/sdk/common/utils/sleep.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | */
4 | export function sleep(sec: number): Promise {
5 | return new Promise((resolve) => setTimeout(resolve, sec * 1000));
6 | }
7 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/addresses-equal.ts:
--------------------------------------------------------------------------------
1 | export function addressesEqual(address1: string, address2: string): boolean {
2 | return (address1 || '').toLowerCase() === (address2 || '').toLowerCase();
3 | }
4 |
--------------------------------------------------------------------------------
/src/sdk/common/classes/base-class.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | */
4 | export class BaseClass {
5 | constructor(raw?: Partial) {
6 | if (raw) {
7 | Object.assign(this, raw);
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/sdk/common/classes/pagination-result.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | */
4 | export abstract class PaginationResult {
5 | items?: T[];
6 |
7 | currentPage: number;
8 |
9 | nextPage: number;
10 | }
11 |
--------------------------------------------------------------------------------
/src/sdk/common/exceptions/interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface ValidationError {
2 | property: string;
3 | value?: any;
4 | constraints?: {
5 | [type: string]: string;
6 | };
7 | children?: ValidationError[];
8 | }
9 |
--------------------------------------------------------------------------------
/src/sdk/common/rxjs/index.ts:
--------------------------------------------------------------------------------
1 | export * from './distinct-unique-key.operator.js';
2 | export * from './object.subject.js';
3 | export * from './error.subject.js';
4 | export * from './synchronized.subject.js';
5 | export * from './unique.subject.js';
--------------------------------------------------------------------------------
/src/sdk/wallet/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Chain } from "viem";
2 |
3 | export interface Wallet {
4 | address: string;
5 | providerType: string;
6 | }
7 |
8 | export interface WalletOptions {
9 | provider?: string;
10 | chain?: Chain;
11 | }
12 |
--------------------------------------------------------------------------------
/src/sdk/bundler/providers/GenericBundler.ts:
--------------------------------------------------------------------------------
1 | import { BundlerProvider } from "../interface.js";
2 |
3 |
4 | export class GenericBundler implements BundlerProvider {
5 | readonly url: string;
6 | constructor(bundlerUrl: string) {
7 | this.url = bundlerUrl;
8 | }
9 | }
--------------------------------------------------------------------------------
/src/sdk/dto/sign-message.dto.ts:
--------------------------------------------------------------------------------
1 | import { type BytesLike } from '../common/types.js';
2 | import { IsBytesLike } from './validators/index.js';
3 |
4 | export class SignMessageDto {
5 | @IsBytesLike({
6 | acceptText: true,
7 | })
8 | message: BytesLike;
9 | }
10 |
--------------------------------------------------------------------------------
/src/sdk/dto/validators/index.ts:
--------------------------------------------------------------------------------
1 | export * from './is-address.validator.js';
2 | export * from './is-big-numberish.validator.js';
3 | export * from './is-bytes-like.validator.js';
4 | export * from './is-hex.validator.js';
5 | export * from './is-hex32.validator.js';
6 | export * from './is-url.validator.js';
7 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/parse-json.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @ignore
3 | */
4 | export function parseJson(raw: string, defaultValue: T = null): T {
5 | let result: T;
6 | try {
7 | result = JSON.parse(raw);
8 | } catch (err) {
9 | result = defaultValue;
10 | }
11 |
12 | return result;
13 | }
14 |
--------------------------------------------------------------------------------
/src/sdk/network/utils/network-name-to-chain-id.ts:
--------------------------------------------------------------------------------
1 | import { NetworkNames, NETWORK_NAME_TO_CHAIN_ID } from '../constants.js';
2 |
3 | export function networkNameToChainId(networkName: NetworkNames): number {
4 | return NETWORK_NAME_TO_CHAIN_ID[networkName] || null;
5 | }
6 |
7 | export { NETWORK_NAME_TO_CHAIN_ID };
8 |
--------------------------------------------------------------------------------
/src/sdk/dto/validators/is-hex32.validator.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions } from 'class-validator';
2 | import { IsHex } from './is-hex.validator.js';
3 |
4 | export function IsHex32(options: ValidationOptions = {}) {
5 | return IsHex(
6 | {
7 | size: 32,
8 | },
9 | options,
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/sdk/wallet/providers/utils/is-wallet-connect-provider.ts:
--------------------------------------------------------------------------------
1 | import { EthereumProvider, WalletProviderLike } from '../interfaces.js';
2 |
3 | export function isWalletConnectProvider(provider: WalletProviderLike): boolean {
4 | return typeof provider === 'object' && (provider as EthereumProvider)?.isWalletConnect;
5 | }
6 |
--------------------------------------------------------------------------------
/src/sdk/common/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { BigNumberish } from "../types/bignumber.js";
2 |
3 | export interface BatchUserOpsRequest {
4 | to: string[];
5 | data: string[];
6 | value: BigNumberish[];
7 | }
8 |
9 | export interface UserOpsRequest {
10 | to: string;
11 | data?: string;
12 | value?: BigNumberish;
13 | }
14 |
--------------------------------------------------------------------------------
/src/sdk/pulse/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Hex } from 'viem';
2 |
3 | export interface PulseConfig {
4 | credibleAccountModuleAddress: string;
5 | resourceLockValidatorAddress: string;
6 | uninstallOldHookMultiplexer?: boolean;
7 | }
8 |
9 | export interface SigHookInit {
10 | sig: string;
11 | subHooks: Hex[];
12 | }
13 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/is-url.ts:
--------------------------------------------------------------------------------
1 | import validator from 'validator';
2 |
3 | /**
4 | * @ignore
5 | */
6 | export function isUrl(url: string): boolean {
7 | return validator.isURL(url, {
8 | protocols: ['http', 'https'],
9 | require_tld: false,
10 | require_host: true,
11 | require_protocol: true,
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
3 | "extends": "./tsconfig.default.json",
4 | "compilerOptions": {
5 | "module": "NodeNext",
6 | "moduleResolution": "NodeNext",
7 | "outDir": "../dist/esm",
8 | }
9 | }
--------------------------------------------------------------------------------
/src/sdk/types/viem-rpc.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface ViemRpcRequestError {
3 | details: string;
4 | metaMessages: string[];
5 | shortMessage: string;
6 | name: string;
7 | version: string;
8 | message: string;
9 | cause: {
10 | message: string;
11 | code: number;
12 | };
13 | code: number;
14 | }
--------------------------------------------------------------------------------
/.github-pages/theme/partials/toc.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{{wbr title}}}
3 | {{#if children}}
4 |
5 | {{#each children}}
6 | {{> toc}}
7 | {{/each}}
8 |
9 | {{/if}}
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Documentation
4 | url: https://etherspot.fyi/introduction
5 | about: For more detailed guide follow our docs
6 | - name: Join our Discord community
7 | url: https://discord.etherspot.io
8 | about: Participate in our thriving community of contributors, users, and supporters.
9 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './deep-compare.js';
2 | export * from './is-url.js';
3 | export * from './sleep.js';
4 | export * from './viem-utils.js';
5 | export * from './bignumber-utils.js';
6 | export * from './hashing-utils.js';
7 | export * from './json-utils.js';
8 | export * from './userop-utils.js';
9 | export * from './get-bytes.js';
10 | export * from './hexlify.js';
11 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/breadcrumb.hbs:
--------------------------------------------------------------------------------
1 | {{#if parent}}
2 | {{#with parent}}{{> breadcrumb}}{{/with}}
3 |
4 | {{#if url}}
5 | {{name}}
6 | {{else}}
7 | {{name}}
8 | {{/if}}
9 |
10 | {{else}}
11 | {{#if url}}
12 |
13 | Home
14 |
15 | {{/if}}
16 | {{/if}}
17 |
--------------------------------------------------------------------------------
/tsconfig/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
3 | "extends": "./tsconfig.default.json",
4 | "compilerOptions": {
5 | "module": "commonjs",
6 | "moduleResolution": "Node",
7 | "outDir": "../dist/cjs",
8 | "removeComments": true,
9 | "verbatimModuleSyntax": false
10 | }
11 | }
--------------------------------------------------------------------------------
/src/sdk/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ERC4337Utils.js';
2 | export * from './getGasFee.js';
3 | export * from './service.js';
4 | export * from './rxjs/index.js';
5 | export * from './utils/index.js';
6 | export * from './classes/index.js';
7 | export * from './exceptions/index.js';
8 | export * from './constants.js';
9 | export * from './interfaces.js';
10 | export * from './transformers/index.js';
11 | export * from './types.js';
12 |
--------------------------------------------------------------------------------
/src/sdk/common/rxjs/unique.subject.ts:
--------------------------------------------------------------------------------
1 | import { BehaviorSubject } from 'rxjs';
2 | import { deepCompare } from '../utils/index.js';
3 |
4 | /**
5 | * @ignore
6 | */
7 | export class UniqueSubject extends BehaviorSubject {
8 | constructor(value: T = null) {
9 | super(value);
10 | }
11 |
12 | next(value: T): void {
13 | if (!deepCompare(this.value, value)) {
14 | super.next(value);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/sdk/index.ts:
--------------------------------------------------------------------------------
1 | import { SessionKeyValidator } from './SessionKeyValidator/index.js';
2 | import { ModularSdk } from './sdk.js';
3 |
4 | export * from './dto/index.js';
5 | export * from './interfaces.js';
6 | export * from './network/index.js';
7 | export * from './bundler/index.js';
8 | export * from './common/index.js';
9 | export * from './wallet/index.js';
10 |
11 | export { ModularSdk, SessionKeyValidator };
12 | export default ModularSdk;
--------------------------------------------------------------------------------
/src/sdk/common/utils/json-utils.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export function parseJson(raw: string, defaultValue?: T): T | undefined {
4 | let result: T | undefined;
5 | try {
6 | result = JSON.parse(raw);
7 | } catch (err) {
8 | result = defaultValue;
9 | }
10 |
11 | return result;
12 | }
13 |
14 | export function stringifyJson(value: T, space?: number): string {
15 | return JSON.stringify(value, null, space);
16 | }
17 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.signatures.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#each signatures}}
3 | - {{#compact}}{{> member.signature.title }}{{/compact}}
4 | {{/each}}
5 |
6 |
7 |
8 | {{#each signatures}}
9 | -
10 | {{> member.signature.body }}
11 |
12 | {{/each}}
13 |
14 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.reference.hbs:
--------------------------------------------------------------------------------
1 | {{#with tryGetTargetReflectionDeep}}
2 | {{#ifCond ../name '===' name}}
3 | Re-exports {{name}}
4 | {{else if flags.isExported}}
5 | Renames and re-exports {{name}}
6 | {{else}}
7 | Renames and exports {{name}}
8 | {{/ifCond}}
9 | {{else}}
10 | Re-exports {{name}}
11 | {{/with}}
12 |
--------------------------------------------------------------------------------
/tsconfig/tsconfig.types.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
3 | "extends": "./tsconfig.default.json",
4 | "compilerOptions": {
5 | "moduleResolution": "NodeNext",
6 | "module": "NodeNext",
7 | "outDir": "../dist/esm",
8 | "declarationDir": "../dist/types",
9 | "emitDeclarationOnly": true,
10 | "declaration": true,
11 | "declarationMap": true,
12 | }
13 | }
--------------------------------------------------------------------------------
/.github-pages/theme/partials/typeParameters.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#each typeParameters}}
3 | -
4 |
{{#compact}}
5 | {{name}}
6 | {{#if type}}
7 | :
8 | {{#with type}}{{> type}}{{/with}}
9 | {{/if}}
10 | {{/compact}}
11 | {{> comment}}
12 |
13 | {{/each}}
14 |
15 |
--------------------------------------------------------------------------------
/src/sdk/base/index.ts:
--------------------------------------------------------------------------------
1 | export { EtherspotWalletAPI, type ModuleInfo } from './EtherspotWalletAPI.js';
2 | export { PaymasterAPI } from './PaymasterAPI.js';
3 | export type { ClientConfig } from './ClientConfig.js';
4 | export { HttpRpcClient } from './HttpRpcClient.js';
5 | export { DeterministicDeployer } from './DeterministicDeployer.js';
6 | export { VerifyingPaymasterAPI } from './VerifyingPaymasterAPI.js';
7 | export * from './calcPreVerificationGas.js';
8 | export * from './TransactionDetailsForUserOp.js';
9 |
--------------------------------------------------------------------------------
/src/sdk/helpers/abi/ERC20_ABI.ts:
--------------------------------------------------------------------------------
1 | export const ERC20_ABI = [
2 | 'function balanceOf(address owner) view returns (uint256)',
3 | 'function decimals() view returns (uint8)',
4 | 'function symbol() view returns (string)',
5 | 'function transfer(address to, uint amount) returns (bool)',
6 | 'function approve(address spender, uint256 amount) returns (bool)',
7 | 'function mint(address to, uint256 amount) public',
8 | 'event Transfer(address indexed from, address indexed to, uint amount)',
9 | ];
10 |
--------------------------------------------------------------------------------
/src/sdk/wallet/providers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dynamic.wallet-provider.js';
2 | export * from './interfaces.js';
3 | export * from './key.wallet-provider.js';
4 | export * from './meta-mask.wallet-provider.js';
5 | export * from './utils/index.js';
6 | export * from './wallet-connect.wallet-provider.js';
7 | export * from './wallet-connect-2.wallet-provider.js';
8 | export * from './web3.wallet-provider.js';
9 | export * from './web3eip1193.wallet-provider.js';
10 | export * from './walletClient.provider.js';
11 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/toc.root.hbs:
--------------------------------------------------------------------------------
1 | {{#if isInPath}}
2 |
3 |
4 | {{/if}}
5 | -
6 | {{{wbr title}}}
7 | {{#if children}}
8 |
9 | {{#each children}}
10 | {{> toc}}
11 | {{/each}}
12 |
13 | {{/if}}
14 |
15 | {{#if isInPath}}
16 |
17 |
18 | {{/if}}
19 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/hierarchy.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#each types}}
3 | -
4 | {{#if ../isTarget}}
5 | {{this}}
6 | {{else}}
7 | {{#compact}}{{> type}}{{/compact}}
8 | {{/if}}
9 |
10 | {{#if @last}}
11 | {{#with ../next}}
12 | {{> hierarchy}}
13 | {{/with}}
14 | {{/if}}
15 |
16 | {{/each}}
17 |
18 |
--------------------------------------------------------------------------------
/tsconfig/tsconfig.default.json:
--------------------------------------------------------------------------------
1 | {
2 | // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files.
3 | "extends": "./tsconfig.base.json",
4 | "include": [
5 | "../src"
6 | ],
7 | "exclude": [
8 | "../examples/**/*.ts",
9 | "../test/**/*.ts",
10 | "../src/**/*.spec.ts",
11 | "../src/**/__mocks__/*.ts",
12 | "../src/testing/**/*.ts"
13 | ],
14 | "compilerOptions": {
15 | "sourceMap": true,
16 | "rootDir": "../src"
17 | }
18 | }
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "inputFiles": [
3 | "./src/index.ts"
4 | ],
5 | "readme": "./README.md",
6 | "out": "./docs",
7 | "mode": "file",
8 | "exclude": [
9 | "**/__mocks__/*.*",
10 | "**/testing/*.*",
11 | "**/*+(index|.spec|.e2e).ts"
12 | ],
13 | "theme": "./.github-pages/theme",
14 | "name": "Etherspot Modular reference docs",
15 | "hideGenerator": true,
16 | "disableSources": true,
17 | "excludePrivate": true,
18 | "excludeProtected": true,
19 | "stripInternal": true
20 | }
--------------------------------------------------------------------------------
/src/sdk/common/rxjs/distinct-unique-key.operator.ts:
--------------------------------------------------------------------------------
1 | import { OperatorFunction, distinctUntilKeyChanged, map, pluck } from 'rxjs';
2 | import { deepCompare } from '../utils/index.js';
3 |
4 | /**
5 | * @ignore
6 | */
7 | export function distinctUniqueKey(key: K): OperatorFunction {
8 | return (input$) =>
9 | input$.pipe(
10 | map((value) => {
11 | return (value ? value : { [key]: null }) as T;
12 | }),
13 | distinctUntilKeyChanged(key, deepCompare),
14 | pluck(key),
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/analytics.hbs:
--------------------------------------------------------------------------------
1 | {{#if settings.gaID}}
2 |
11 | {{/if}}
--------------------------------------------------------------------------------
/src/sdk/common/exceptions/validation.exception.ts:
--------------------------------------------------------------------------------
1 | import { Exception } from './exception.js';
2 | import { ValidationError } from './interfaces.js';
3 |
4 | export class ValidationException extends Exception {
5 | static throw(property: string, constraints: { [key: string]: string }) {
6 | const validationError: ValidationError = {
7 | property,
8 | constraints,
9 | };
10 |
11 | throw new ValidationException([validationError]);
12 | }
13 |
14 | constructor(public errors: ValidationError[]) {
15 | super(JSON.stringify(errors, null, 2));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/scripts/commands/address.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import config from "../../config.json";
3 | import { generateModularSDKInstance } from "../../helpers/sdk-helper";
4 |
5 | export default async function main() {
6 | const bundlerApiKey = 'etherspot_public_key';
7 | // initializating sdk...
8 | const modularSdk = generateModularSDKInstance(
9 | config.signingKey,
10 | 80001,
11 | bundlerApiKey
12 | );// Testnets dont need apiKey on bundlerProvider
13 | const address = await modularSdk.getCounterFactualAddress();
14 | console.log(`Etherspot address: ${address}`);
15 | }
16 |
--------------------------------------------------------------------------------
/src/sdk/network/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Chain } from 'viem/chains';
2 | import { NetworkNames } from './constants.js';
3 |
4 | export interface Network {
5 | name: NetworkNames;
6 | chainId: number;
7 | }
8 |
9 | export interface NetworkConfig {
10 | chainId: number;
11 | chain: Chain;
12 | bundler: string;
13 | contracts: {
14 | entryPoint: string;
15 | walletFactory: string;
16 | bootstrap: string;
17 | multipleOwnerECDSAValidator: string;
18 | erc20SessionKeyValidator: string;
19 | hookMultiPlexer: string;
20 | hookMultiPlexerV2: string;
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/src/sdk/errorHandler/errorHandler.service.ts:
--------------------------------------------------------------------------------
1 | import { entryPointErrorMsg, errorMsg } from "./constants.js";
2 |
3 | export class ErrorHandler extends Error {
4 | public rawError: string = null;
5 | constructor(public error: string, public code?: number) {
6 | super(error);
7 | this.rawError = error;
8 | this.code = code;
9 | if (code) {
10 | this.message = errorMsg[code.toString()];
11 | if (entryPointErrorMsg[error]) {
12 | this.message = entryPointErrorMsg[error];
13 | }
14 |
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS
2 | .DS_Store
3 |
4 | # IDEs
5 | /.idea
6 | .project
7 | .classpath
8 | .c9/
9 | *.launch
10 | .settings/
11 | *.sublime-workspace
12 | *.code-workspace
13 | .vscode/*
14 | !.vscode/settings.json
15 | !.vscode/tasks.json
16 | !.vscode/launch.json
17 | !.vscode/extensions.json
18 | workspace.code-workspace
19 |
20 | # build
21 | dist
22 |
23 | # node
24 | node_modules
25 |
26 | # develop
27 | .graphqlconfig
28 | .cache
29 | bak
30 | docs
31 | demo
32 | test.js
33 | yarn.lock
34 |
35 | # .env file
36 | .env
37 |
38 | # config
39 | examples/config.json
40 |
41 | # contracts exposed
42 | src/contracts/factories/contracts-exposed/
43 |
--------------------------------------------------------------------------------
/src/sdk/base/EtherspotWalletFactory.ts:
--------------------------------------------------------------------------------
1 | import { encodeFunctionData, parseAbi } from 'viem';
2 |
3 | export class EtherspotWalletFactoryAPI {
4 | static createAccount(
5 | factoryAddress: string,
6 | registry: string,
7 | owner: string,
8 | salt: number,
9 | ): string {
10 | const abi = ['function createAccount(address, _registry, address owner, uint256 salt) returns(address)'];
11 | const encodedData = encodeFunctionData({
12 | functionName: 'createAccount',
13 | abi: parseAbi(abi),
14 | args: [registry,
15 | owner,
16 | salt,],
17 | });
18 | return encodedData;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/sdk/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Chain } from 'viem';
2 | import { BundlerProviderLike } from './bundler/index.js';
3 |
4 | export interface PaymasterApi {
5 | url: string;
6 | context?: any;
7 | }
8 |
9 | export enum Factory {
10 | ETHERSPOT = 'etherspot',
11 | }
12 |
13 | export interface SdkOptions {
14 | chainId: number;
15 | chain?: Chain;
16 | bundlerProvider?: BundlerProviderLike;
17 | rpcProviderUrl?: string;
18 | factoryWallet?: Factory;
19 | walletFactoryAddress?: string;
20 | entryPointAddress?: string;
21 | accountAddress?: string;
22 | index?: number;
23 | bootstrapAddress?: string;
24 | multipleOwnerECDSAValidatorAddress?: string;
25 | }
26 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/members.hbs:
--------------------------------------------------------------------------------
1 | {{#if categories}}
2 | {{#each categories}}
3 | {{#unless allChildrenHaveOwnDocument}}
4 |
5 | {{title}}
6 | {{#each children}}
7 | {{#unless hasOwnDocument}}
8 | {{> member}}
9 | {{/unless}}
10 | {{/each}}
11 |
12 | {{/unless}}
13 | {{/each}}
14 | {{else}}
15 | {{#each groups}}
16 | {{#unless allChildrenHaveOwnDocument}}
17 | {{> members.group}}
18 | {{/unless}}
19 | {{/each}}
20 | {{/if}}
--------------------------------------------------------------------------------
/src/sdk/common/rxjs/synchronized.subject.ts:
--------------------------------------------------------------------------------
1 | import { Synchronized } from '../classes/index.js';
2 | import { ObjectSubject } from './object.subject.js';
3 |
4 | /**
5 | * @ignore
6 | */
7 | export class SynchronizedSubject extends ObjectSubject {
8 | prepareForCompare(value: T): any {
9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
10 | const { synchronizedAt, ...data } = value;
11 | return data;
12 | }
13 |
14 | prepareForNext(value: T): T {
15 | if (value !== null && value.synchronizedAt !== null) {
16 | value.synchronizedAt = new Date();
17 | }
18 |
19 | return value;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/sdk/dto/validators/is-address.validator.ts:
--------------------------------------------------------------------------------
1 | import { registerDecorator, ValidationOptions } from 'class-validator';
2 | import { isAddress } from '../../common/index.js';
3 |
4 | export function IsAddress(options: ValidationOptions = {}) {
5 | return (object: any, propertyName: string) => {
6 | registerDecorator({
7 | propertyName,
8 | options: {
9 | message: `${propertyName} must be an address`,
10 | ...options,
11 | },
12 | name: 'isAddress',
13 | target: object.constructor,
14 | constraints: [],
15 | validator: {
16 | validate(value: any): boolean {
17 | return isAddress(value);
18 | },
19 | },
20 | });
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/src/sdk/dto/validators/is-url.validator.ts:
--------------------------------------------------------------------------------
1 | import { registerDecorator, ValidationOptions } from 'class-validator';
2 | import { isUrl } from '../../common/index.js';
3 |
4 | export function IsUrl(validationOptions: ValidationOptions = {}) {
5 | return (object: any, propertyName: string) => {
6 | registerDecorator({
7 | propertyName,
8 | options: {
9 | message: `${propertyName} must be url`,
10 | ...validationOptions,
11 | },
12 | name: 'isUrl',
13 | target: object.constructor,
14 | constraints: [],
15 | validator: {
16 | validate(value: string): boolean {
17 | return isUrl(value);
18 | },
19 | },
20 | });
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/examples/basics/get-nonce.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
3 |
4 | dotenv.config();
5 |
6 | // tsx examples/basics/get-nonce.ts
7 | async function main() {
8 | const bundlerApiKey = 'etherspot_public_key';
9 | const customBundlerUrl = '';
10 |
11 | // initializating sdk...
12 | const modularSdk = generateModularSDKInstance(
13 | process.env.WALLET_PRIVATE_KEY as string,
14 | Number(process.env.CHAIN_ID), bundlerApiKey);
15 |
16 | // get EtherspotWallet nonce...
17 | const nonce = await modularSdk.getNonce();
18 | console.log(`nonce is: ${nonce}`);
19 | }
20 |
21 | main()
22 | .catch(console.error)
23 | .finally(() => process.exit());
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature request"
3 | about: Suggest an idea/feature for Etherspot Prime SDK
4 | title: "[FEATURE]"
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/members.group.hbs:
--------------------------------------------------------------------------------
1 | {{#if categories}}
2 | {{#each categories}}
3 |
4 | {{#if title}}{{title}} {{/if}}{{../title}}
5 | {{#each children}}
6 | {{#unless hasOwnDocument}}
7 | {{> member}}
8 | {{/unless}}
9 | {{/each}}
10 |
11 | {{/each}}
12 | {{else}}
13 |
14 | {{title}}
15 | {{#each children}}
16 | {{#unless hasOwnDocument}}
17 | {{> member}}
18 | {{/unless}}
19 | {{/each}}
20 |
21 | {{/if}}
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting security issues
2 |
3 | If you think you have found a security vulnerability, please send an email to [security@etherspot.io](mailto:security@etherspot.io). This address can be used for all of Etherspot's open source (including but not limited to SDK, Prime SDK, BUIDler, React Etherspot Kit).
4 |
5 | Etherspot will send you a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
6 |
7 | **Important:** We ask you to not disclose the vulnerability before it have been fixed and announced, unless you received a response from the Etherspot security team that you can do so.
8 |
--------------------------------------------------------------------------------
/examples/basics/get-address.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
3 |
4 | dotenv.config();
5 |
6 | // tsx examples/basics/get-address.ts
7 | async function main() {
8 | const bundlerApiKey = 'etherspot_public_key';
9 |
10 | // initializating sdk...
11 | const modularSdk = generateModularSDKInstance(
12 | process.env.WALLET_PRIVATE_KEY as string,
13 | Number(process.env.CHAIN_ID), bundlerApiKey);
14 |
15 | // get EtherspotWallet address...
16 | const address: string = await modularSdk.getCounterFactualAddress();
17 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
18 | }
19 |
20 | main()
21 | .catch(console.error)
22 | .finally(() => process.exit());
23 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/get-bytes.ts:
--------------------------------------------------------------------------------
1 | import { BytesLike } from '../index.js';
2 |
3 | /**
4 | * @ignore
5 | */
6 | export function getBytes(value: BytesLike, name?: string, copy?: boolean): Uint8Array {
7 | if (value instanceof Uint8Array) {
8 | if (copy) { return new Uint8Array(value); }
9 | return value;
10 | }
11 |
12 | if (typeof(value) === "string" && value.match(/^0x([0-9a-f][0-9a-f])*$/i)) {
13 | const result = new Uint8Array((value.length - 2) / 2);
14 | let offset = 2;
15 | for (let i = 0; i < result.length; i++) {
16 | result[i] = parseInt(value.substring(offset, offset + 2), 16);
17 | offset += 2;
18 | }
19 | return result;
20 | }
21 |
22 | throw new Error('Invalid value for getBytes!');
23 | }
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | parserOptions: {
5 | sourceType: 'module',
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'plugin:@typescript-eslint/eslint-recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'prettier',
12 | ],
13 | env: {
14 | node: true,
15 | jest: true,
16 | },
17 | rules: {
18 | '@typescript-eslint/interface-name-prefix': 'off',
19 | '@typescript-eslint/explicit-function-return-type': 'off',
20 | '@typescript-eslint/no-explicit-any': 'off',
21 | '@typescript-eslint/camelcase': 'off',
22 | '@typescript-eslint/ban-types': 'off',
23 | '@typescript-eslint/ban-ts-comment': 'off',
24 | },
25 | };
--------------------------------------------------------------------------------
/examples/basics/get-gas-fees.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
3 |
4 | dotenv.config();
5 |
6 | // tsx examples/basics/get-gas-fees.ts
7 | async function main() {
8 | const bundlerApiKey = 'etherspot_public_key';
9 | const customBundlerUrl = '';
10 |
11 | // initializating sdk...
12 | const modularSdk = generateModularSDKInstance(
13 | process.env.WALLET_PRIVATE_KEY as string,
14 | Number(process.env.CHAIN_ID), bundlerApiKey);
15 |
16 | // get EtherspotWallet address...
17 | const gasFees: any = await modularSdk.getGasFee();
18 | console.log(`gasFees is: ${JSON.stringify(gasFees)}`);
19 | }
20 |
21 | main()
22 | .catch(console.error)
23 | .finally(() => process.exit());
24 |
--------------------------------------------------------------------------------
/src/sdk/base/TransactionDetailsForUserOp.ts:
--------------------------------------------------------------------------------
1 | import { BigNumberish } from "../types/bignumber.js";
2 |
3 | /**
4 | * basic struct to create a UserOperation from
5 | */
6 | export interface TransactionDetailsForUserOp {
7 | // target address, or multiple addresses for a batch
8 | target: string | string[];
9 | // target data or multiple target datas for a batch
10 | data: string | string[];
11 | value?: BigNumberish;
12 | values?: BigNumberish[];
13 | gasLimit?: BigNumberish;
14 | maxFeePerGas?: BigNumberish;
15 | maxPriorityFeePerGas?: BigNumberish;
16 | dummySignature?: string;
17 | }
18 |
19 | export interface TransactionGasInfoForUserOp {
20 | gasLimit?: BigNumberish;
21 | maxFeePerGas?: BigNumberish;
22 | maxPriorityFeePerGas?: BigNumberish;
23 | }
24 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/bignumber-utils.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber, BigNumberish } from '../../types/bignumber.js';
2 |
3 | // Function to convert BigNumberish to bigint
4 | export function bigNumberishToBigInt(value: BigNumberish): bigint {
5 | if (typeof value === 'bigint') {
6 | return value;
7 | } else if (typeof value === 'string' || typeof value === 'number') {
8 | return BigInt(value);
9 | } else if (BigNumber.isBigNumber(value)) {
10 | return BigInt(value.toString());
11 | } else {
12 | throw new Error('Unsupported BigNumberish type');
13 | }
14 | }
15 |
16 | export function isBigNumber(value: any): boolean {
17 | return value instanceof BigNumber;
18 | }
19 |
20 | export function fromBigInt(value: bigint): BigNumber {
21 | return BigNumber.from(value.toString());
22 | }
23 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/comment.hbs:
--------------------------------------------------------------------------------
1 | {{#with comment}}
2 | {{#if hasVisibleComponent}}
3 |
21 | {{/if}}
22 | {{/with}}
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{#if name}}
4 | {{#each flags}}{{this}} {{/each}}{{{wbr name}}}
5 | {{/if}}
6 |
7 | {{#if signatures}}
8 | {{> member.signatures}}
9 | {{else}}{{#if hasGetterOrSetter}}
10 | {{> member.getterSetter}}
11 | {{else}}{{#if isReference}}
12 | {{> member.reference}}
13 | {{else}}
14 | {{> member.declaration}}
15 | {{/if}}{{/if}}{{/if}}
16 |
17 | {{#each groups}}
18 | {{#each children}}
19 | {{#unless hasOwnDocument}}
20 | {{> member}}
21 | {{/unless}}
22 | {{/each}}
23 | {{/each}}
24 |
25 |
--------------------------------------------------------------------------------
/examples/basics/get-counterfactual-address.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
3 |
4 | dotenv.config();
5 |
6 | // tsx examples/basics/get-counterfactual-address.ts
7 | async function main() {
8 | const etherspotBundlerApiKey = 'etherspot_public_key';
9 | // initializating sdk for index 0...
10 | const modularSdk = generateModularSDKInstance(
11 | process.env.WALLET_PRIVATE_KEY as string,
12 | Number(process.env.CHAIN_ID), etherspotBundlerApiKey);
13 |
14 | // get EtherspotWallet address...
15 | const address: string = await modularSdk.getCounterFactualAddress();
16 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
17 | }
18 |
19 | main()
20 | .catch(console.error)
21 | .finally(() => process.exit());
22 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/footer.hbs:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 | {{#unless settings.hideGenerator}}
18 |
21 | {{/unless}}
--------------------------------------------------------------------------------
/src/sdk/base/ClientConfig.ts:
--------------------------------------------------------------------------------
1 | import { PaymasterAPI } from './PaymasterAPI.js';
2 |
3 | /**
4 | * configuration params for wrapProvider
5 | */
6 | export interface ClientConfig {
7 | /**
8 | * the entry point to use
9 | */
10 | entryPointAddress: string;
11 | /**
12 | * the personal account registry to use
13 | */
14 | registryAddress: string;
15 | /**
16 | * url to the bundler
17 | */
18 | bundlerUrl: string;
19 | /**
20 | * if set, use this pre-deployed wallet.
21 | * (if not set, use getSigner().getAddress() to query the "counterfactual" address of wallet.
22 | * you may need to fund this address so the wallet can pay for its own creation)
23 | */
24 | walletAddres?: string;
25 | /**
26 | * if set, call just before signing.
27 | */
28 | paymasterAPI?: PaymasterAPI;
29 | }
30 |
--------------------------------------------------------------------------------
/src/sdk/wallet/providers/utils/is-wallet-provider.ts:
--------------------------------------------------------------------------------
1 | import { isHex } from 'viem';
2 | import { WalletLike, WalletProvider, WalletProviderLike } from '../interfaces.js';
3 |
4 | export function isWalletProvider(provider: WalletProviderLike): boolean {
5 | let result = false;
6 |
7 | if (provider) {
8 | switch (typeof provider) {
9 | case 'string':
10 | result = isHex(provider);
11 | break;
12 |
13 | case 'object':
14 | const { privateKey } = provider as WalletLike;
15 | if (isHex(privateKey)) {
16 | result = true;
17 | } else {
18 | const { type, signMessage } = provider as WalletProvider;
19 |
20 | result = !!type && typeof signMessage === 'function';
21 | }
22 | break;
23 | }
24 | }
25 |
26 | return result;
27 | }
28 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.sources.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 | ## Description
3 |
4 | -
5 |
6 | ## Types of changes
7 | What types of changes does your code introduce?
8 |
9 |
10 | - [ ] Bugfix (non-breaking change which fixes an issue)
11 | - [ ] New feature (non-breaking change which adds functionality)
12 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
13 | - [ ] Documentation Update
14 | - [ ] Code style update (formatting, renaming)
15 | - [ ] Refactoring (no functional changes, no api changes)
16 | - [ ] Build related changes
17 | - [ ] Other (please describe):
18 |
19 | ## Further comments (optional)
20 |
21 | -
22 |
--------------------------------------------------------------------------------
/src/sdk/SessionKeyValidator/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from "../types/bignumber.js";
2 |
3 | export interface GenerateSessionKeyResponse {
4 | sessionKey: string;
5 | enableSessionKeyData: string;
6 | oldSessionKey?: string;
7 | }
8 |
9 | export interface GetSessionKeyResponse {
10 | sessionKey: string;
11 | }
12 |
13 | export interface DeleteSessionKeyResponse {
14 | account: string;
15 | chainId: number;
16 | message: string;
17 | }
18 |
19 | export interface SessionKeyResponse {
20 | userOpHash: string;
21 | sessionKey: string;
22 | }
23 |
24 | export interface GetNonceResponse {
25 | nonce: number;
26 | }
27 |
28 | export interface SessionData {
29 | token: string;
30 | funcSelector: string;
31 | spendingLimit: BigNumber;
32 | validAfter: number;
33 | validUntil: number;
34 | live: boolean;
35 | }
--------------------------------------------------------------------------------
/src/sdk/context.ts:
--------------------------------------------------------------------------------
1 | import { ErrorSubject, Service } from './common/index.js';
2 | import { NetworkService } from './network/index.js';
3 | import { WalletService } from './wallet/index.js';
4 |
5 | export class Context {
6 | readonly error$ = new ErrorSubject();
7 |
8 | private readonly attached: Service[] = [];
9 |
10 | constructor(
11 | readonly services: {
12 | networkService: NetworkService;
13 | walletService: WalletService;
14 | },
15 | ) {
16 | const items = [...Object.values(services)];
17 |
18 | for (const item of items) {
19 | this.attach(item);
20 | }
21 | }
22 |
23 | attach(service: T): void {
24 | this.attached.push(service);
25 | service.init(this);
26 | }
27 |
28 | destroy(): void {
29 | for (const attached of this.attached) {
30 | attached.destroy();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.circleci/announcePublish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -euo pipefail
3 |
4 | applicationName=$1
5 | package=$2
6 |
7 | payload=$(
8 | cat < {
9 | const { type, value } = params;
10 |
11 | let result: any = null;
12 |
13 | switch (type) {
14 | case TransformationType.PLAIN_TO_CLASS:
15 | result = value ? BigNumber.from(value) : null;
16 | break;
17 |
18 | case TransformationType.CLASS_TO_CLASS:
19 | result = value;
20 | break;
21 |
22 | case TransformationType.CLASS_TO_PLAIN:
23 | result = isBigNumber(value) ? BigNumber.from(value).toHexString() : '0x00';
24 | break;
25 | }
26 |
27 | return result;
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/src/sdk/network/utils/prepare-network-name.ts:
--------------------------------------------------------------------------------
1 | import { NetworkNames, NETWORK_NAME_TO_CHAIN_ID, CHAIN_ID_TO_NETWORK_NAME } from '../constants.js';
2 |
3 | export function prepareNetworkName(networkNameOrChainId: string | number): NetworkNames {
4 | let result: NetworkNames = null;
5 |
6 | if (networkNameOrChainId) {
7 | if (typeof networkNameOrChainId === 'string') {
8 | if (networkNameOrChainId.startsWith('0x')) {
9 | networkNameOrChainId = parseInt(networkNameOrChainId.slice(2), 16) || 0;
10 | } else {
11 | const chainId = NETWORK_NAME_TO_CHAIN_ID[networkNameOrChainId];
12 |
13 | networkNameOrChainId = chainId ? chainId : parseInt(networkNameOrChainId, 10) || 0;
14 | }
15 | }
16 |
17 | if (typeof networkNameOrChainId === 'number') {
18 | result = CHAIN_ID_TO_NETWORK_NAME[networkNameOrChainId] || null;
19 | }
20 | }
21 |
22 | return result;
23 | }
24 |
--------------------------------------------------------------------------------
/src/sdk/common/constants.ts:
--------------------------------------------------------------------------------
1 | import { Address } from 'viem'
2 |
3 | /**
4 | * @ignore
5 | */
6 | export enum HeaderNames {
7 | AuthToken = 'x-auth-token',
8 | AnalyticsToken = 'x-analytics-token',
9 | ProjectMetadata = 'x-project-metadata',
10 | }
11 |
12 | export const bufferPercent = 13; // Buffer in percent
13 |
14 | export const onRampApiKey = 'pk_prod_01H66WYDRFM95JBTJ4VMGY1FAX';
15 |
16 | export const AddressZero = "0x0000000000000000000000000000000000000000";
17 |
18 | export enum CALL_TYPE {
19 | SINGLE = "0x00",
20 | BATCH = "0x01",
21 | STATIC = "0xFE",
22 | DELEGATE_CALL = "0xFF"
23 | }
24 |
25 | export enum EXEC_TYPE {
26 | DEFAULT = "0x00",
27 | TRY_EXEC = "0x01"
28 | }
29 |
30 | export enum MODULE_TYPE {
31 | VALIDATOR = '0x01',
32 | EXECUTOR = '0x02',
33 | FALLBACK = '0x03',
34 | HOOK = '0x04',
35 | }
36 |
37 | export const VIEM_SENTINEL_ADDRESS: Address =
38 | '0x0000000000000000000000000000000000000001'
--------------------------------------------------------------------------------
/src/sdk/bundler/providers/EtherspotBundler.ts:
--------------------------------------------------------------------------------
1 | import { Exception } from "../../common/index.js";
2 | import { getNetworkConfig } from "../../network/index.js";
3 | import { BundlerProvider } from "../interface.js";
4 |
5 | export class EtherspotBundler implements BundlerProvider {
6 | readonly url: string;
7 | readonly apiKey: string | undefined;
8 | readonly chainId: string;
9 |
10 | constructor(chainId: number, apiKey?: string, bundlerUrl?: string) {
11 | if (!bundlerUrl) {
12 | const networkConfig = getNetworkConfig(chainId);
13 | if (!networkConfig || networkConfig.bundler == '') throw new Exception('No bundler url provided')
14 | bundlerUrl = networkConfig.bundler;
15 | }
16 | if (apiKey) {
17 | if (bundlerUrl.includes('?api-key=')) this.url = bundlerUrl + apiKey;
18 | else this.url = bundlerUrl + '?api-key=' + apiKey;
19 | }
20 | else this.url = bundlerUrl;
21 | this.apiKey = apiKey;
22 | }
23 | }
--------------------------------------------------------------------------------
/examples/turnkey/create-wallet.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_ETHEREUM_ACCOUNTS, Turnkey } from '@turnkey/sdk-server';
2 |
3 | const turnkey = new Turnkey({
4 | apiBaseUrl: "https://api.turnkey.com",
5 | apiPrivateKey: "", // turnkey api private key
6 | apiPublicKey: "", // turnkey api public key
7 | defaultOrganizationId: "", // default organization id
8 | });
9 |
10 | const apiClient = turnkey.apiClient();
11 |
12 | async function main() {
13 | const walletResponse = await apiClient.createWallet({
14 | walletName: 'Example Wallet 1',
15 | accounts: DEFAULT_ETHEREUM_ACCOUNTS,
16 | });
17 |
18 | const walletId = walletResponse.walletId;
19 | const accountAddress = walletResponse.addresses[0];
20 |
21 | console.log('\x1b[33m%s\x1b[0m', 'walletId: ', walletId);
22 | console.log('\x1b[33m%s\x1b[0m', 'accountAddress: ', accountAddress);
23 | return;
24 | }
25 |
26 | main()
27 | .catch(console.error)
28 | .finally(() => process.exit());
--------------------------------------------------------------------------------
/.github-pages/theme/partials/navigation.hbs:
--------------------------------------------------------------------------------
1 | {{#if isVisible}}
2 | {{#if isLabel}}
3 | -
4 | {{{wbr title}}}
5 |
6 | {{else}}
7 | {{#if isGlobals}}
8 | -
9 | {{{wbr title}}}
10 |
11 | {{else}}
12 | -
13 | {{{wbr title}}}
14 | {{#if isInPath}}
15 | {{#if children}}
16 |
17 | {{#each children}}
18 | {{> navigation}}
19 | {{/each}}
20 |
21 | {{/if}}
22 | {{/if}}
23 |
24 | {{/if}}
25 | {{/if}}
26 | {{/if}}
27 |
--------------------------------------------------------------------------------
/examples/scripts/init.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs/promises';
2 | import path from "path";
3 | import prettier from "prettier";
4 | import crypto from 'crypto';
5 |
6 | const INIT_CONFIG = {
7 | rpcProviderUrl: "https://testnet-rpc.etherspot.io/v1/11155111",
8 | signingKey: generateRandomPrivateKey(),
9 | chainId: 11155111,
10 | paymaster: {
11 | rpcUrl: "",
12 | context: {},
13 | },
14 | };
15 |
16 | function generateRandomPrivateKey() {
17 | // Generate a random 32-byte buffer
18 | const privateKey = crypto.randomBytes(32);
19 | return privateKey.toString('hex');
20 | }
21 |
22 | const CONFIG_PATH = path.resolve(__dirname, "../config.json");
23 |
24 | async function main() {
25 | return fs.writeFile(
26 | CONFIG_PATH,
27 | prettier.format(JSON.stringify(INIT_CONFIG, null, 2), { parser: "json" })
28 | );
29 | }
30 |
31 | main()
32 | .then(() => console.log(`Config written to ${CONFIG_PATH}`))
33 | .catch((error) => {
34 | console.error(error);
35 | process.exit(1);
36 | });
37 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.signature.title.hbs:
--------------------------------------------------------------------------------
1 | {{#unless hideName}}{{{wbr name}}}{{/unless}}
2 | {{#if typeParameters}}
3 | <
4 | {{#each typeParameters}}
5 | {{#if @index}}, {{/if}}
6 | {{name}}
7 | {{/each}}
8 | >
9 | {{/if}}
10 | (
11 | {{#each parameters}}
12 | {{#if @index}}, {{/if}}
13 | {{#if flags.isRest}}...{{/if}}
14 | {{name}}
15 |
16 | {{#if flags.isOptional}}?{{/if}}
17 | {{#if defaultValue}}?{{/if}}
18 | :
19 |
20 | {{#with type}}{{>type}}{{/with}}
21 | {{/each}}
22 | )
23 | {{#if type}}
24 | {{#if arrowStyle}}
25 | =>
26 | {{else}}
27 | :
28 | {{/if}}
29 | {{#with type}}
30 | {{>type}}
31 | {{/with}}
32 | {{/if}}
33 |
--------------------------------------------------------------------------------
/examples/modules/list-modules.ts:
--------------------------------------------------------------------------------
1 | import { EtherspotBundler, ModularSdk } from '../../src';
2 | import * as dotenv from 'dotenv';
3 | import { getViemAccount } from '../../src/sdk/common/utils';
4 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
5 |
6 | dotenv.config();
7 |
8 | // tsx examples/modules/list-modules.ts
9 | async function main() {
10 | const bundlerApiKey = 'etherspot_public_key';
11 |
12 | // initializating sdk...
13 | const modularSdk = generateModularSDKInstance(
14 | process.env.WALLET_PRIVATE_KEY as string,
15 | Number(process.env.CHAIN_ID), bundlerApiKey);
16 |
17 | // get address of EtherspotWallet
18 | const address: string = await modularSdk.getCounterFactualAddress();
19 |
20 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
21 |
22 | const moduleInfo = await modularSdk.getAllModules();
23 | console.log(`moduleInfo: ${JSON.stringify(moduleInfo)}`);
24 | }
25 |
26 | main()
27 | .catch(console.error)
28 | .finally(() => process.exit());
29 |
--------------------------------------------------------------------------------
/src/sdk/base/PaymasterAPI.ts:
--------------------------------------------------------------------------------
1 | import { UserOperationStruct } from '../types/user-operation-types.js';
2 | import { PaymasterResponse } from './VerifyingPaymasterAPI.js';
3 |
4 | /**
5 | * an API to external a UserOperation with paymaster info
6 | */
7 | export class PaymasterAPI {
8 | /**
9 | * @param userOp a partially-filled UserOperation (without signature and paymasterData
10 | * note that the "preVerificationGas" is incomplete: it can't account for the
11 | * paymasterData value, which will only be returned by this method..
12 | * @returns the value to put into the PaymasterData, undefined to leave it empty
13 | */
14 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
15 | async getPaymasterData(userOp: Partial): Promise {
16 | return { result: { paymaster: '0x', paymasterData: '0x', paymasterPostOpGasLimit: '0x', paymasterVerificationGasLimit: '0x', preVerificationGas: '0x', verificationGasLimit: '0x', callGasLimit: '0x' }};
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/sdk/common/types.ts:
--------------------------------------------------------------------------------
1 | import { Address, Hex, Account } from 'viem'
2 |
3 | export type AccountType = 'erc7579-implementation'
4 |
5 | // export type Account = {
6 | // address: Address
7 | // initCode?: Hex
8 | // type: AccountType
9 | // deployedOnChains: Number[]
10 | // }
11 |
12 | export type InitialModules = {
13 | validators: Module[]
14 | executors: Module[]
15 | hooks: Module[]
16 | fallbacks: Module[]
17 | }
18 |
19 | export type ModuleType = 'validator' | 'executor' | 'fallback' | 'hook'
20 |
21 | export type Module = {
22 | module: Address
23 | data?: Hex
24 | additionalContext?: Hex
25 | type: ModuleType
26 | }
27 |
28 | type ModuleTypeIds = {
29 | [index in ModuleType]: number
30 | }
31 |
32 | export const moduleTypeIds: ModuleTypeIds = {
33 | validator: 1,
34 | executor: 2,
35 | fallback: 3,
36 | hook: 4,
37 | }
38 |
39 | export interface TypedDataField {
40 | name: string;
41 | type: string;
42 | };
43 |
44 | export type Bytes = ArrayLike;
45 |
46 | export type BytesLike = Uint8Array | Hex | Bytes | String
47 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.declaration.hbs:
--------------------------------------------------------------------------------
1 | {{#compact}}
2 | {{{wbr name}}}
3 | {{#if typeParameters}}
4 | <
5 | {{#each typeParameters}}
6 | {{#if @index}}, {{/if}}
7 | {{name}}
8 | {{/each}}
9 | >
10 | {{/if}}
11 | {{#if isOptional}}?{{/if}}: {{#with type}}{{>type}}{{/with}}
12 | {{#if defaultValue}}
13 |
14 | =
15 | {{defaultValue}}
16 |
17 | {{/if}}
18 | {{/compact}}
19 |
20 | {{> member.sources}}
21 |
22 | {{> comment}}
23 |
24 | {{#if typeParameters}}
25 | Type parameters
26 | {{> typeParameters}}
27 | {{/if}}
28 |
29 | {{#if type.declaration}}
30 |
31 |
Type declaration
32 | {{#with type.declaration}}
33 | {{> parameter}}
34 | {{/with}}
35 |
36 | {{/if}}
37 |
--------------------------------------------------------------------------------
/src/sdk/dto/validators/is-hex.validator.ts:
--------------------------------------------------------------------------------
1 | import { registerDecorator, ValidationOptions } from 'class-validator';
2 | import { isHex } from '../../common/index.js';
3 |
4 | export function IsHex(
5 | options: {
6 | size?: number;
7 | } = {},
8 | validationOptions: ValidationOptions = {},
9 | ) {
10 | return (object: any, propertyName: string) => {
11 | const { size } = options;
12 | let message = `${propertyName} must be hex`;
13 |
14 | if (size > 0) {
15 | message = `${message} with ${size} size`;
16 | }
17 |
18 | registerDecorator({
19 | propertyName,
20 | options: {
21 | message,
22 | ...validationOptions,
23 | },
24 | name: 'isHex',
25 | target: object.constructor,
26 | constraints: [],
27 | validator: {
28 | validate(value: string): boolean {
29 | let result = isHex(value);
30 |
31 | if (result && size > 0) {
32 | result = value.length === size * 2 + 2;
33 | }
34 |
35 | return result;
36 | },
37 | },
38 | });
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/src/sdk/common/OperationUtils.ts:
--------------------------------------------------------------------------------
1 | import { resolveProperties } from './utils/index.js';
2 | import { BaseAccountUserOperationStruct } from '../types/user-operation-types.js';
3 | import { toHex } from 'viem';
4 | import { BigNumber } from '../types/bignumber.js';
5 |
6 | export function toJSON(op: Partial): Promise {
7 | return resolveProperties(op).then((userOp) =>
8 | Object.keys(userOp)
9 | .map((key) => {
10 | let val = (userOp as any)[key];
11 | if (typeof val === 'object' && BigNumber.isBigNumber(val)) {
12 | val = val.toHexString()
13 | }
14 | else if (typeof val !== 'string' || !val.startsWith('0x')) {
15 | val = toHex(val);
16 | }
17 | return [key, val];
18 | })
19 | .reduce(
20 | (set, [k, v]) => ({
21 | ...set,
22 | [k]: v,
23 | }),
24 | {},
25 | ),
26 | );
27 | }
28 | export async function printOp(op: Partial): Promise {
29 | return toJSON(op).then((userOp) => JSON.stringify(userOp, null, 2));
30 | }
31 |
--------------------------------------------------------------------------------
/src/sdk/dto/validators/is-big-numberish.validator.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '../../types/bignumber.js';
2 | import { registerDecorator, ValidationOptions } from 'class-validator';
3 |
4 | export function IsBigNumberish(
5 | options: {
6 | positive?: boolean;
7 | } = {},
8 | validationOptions: ValidationOptions = {},
9 | ) {
10 | return (object: any, propertyName: string) => {
11 | const { positive } = options;
12 |
13 | registerDecorator({
14 | propertyName,
15 | options: {
16 | message: `${propertyName} must be ${positive ? 'positive ' : ''}big numberish`,
17 | ...validationOptions,
18 | },
19 | name: 'IsBigNumberish',
20 | target: object.constructor,
21 | constraints: [],
22 | validator: {
23 | validate(value: any): boolean {
24 | let result = false;
25 |
26 | try {
27 | const bn = BigNumber.from(value);
28 | result = positive ? bn.gt(0) : bn.gte(0);
29 | } catch (err) {
30 | //
31 | }
32 |
33 | return result;
34 | },
35 | },
36 | });
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Etherspot
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/modules/is-module-initialised.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { MODULE_TYPE } from '../../src/sdk/common';
3 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
4 |
5 | dotenv.config();
6 |
7 | // tsx examples/modules/is-module-installed.ts
8 | async function main() {
9 | const bundlerApiKey = 'etherspot_public_key';
10 |
11 | console.log(`inside is-module-installed script:`);
12 |
13 | // initializating sdk for index 0...
14 | const modularSdk = generateModularSDKInstance(
15 | process.env.WALLET_PRIVATE_KEY,
16 | Number(process.env.CHAIN_ID),
17 | bundlerApiKey
18 | );// Testnets dont need apiKey on bundlerProvider
19 |
20 | // get address of EtherspotWallet
21 | const address: string = await modularSdk.getCounterFactualAddress();
22 |
23 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
24 |
25 | const isModuleInstalled = await modularSdk.isModuleInstalled(MODULE_TYPE.VALIDATOR, '0xFE14F6d4e407850b24D160B9ACfBb042D32BE492');
26 | console.log(`isModuleInstalled: ${isModuleInstalled}`);
27 | }
28 |
29 | main()
30 | .catch(console.error)
31 | .finally(() => process.exit());
32 |
--------------------------------------------------------------------------------
/examples/modules/is-module-installed.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { MODULE_TYPE } from '../../src/sdk/common';
3 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
4 |
5 | dotenv.config();
6 |
7 | // tsx examples/modules/is-module-installed.ts
8 | async function main() {
9 | const bundlerApiKey = 'etherspot_public_key';
10 |
11 | console.log(`inside is-module-installed script:`);
12 |
13 | // initializating sdk for index 0...
14 | const modularSdk = generateModularSDKInstance(
15 | process.env.WALLET_PRIVATE_KEY,
16 | Number(process.env.CHAIN_ID),
17 | bundlerApiKey
18 | );// Testnets dont need apiKey on bundlerProvider
19 |
20 | // get address of EtherspotWallet
21 | const address: string = await modularSdk.getCounterFactualAddress();
22 |
23 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
24 |
25 | const isModuleInstalled = await modularSdk.isModuleInstalled(MODULE_TYPE.VALIDATOR, '0xFE14F6d4e407850b24D160B9ACfBb042D32BE492');
26 | console.log(`isModuleInstalled: ${isModuleInstalled}`);
27 | }
28 |
29 | main()
30 | .catch(console.error)
31 | .finally(() => process.exit());
32 |
--------------------------------------------------------------------------------
/examples/modules/get-previous-address.ts:
--------------------------------------------------------------------------------
1 | import { EtherspotBundler, ModularSdk } from '../../src';
2 | import * as dotenv from 'dotenv';
3 | import { MODULE_TYPE } from '../../src/sdk/common';
4 | import { getViemAccount } from '../../src/sdk/common/utils/viem-utils';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 |
9 | // tsx examples/modules/get-previous-address.ts
10 | async function main() {
11 | const bundlerApiKey = 'etherspot_public_key';
12 |
13 | // initializating sdk...
14 | const modularSdk = generateModularSDKInstance(
15 | process.env.WALLET_PRIVATE_KEY as string,
16 | Number(process.env.CHAIN_ID), bundlerApiKey);
17 |
18 | // get address of EtherspotWallet
19 | const address: string = await modularSdk.getCounterFactualAddress();
20 |
21 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
22 |
23 | const previousAddress = await modularSdk.getPreviousAddress(MODULE_TYPE.VALIDATOR, '0xFE14F6d4e407850b24D160B9ACfBb042D32BE492');
24 | console.log(`previousAddress: ${previousAddress}`);
25 | }
26 |
27 | main()
28 | .catch(console.error)
29 | .finally(() => process.exit());
30 |
--------------------------------------------------------------------------------
/src/sdk/base/Bootstrap.ts:
--------------------------------------------------------------------------------
1 | import { encodeFunctionData, parseAbi } from "viem";
2 | import { modulesAbi } from "../common/abis.js";
3 |
4 | export interface BootstrapConfig {
5 | module: string;
6 | data: string;
7 | }
8 |
9 | export function _makeBootstrapConfig(module: string, data: string): BootstrapConfig {
10 | const config: BootstrapConfig = {
11 | module: "",
12 | data: ""
13 | };
14 | config.module = module;
15 | const encodedFunctionData = encodeFunctionData({
16 | functionName: 'onInstall',
17 | abi: parseAbi(modulesAbi),
18 | args: [data],
19 | });
20 |
21 | config.data = encodedFunctionData;
22 |
23 | return config;
24 | }
25 |
26 | export function makeBootstrapConfig(module: string, data: string): BootstrapConfig[] {
27 | const config: BootstrapConfig[] = [];
28 | const encodedFunctionData = encodeFunctionData({
29 | functionName: 'onInstall',
30 | abi: parseAbi(modulesAbi),
31 | args: [data],
32 | });
33 | const newConfig: BootstrapConfig = {
34 | module: module,
35 | data: encodedFunctionData
36 | };
37 | config.push(newConfig);
38 | return config;
39 | }
40 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/userop-utils.ts:
--------------------------------------------------------------------------------
1 | import { CALL_TYPE, EXEC_TYPE } from "../constants.js";
2 | import { concat, pad } from "viem";
3 |
4 | export type Result = { key: string, value: any };
5 |
6 | export type Deferrable = {
7 | [K in keyof T]: T[K] | Promise;
8 | }
9 |
10 | export const resolveProperties = async (object: Readonly>): Promise => {
11 | const promises: Array> = Object.keys(object).map((key) => {
12 | const value = object[>key];
13 | return Promise.resolve(value).then((v) => ({ key: key, value: v }));
14 | });
15 |
16 | const results = await Promise.all(promises);
17 |
18 | return results.reduce((accum, result) => {
19 | accum[(result.key)] = result.value;
20 | return accum;
21 | }, {});
22 | }
23 |
24 | export const getExecuteMode = ({
25 | callType,
26 | execType
27 | }: {
28 | callType: CALL_TYPE
29 | execType: EXEC_TYPE
30 | }): string => {
31 | return concat([
32 | callType, // 1 byte
33 | execType, // 1 byte
34 | "0x00000000", // 4 bytes
35 | "0x00000000", // 4 bytes
36 | pad("0x00000000", {size: 22})
37 | ]);
38 | }
39 |
--------------------------------------------------------------------------------
/DEPLOYMENT_COSTS.md:
--------------------------------------------------------------------------------
1 | # Deployment Costs
2 |
3 | ## Average Gas Costs (per transaction)
4 |
5 | | Transaction | Gas Cost (Avg) |
6 | |--------------|----------------|
7 | | Wallet deployment | 180000 gwei |
8 | | Native token transfer | 102344 gwei |
9 | | ERC20 token transfer | 127683 gwei |
10 | | Token swap (Uniswap v3) | 308041 gwei (swap + approval: 185648 + 122393) |
11 |
12 | ## Costs By Chain (per transaction)
13 |
14 | | Ethereum | Standard (53 gwei) | Fast (57 gwei) |
15 | |----------|--------------------|----------------|
16 | | Wallet deployment | $19.32 | $20.25 |
17 | | Native token transfer | $10.07 | $10.83 |
18 | | ERC20 token transfer | $12.61 | $13.55 |
19 | | Token swap (Uniswap v3) | $30.27 | $32.67 |
20 |
21 | | Polygon | Low (425.6 gwei) | High (426.6 gwei) |
22 | |----------|--------------------|----------------|
23 | | Wallet deployment | $0.09 | $0.09 |
24 | | Native token transfer | $0.05 | $0.05 |
25 | | ERC20 token transfer | $0.06 | $0.06 |
26 | | Token swap (Uniswap v3) | $0.14 | $0.14 |
27 |
28 | | Binance Smart Chain | Low (3 gwei) | High (3 gwei) |
29 | |----------|--------------------|----------------|
30 | | Wallet deployment | $0.19 | $0.19 |
31 | | Native token transfer | $0.11 | $0.11 |
32 | | ERC20 token transfer | $0.13 | $0.13 |
33 | | Token swap (Uniswap v3) | $0.31 | $0.31 |
--------------------------------------------------------------------------------
/examples/basics/get-multiple-accounts.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
3 |
4 | dotenv.config();
5 |
6 | const bundlerApiKey = 'etherspot_public_key';
7 |
8 | // tsx examples/basics/get-multiple-accounts.ts
9 | async function main() {
10 | // initializating sdk for index 0...
11 | const modularSdk = generateModularSDKInstance(
12 | process.env.WALLET_PRIVATE_KEY as string,
13 | Number(process.env.CHAIN_ID), bundlerApiKey);
14 |
15 | // get EtherspotWallet address for index 0...
16 | const address: string = await modularSdk.getCounterFactualAddress();
17 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address for index 0: ${address}`);
18 |
19 | // initializating sdk for index 1...
20 | const modularSdk1 = generateModularSDKInstance(
21 | process.env.WALLET_PRIVATE_KEY,
22 | Number(process.env.CHAIN_ID),
23 | bundlerApiKey,
24 | 1
25 | );// Testnets dont need apiKey on bundlerProvider
26 |
27 | // get EtherspotWallet address for index 1...
28 | const address1: string = await modularSdk1.getCounterFactualAddress();
29 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address for index 1: ${address1}`);
30 | }
31 |
32 | main()
33 | .catch(console.error)
34 | .finally(() => process.exit());
35 |
--------------------------------------------------------------------------------
/src/sdk/common/rxjs/object.subject.ts:
--------------------------------------------------------------------------------
1 | import { Observable, BehaviorSubject } from 'rxjs';
2 | import { deepCompare } from '../utils/index.js';
3 | import { distinctUniqueKey } from './distinct-unique-key.operator.js';
4 |
5 | /**
6 | * @ignore
7 | */
8 | export class ObjectSubject extends BehaviorSubject {
9 | constructor(value: T = null) {
10 | super(value);
11 | }
12 |
13 | observeKey(key: K): Observable {
14 | return this.pipe(distinctUniqueKey(key));
15 | }
16 |
17 | next(value: T): void {
18 | if (!value) {
19 | super.next(null);
20 | } else if (
21 | !this.value || //
22 | !deepCompare(this.prepareForCompare(this.value), this.prepareForCompare(value))
23 | ) {
24 | super.next(this.prepareForNext(value));
25 | }
26 | }
27 |
28 | nextData(value: T): void {
29 | if (!value) {
30 | super.next('' as any);
31 | } else if (
32 | !this.value || //
33 | !deepCompare(this.prepareForCompare(this.value), this.prepareForCompare(value))
34 | ) {
35 | super.next(this.prepareForNext(value));
36 | }
37 | }
38 |
39 | prepareForNext(value: T): T {
40 | return value;
41 | }
42 |
43 | prepareForCompare(value: T): any {
44 | return value;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/sdk/common/getInitData.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Hex,
3 | decodeAbiParameters,
4 | decodeFunctionData,
5 | parseAbi,
6 | slice,
7 | } from 'viem'
8 | import { InitialModules, Module } from './types.js'
9 | import { bootstrapAbi, factoryAbi } from './abis.js'
10 |
11 | export const getInitData = ({
12 | initCode,
13 | }: {
14 | initCode: Hex
15 | }): InitialModules => {
16 | const { args: initCodeArgs } = decodeFunctionData({
17 | abi: parseAbi(factoryAbi),
18 | data: slice(initCode, 20),
19 | })
20 |
21 | if (initCodeArgs?.length !== 2) {
22 | throw new Error('Invalid init code')
23 | }
24 |
25 | const initCallData = decodeAbiParameters(
26 | [
27 | { name: 'bootstrap', type: 'address' },
28 | { name: 'initCallData', type: 'bytes' },
29 | ],
30 | initCodeArgs[1] as Hex,
31 | )
32 |
33 | const { args: initCallDataArgs } = decodeFunctionData({
34 | abi: parseAbi(bootstrapAbi),
35 | data: initCallData[1],
36 | })
37 |
38 | if (initCallDataArgs?.length !== 4) {
39 | throw new Error('Invalid init code')
40 | }
41 |
42 | return {
43 | validators: initCallDataArgs[0] as Module[],
44 | executors: initCallDataArgs[1] as Module[],
45 | hooks: [initCallDataArgs[2]] as Module[],
46 | fallbacks: initCallDataArgs[3] as Module[],
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.getterSetter.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#if getSignature}}
3 | {{#with getSignature}}
4 | - {{#compact}}
5 | get
6 | {{../name}}
7 | {{> member.signature.title hideName=true }}
8 | {{/compact}}
9 | {{/with}}
10 | {{/if}}
11 | {{#if setSignature}}
12 | {{#with setSignature}}
13 | - {{#compact}}
14 | set
15 | {{../name}}
16 | {{> member.signature.title hideName=true }}
17 | {{/compact}}
18 | {{/with}}
19 | {{/if}}
20 |
21 |
22 |
23 | {{#if getSignature}}
24 | {{#with getSignature}}
25 | -
26 | {{> member.signature.body }}
27 |
28 | {{/with}}
29 | {{/if}}
30 | {{#if setSignature}}
31 | {{#with setSignature}}
32 | -
33 | {{> member.signature.body }}
34 |
35 | {{/with}}
36 | {{/if}}
37 |
38 |
--------------------------------------------------------------------------------
/src/sdk/dto/utils/validate-dto.ts:
--------------------------------------------------------------------------------
1 | import { validate } from 'class-validator';
2 | import { ValidationException, prepareAddresses } from '../../common/index.js';
3 |
4 | /**
5 | * @ignore
6 | */
7 | export async function validateDto(
8 | dto: Partial,
9 | DtoConstructor: { new (): T },
10 | options: {
11 | addressKeys?: (keyof T)[];
12 | } = {},
13 | ): Promise {
14 | const result = new DtoConstructor();
15 |
16 | const { addressKeys } = options;
17 |
18 | try {
19 | let dtoWithoutUndefined = Object.entries(dto).reduce((result, [key, value]) => {
20 | if (typeof value !== 'undefined') {
21 | result = {
22 | ...result,
23 | [key]: value,
24 | };
25 | }
26 | return result;
27 | }, {}) as T;
28 |
29 | if (addressKeys) {
30 | dtoWithoutUndefined = prepareAddresses(dtoWithoutUndefined, ...addressKeys);
31 | }
32 |
33 | Object.assign(result, dtoWithoutUndefined);
34 | } catch (err) {
35 | //
36 | }
37 |
38 | const errors = await validate(result, {
39 | forbidUnknownValues: true,
40 | validationError: {
41 | target: false,
42 | value: false,
43 | },
44 | });
45 |
46 | if (errors && errors.length) {
47 | throw new ValidationException(errors);
48 | }
49 |
50 | return result;
51 | }
52 |
--------------------------------------------------------------------------------
/examples/modules/list-paginated-modules.ts:
--------------------------------------------------------------------------------
1 | import { NETWORK_NAME_TO_CHAIN_ID, NetworkNames } from '../../src';
2 | import * as dotenv from 'dotenv';
3 | import { Networks } from '../../src/sdk/network/constants';
4 | import { getPublicClient, getViemAddress } from '../../src/sdk/common/utils/viem-utils';
5 | import { getModulesPaginated } from '../../src/sdk/common/getInstalledModules';
6 | import { http, PublicClient } from 'viem';
7 |
8 | dotenv.config();
9 |
10 | // tsx examples/modules/list-paginated-modules.ts
11 | async function main() {
12 | const chainId: number = NETWORK_NAME_TO_CHAIN_ID[NetworkNames.Sepolia];
13 | const rpcProviderUrl = Networks[chainId].bundler;
14 | const walletAddress = "0x8E367D39368fc545E64c4a8C30Af7dF05edDf789";
15 |
16 | console.log(`rpcProviderUrl: ${rpcProviderUrl}`);
17 |
18 | const viemPublicClient = getPublicClient({
19 | chainId: chainId,
20 | transport: http(
21 | rpcProviderUrl
22 | )
23 | }) as PublicClient;
24 |
25 | const addresses = await getModulesPaginated({
26 | client: viemPublicClient as PublicClient,
27 | functionName: 'getValidatorPaginated',
28 | walletAddress: getViemAddress(walletAddress),
29 | });
30 |
31 | console.log(`addresses are: ${addresses}`);
32 | }
33 |
34 | main()
35 | .catch(console.error)
36 | .finally(() => process.exit());
37 |
--------------------------------------------------------------------------------
/src/sdk/common/rxjs/error.subject.ts:
--------------------------------------------------------------------------------
1 | import { Subject } from 'rxjs';
2 |
3 | /**
4 | * @ignore
5 | */
6 | export class ErrorSubject extends Subject {
7 | complete(): void {
8 | //
9 | }
10 |
11 | next(value?: any): void {
12 | if (value) {
13 | super.next(value);
14 | }
15 | }
16 |
17 | wrap(func: () => T): T {
18 | let result: any;
19 |
20 | try {
21 | result = func();
22 |
23 | if (result instanceof Promise) {
24 | result = result.catch((err) => {
25 | this.next(err);
26 | return null;
27 | });
28 | }
29 | } catch (err) {
30 | this.next(err);
31 | result = null;
32 | }
33 |
34 | return result;
35 | }
36 |
37 | catch(func: () => T, onComplete?: () => any): void {
38 | const fireOnComplete = () => {
39 | if (onComplete) {
40 | onComplete();
41 | }
42 | };
43 |
44 | try {
45 | const promise = func();
46 |
47 | if (promise instanceof Promise) {
48 | promise
49 | .catch((err) => {
50 | this.next(err);
51 | })
52 | .finally(() => {
53 | fireOnComplete();
54 | });
55 | return;
56 | }
57 |
58 | fireOnComplete();
59 | } catch (err) {
60 | this.next(err);
61 | fireOnComplete();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/sdk/dto/validators/is-bytes-like.validator.ts:
--------------------------------------------------------------------------------
1 | import { registerDecorator, ValidationOptions } from 'class-validator';
2 | import { isHex } from 'viem';
3 |
4 | export function IsBytesLike(options: ValidationOptions & { acceptText?: boolean } = {}) {
5 | return (object: any, propertyName: string) => {
6 | registerDecorator({
7 | propertyName,
8 | options: {
9 | message: `${propertyName} must be bytes like`,
10 | ...options,
11 | },
12 | name: 'IsBytesLike',
13 | target: object ? object.constructor : undefined,
14 | constraints: [],
15 | validator: {
16 | validate(value: any): boolean {
17 | let result = false;
18 |
19 | try {
20 | if (value) {
21 | switch (typeof value) {
22 | case 'string':
23 | if (options.acceptText) {
24 | result = true;
25 | } else {
26 | result = isHex(value) && value.length % 2 === 0;
27 | }
28 | break;
29 |
30 | case 'object':
31 | result = isHex(value);
32 | break;
33 | }
34 | }
35 | } catch (err) {
36 | //
37 | }
38 |
39 | return result;
40 | },
41 | },
42 | });
43 | };
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-docs.yml:
--------------------------------------------------------------------------------
1 | name: Deploy docs
2 | on:
3 | push:
4 | branches:
5 | - master
6 | paths:
7 | - '**.js'
8 | - '**.json'
9 | - '**.ts'
10 | - '**.md'
11 | - '**.yml'
12 | jobs:
13 | start:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Setup ssh agent
17 | env:
18 | SSH_AUTH_SOCK: /tmp/ssh_agent.sock
19 | run: |
20 | mkdir -p ~/.ssh
21 | ssh-keyscan github.com >> ~/.ssh/known_hosts
22 | ssh-agent -a $SSH_AUTH_SOCK > /dev/null
23 | ssh-add - <<< "${{ secrets.SSH_MACHINE_KEY_DOCS }}"
24 |
25 | - name: Setup git
26 | run: |
27 | git config --global user.email "infrastructure@pillarproject.io"
28 | git config --global user.name "EtherspotBOT"
29 |
30 | - name: Checkout
31 | uses: actions/checkout@v4
32 |
33 | - name: Setup node.js
34 | uses: actions/setup-node@v4
35 | with:
36 | node-version: 18.x
37 |
38 | - name: Install dependencies
39 | run: npm install
40 |
41 | - name: Build docs
42 | run: npm run docs:build
43 |
44 | - name: Install gh-pages cli
45 | run: npm install gh-pages -g
46 |
47 | - name: Deploy docs
48 | env:
49 | SSH_AUTH_SOCK: /tmp/ssh_agent.sock
50 | run: |
51 | npm run docs:deploy
52 |
--------------------------------------------------------------------------------
/examples/helpers/sdk-helper.ts:
--------------------------------------------------------------------------------
1 | import { getPublicClient, getViemAccount } from "../../src/sdk/common";
2 | import { EtherspotBundler, ModularSdk } from "../../src";
3 | import { Hex, http, parseAbi } from "viem";
4 | import { erc20Abi } from "../../src/sdk/common/abis";
5 |
6 | export const generateModularSDKInstance = (privateKey: string, chainId: number, bundlerApiKey: string, index: number = 0) => {
7 | const modularSdk = new ModularSdk(
8 | privateKey,
9 | {
10 | chainId: chainId,
11 | bundlerProvider: new EtherspotBundler(chainId, bundlerApiKey),
12 | index: index
13 | })
14 |
15 | return modularSdk;
16 | }
17 |
18 | export const getTokenMetaData = async (rpcProviderUrl: string, tokenAddress: string) => {
19 | const publicClient = getPublicClient({
20 | chainId: Number(process.env.CHAIN_ID),
21 | transport: http(rpcProviderUrl)
22 | });
23 |
24 | const symbol : string = await publicClient.readContract({
25 | address: tokenAddress as Hex,
26 | abi: parseAbi(erc20Abi),
27 | functionName: 'symbol',
28 | args: []
29 | }) as string;
30 |
31 | const decimal : number = await publicClient.readContract({
32 | address: tokenAddress as Hex,
33 | abi: parseAbi(erc20Abi),
34 | functionName: 'decimals',
35 | args: []
36 | }) as number;
37 |
38 | return { symbol , decimal };
39 | }
--------------------------------------------------------------------------------
/examples/modules/install-module.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { MODULE_TYPE, sleep } from '../../src/sdk/common';
3 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
4 |
5 | dotenv.config();
6 |
7 | // tsx examples/modules/install-module.ts
8 | async function main() {
9 | const bundlerApiKey = 'etherspot_public_key';
10 |
11 | // initializating sdk...
12 | const modularSdk = generateModularSDKInstance(
13 | process.env.WALLET_PRIVATE_KEY as string,
14 | Number(process.env.CHAIN_ID), bundlerApiKey);
15 |
16 | // get address of EtherspotWallet
17 | const address: string = await modularSdk.getCounterFactualAddress();
18 |
19 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
20 |
21 | const uoHash = await modularSdk.installModule(MODULE_TYPE.VALIDATOR, '0xF4CDE8B11500ca9Ea108c5838DD26Ff1a4257a0c');
22 | console.log(`UserOpHash: ${uoHash}`);
23 |
24 | // get transaction hash...
25 | console.log('Waiting for transaction...');
26 | let userOpsReceipt = null;
27 | const timeout = Date.now() + 60000; // 1 minute timeout
28 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
29 | await sleep(2);
30 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
31 | }
32 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
33 | }
34 |
35 | main()
36 | .catch(console.error)
37 | .finally(() => process.exit());
38 |
--------------------------------------------------------------------------------
/examples/sessionkeys/get-associated-session-keys.ts:
--------------------------------------------------------------------------------
1 | import { EtherspotBundler, ModularSdk, SessionKeyValidator } from '../../src';
2 | import * as dotenv from 'dotenv';
3 | import { getViemAccount, sleep } from '../../src/sdk/common';
4 | import { KeyStore } from '../../src/sdk/SessionKeyValidator';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 | const secondsInAMonth = 30 * 24 * 60 * 60; // 2592000 seconds
9 |
10 | // tsx examples/sessionkeys/get-associated-session-keys.ts
11 | async function main() {
12 | const bundlerApiKey = 'etherspot_public_key';
13 |
14 | // initializating sdk...
15 | const modularSdk = generateModularSDKInstance(
16 | process.env.WALLET_PRIVATE_KEY as string,
17 | Number(process.env.CHAIN_ID), bundlerApiKey);
18 |
19 |
20 | // get address of EtherspotWallet
21 | const address: string = await modularSdk.getCounterFactualAddress();
22 |
23 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
24 |
25 | // get instance of SessionKeyValidator
26 | const sessionKeyModule = await SessionKeyValidator.create(modularSdk);
27 | const sessionKeys = await sessionKeyModule.getAssociatedSessionKeys();
28 | console.log('\x1b[33m%s\x1b[0m', `AssociatedSessionKeys: `, sessionKeys);
29 | }
30 |
31 | main()
32 | .catch(console.error)
33 | .finally(() => process.exit());
34 |
35 | const getEpochTimeInSeconds = () => Math.floor(new Date().getTime() / 1000);
36 |
--------------------------------------------------------------------------------
/examples/modules/generate-module-uninstall-deinitdata.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { MODULE_TYPE } from '../../src/sdk/common';
3 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
4 |
5 | dotenv.config();
6 |
7 | // npx ts-node examples/modules/generate-module-uninstall-deinitdata.ts
8 | async function main() {
9 | const bundlerApiKey = 'etherspot_public_key';
10 |
11 | // initializating sdk for index 0...
12 | const modularSdk = generateModularSDKInstance(
13 | process.env.WALLET_PRIVATE_KEY as string,
14 | Number(process.env.CHAIN_ID), bundlerApiKey);
15 |
16 | // get address of EtherspotWallet
17 | const address: string = await modularSdk.getCounterFactualAddress();
18 |
19 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
20 |
21 | //this should be previous node address of the module to be uninstalled and the deinit data
22 | //deinit data is the data that is passed to the module to be uninstalled
23 | // here we need to call the function which can find out the address of previous node of the module to be uninstalled
24 | // and the deinit data can be 0x00 as default value
25 | const deInitData = '0x00';
26 | const deinitData = await modularSdk.generateModuleDeInitData(MODULE_TYPE.VALIDATOR, '0x1417aDC5308a32265E0fA0690ea1408FFA62F37c', deInitData);
27 | console.log(`deinitData: ${deinitData}`);
28 | }
29 |
30 | main()
31 | .catch(console.error)
32 | .finally(() => process.exit());
33 |
--------------------------------------------------------------------------------
/src/sdk/types/user-operation-types.ts:
--------------------------------------------------------------------------------
1 | import { BytesLike } from "../common/index.js";
2 | import { BigNumber, BigNumberish } from "./bignumber.js";
3 |
4 | export type PromiseOrValue = T | Promise;
5 |
6 | export type BaseAccountUserOperationStruct = {
7 | sender: PromiseOrValue;
8 | nonce: PromiseOrValue;
9 | initCode: PromiseOrValue;
10 | callData: PromiseOrValue;
11 | accountGasLimits: PromiseOrValue;
12 | preVerificationGas: PromiseOrValue;
13 | gasFees: PromiseOrValue;
14 | paymasterAndData: PromiseOrValue;
15 | signature: PromiseOrValue;
16 | };
17 |
18 | export type UserOperationStruct = {
19 | sender: PromiseOrValue;
20 | nonce: PromiseOrValue;
21 | initCode: PromiseOrValue;
22 | callData: PromiseOrValue;
23 | callGasLimit: PromiseOrValue;
24 | verificationGasLimit: PromiseOrValue;
25 | preVerificationGas: PromiseOrValue;
26 | maxFeePerGas: PromiseOrValue;
27 | maxPriorityFeePerGas: PromiseOrValue;
28 | paymasterAndData: PromiseOrValue;
29 | signature: PromiseOrValue;
30 | };
31 |
32 | export interface TypedDataField {
33 | name: string;
34 | type: string;
35 | };
36 |
37 | export interface FeeData {
38 | maxFeePerGas: null | BigNumber;
39 | maxPriorityFeePerGas: null | BigNumber;
40 | }
--------------------------------------------------------------------------------
/.github-pages/theme/partials/index.hbs:
--------------------------------------------------------------------------------
1 | {{#if groups}}
2 |
3 | Index
4 |
5 |
6 | {{#each groups}}
7 |
8 | {{#if categories}}
9 | {{#each categories}}
10 | {{#if title}}{{title}} {{/if}}{{../title}}
11 |
16 | {{/each}}
17 | {{else}}
18 | {{title}}
19 |
24 | {{/if}}
25 |
26 | {{/each}}
27 |
28 |
29 |
30 | {{/if}}
--------------------------------------------------------------------------------
/examples/scripts/commands/transfer.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import config from "../../config.json";
3 | import { printOp } from "../../../src/sdk/common/OperationUtils";
4 | import { sleep } from "../../../src/sdk/common";
5 | import { generateModularSDKInstance } from "../../helpers/sdk-helper";
6 | import { getAddress, parseEther } from "viem";
7 |
8 | export default async function main(t: string, amt: string) {
9 | const modularSdk = generateModularSDKInstance(
10 | config.signingKey,
11 | config.chainId,
12 | config.rpcProviderUrl
13 | );
14 |
15 | const target = getAddress(t);
16 | const value = parseEther(amt);
17 |
18 | // clear the transaction batch
19 | await modularSdk.clearUserOpsFromBatch();
20 |
21 | await modularSdk.addUserOpsToBatch({to: target, value});
22 | console.log(`Added transaction to batch`);
23 |
24 | const op = await modularSdk.estimate();
25 | console.log(`Estimated UserOp: ${await printOp(op)}`);
26 |
27 | // sign the userOp and sending to the bundler...
28 | const uoHash = await modularSdk.send(op);
29 | console.log(`UserOpHash: ${uoHash}`);
30 |
31 | // get transaction hash...
32 | console.log('Waiting for transaction...');
33 | let userOpsReceipt = null;
34 | const timeout = Date.now() + 60000; // 1 minute timeout
35 | while((userOpsReceipt == null) && (Date.now() < timeout)) {
36 | await sleep(2);
37 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
38 | }
39 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
40 | }
41 |
--------------------------------------------------------------------------------
/src/sdk/common/getGasFee.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber, BigNumberish } from '../types/bignumber.js';
2 | import { bufferPercent } from './constants.js';
3 | import { PublicClient } from 'viem';
4 |
5 | export interface Gas {
6 | maxFeePerGas: BigNumberish;
7 | maxPriorityFeePerGas: BigNumberish;
8 | }
9 |
10 | export async function getGasFee(publicClient: PublicClient): Promise {
11 | try {
12 | //const [fee, block] = await provider.send('eth_maxPriorityFeePerGas', []);
13 |
14 | const gasFeeResponse : any = await publicClient.request(
15 | {
16 | method: 'eth_maxPriorityFeePerGas',
17 | params: [],
18 | }
19 | );
20 |
21 | if(!gasFeeResponse) {
22 | throw new Error('failed to get priorityFeePerGas');
23 | }
24 |
25 | const [fee, block] = gasFeeResponse;
26 |
27 | if (BigNumber.from(0).eq(fee)) {
28 | throw new Error('failed to get priorityFeePerGas');
29 | }
30 | const tip = BigNumber.from(fee);
31 | const buffer = tip.div(100).mul(bufferPercent);
32 | const maxPriorityFeePerGas = tip.add(buffer);
33 | const maxFeePerGas =
34 | block.baseFeePerGas != null ? block.baseFeePerGas.mul(2).add(maxPriorityFeePerGas) : maxPriorityFeePerGas;
35 |
36 | return { maxFeePerGas, maxPriorityFeePerGas };
37 | } catch (err) {
38 | console.warn(
39 | "getGas: eth_maxPriorityFeePerGas failed, falling back to legacy gas price."
40 | );
41 | const gas = await publicClient.getGasPrice();
42 | return { maxFeePerGas: gas, maxPriorityFeePerGas: gas };
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/pulse/utils.ts:
--------------------------------------------------------------------------------
1 | import { encodeAbiParameters, encodeFunctionData, Hex } from "viem";
2 |
3 | const HookMultiplexer = [{
4 | type: 'function',
5 | name: 'onInstall',
6 | inputs: [
7 | {
8 | name: 'data',
9 | type: 'bytes',
10 | internalType: 'bytes',
11 | },
12 | ],
13 | outputs: [],
14 | stateMutability: 'nonpayable',
15 | }] as const;
16 |
17 | export interface SigHookInit {
18 | sig: string;
19 | subHooks: Hex[];
20 | }
21 |
22 | export function getHookMultiPlexerInitData(
23 | globalHooks: Hex[] = [],
24 | valueHooks: Hex[] = [],
25 | delegatecallHooks: Hex[] = [],
26 | sigHooks: SigHookInit[] = [],
27 | targetSigHooks: SigHookInit[] = [],
28 | ): Hex {
29 | const abiType = [
30 | { type: 'address[]' },
31 | { type: 'address[]' },
32 | { type: 'address[]' },
33 | {
34 | type: 'tuple[]',
35 | components: [{ type: 'bytes4' }, { type: 'address[]' }],
36 | },
37 | {
38 | type: 'tuple[]',
39 | components: [{ type: 'bytes4' }, { type: 'address[]' }],
40 | },
41 | ];
42 | const encodedData = encodeAbiParameters(abiType, [
43 | globalHooks,
44 | valueHooks,
45 | delegatecallHooks,
46 | sigHooks,
47 | targetSigHooks
48 | ] as any);
49 |
50 | console.log('Encoded Data:', encodedData);
51 |
52 | const hookMultiplexerInitData = encodeFunctionData({
53 | abi: HookMultiplexer,
54 | args: [encodedData],
55 | functionName: 'onInstall',
56 | });
57 |
58 | console.log('Hook Multiplexer Init Data:', hookMultiplexerInitData);
59 |
60 | return hookMultiplexerInitData;
61 | }
--------------------------------------------------------------------------------
/.github-pages/theme/partials/header.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | - Preparing search index...
13 | - The search index is not available
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{#with model}}{{> breadcrumb}}{{/with}}
23 |
24 |
{{#compact}}
25 | {{model.kindString}}
26 | {{model.name}}
27 | {{#if model.typeParameters}}
28 | <
29 | {{#each model.typeParameters}}
30 | {{#if @index}}, {{/if}}
31 | {{name}}
32 | {{/each}}
33 | >
34 | {{/if}}
35 | {{/compact}}
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/tsconfig/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
3 | "compilerOptions": {
4 | "resolveJsonModule": true,
5 | // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
6 | "incremental": false,
7 | // JavaScript support
8 | "allowJs": false,
9 | "checkJs": false,
10 | // Interop constraints
11 | "esModuleInterop": true,
12 | "experimentalDecorators": true,
13 | "allowSyntheticDefaultImports": false,
14 | // "forceConsistentCasingInFileNames": true,
15 | // "verbatimModuleSyntax": true,
16 | "importHelpers": true, // This is only used for build validation. Since we do not have `tslib` installed, this will fail if we accidentally make use of anything that'd require injection of helpers.
17 | // Language and environment
18 | "target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping.
19 | "lib": [
20 | "ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances.
21 | "DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped.
22 | ],
23 | // Skip type checking for node modules
24 | "skipLibCheck": true,
25 | "strict": false,
26 | "downlevelIteration": true
27 | },
28 | "tsc-alias": {
29 | "resolveFullPaths": true,
30 | "verbose": false
31 | }
32 | }
--------------------------------------------------------------------------------
/.github-pages/theme/partials/typeAndParent.hbs:
--------------------------------------------------------------------------------
1 | {{#compact}}
2 | {{#if this}}
3 | {{#if elementType}}
4 | {{#with elementType}}
5 | {{> typeAndParent}}
6 | {{/with}}
7 | []
8 | {{else}}
9 | {{#if reflection}}
10 | {{#ifSignature reflection}}
11 | {{#if reflection.parent.parent.url}}
12 | {{reflection.parent.parent.name}}
13 | {{else}}
14 | {{reflection.parent.parent.name}}
15 | {{/if}}
16 | .
17 | {{#if reflection.parent.url}}
18 | {{reflection.parent.name}}
19 | {{else}}
20 | {{reflection.parent.name}}
21 | {{/if}}
22 | {{else}}
23 | {{#if reflection.parent.url}}
24 | {{reflection.parent.name}}
25 | {{else}}
26 | {{reflection.parent.name}}
27 | {{/if}}
28 | .
29 | {{#if reflection.url}}
30 | {{reflection.name}}
31 | {{else}}
32 | {{reflection.name}}
33 | {{/if}}
34 | {{/ifSignature}}
35 | {{else}}
36 | {{this}}
37 | {{/if}}
38 | {{/if}}
39 | {{else}}
40 | void
41 | {{/if}}
42 | {{/compact}}
--------------------------------------------------------------------------------
/src/sdk/wallet/providers/dynamic.wallet-provider.ts:
--------------------------------------------------------------------------------
1 | import { NetworkNames, prepareNetworkName } from '../../network/index.js';
2 | import { prepareAddress, UniqueSubject } from '../../common/index.js';
3 | import { MessagePayload, WalletProvider } from './interfaces.js';
4 | import { Address, Hash, Hex, TransactionRequest } from 'viem';
5 |
6 | export abstract class DynamicWalletProvider implements WalletProvider {
7 | readonly address$ = new UniqueSubject();
8 | readonly networkName$ = new UniqueSubject();
9 |
10 | protected constructor(readonly type: string) {
11 | //
12 | }
13 |
14 | get address(): string {
15 | return this.address$.value;
16 | }
17 |
18 | get networkName(): NetworkNames {
19 | return this.networkName$.value;
20 | }
21 |
22 | abstract signMessage(message: any, validatorAddress?: Address, factoryAddress?: Address, initCode?: Hex): Promise;
23 |
24 | abstract signUserOp(message: Hex): Promise;
25 |
26 | abstract signTypedData(msg: MessagePayload, validatorAddress?: Address, factoryAddress?: Address, initCode?: Hex): Promise
27 |
28 | protected setAddress(address: string): void {
29 | this.address$.next(prepareAddress(address));
30 | }
31 |
32 | protected setNetworkName(networkNameOrChainId: string | number): void {
33 | this.networkName$.next(prepareNetworkName(networkNameOrChainId));
34 | }
35 |
36 | abstract eth_requestAccounts(address?: string): Promise;
37 |
38 | abstract eth_accounts(address?: string): Promise;
39 |
40 | abstract eth_sendTransaction(transaction: TransactionRequest): Promise;
41 |
42 | abstract eth_signTransaction(transaction: TransactionRequest): Promise;
43 | }
44 |
--------------------------------------------------------------------------------
/.github-pages/theme/layouts/default.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{#ifCond model.name '==' project.name}}{{project.name}}{{else}}{{model.name}} | {{project.name}}{{/ifCond}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{> header}}
15 |
16 |
17 |
18 |
19 | {{{contents}}}
20 |
21 |
38 |
39 |
40 |
41 | {{> footer}}
42 |
43 |
44 |
45 |
46 |
47 | {{> analytics}}
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/examples/turnkey/get-counterfactual-address.ts:
--------------------------------------------------------------------------------
1 | import { TurnkeyClient } from "@turnkey/http";
2 | import { ApiKeyStamper } from "@turnkey/sdk-server";
3 | import { createAccount } from "@turnkey/viem";
4 | import { EtherspotBundler, ModularSdk } from "../../src";
5 | import { createWalletClient, http } from "viem";
6 | import { polygonAmoy } from "viem/chains";
7 |
8 | // example script to get etherspot smart wallet address using a turnkey client.
9 | async function main() {
10 | const turnkeyClient = new TurnkeyClient(
11 | { baseUrl: "https://api.turnkey.com" },
12 | new ApiKeyStamper({
13 | apiPrivateKey: "", // turnkey api private key
14 | apiPublicKey: "" // turnkey api public key
15 | })
16 | );
17 |
18 | const turnkeyAccount = await createAccount({
19 | client: turnkeyClient,
20 | organizationId: "", // turnkey organization id
21 | signWith: "", // wallet address created using ./create-wallet.ts
22 | });
23 |
24 | const walletClient = createWalletClient({
25 | transport: http("https://testnet-rpc.etherspot.io/v2/80002"),
26 | account: turnkeyAccount,
27 | chain: polygonAmoy
28 | });
29 |
30 | const bundlerApiKey = 'etherspot_public_key';
31 |
32 | const modularSdk = new ModularSdk(
33 | walletClient,
34 | {
35 | chainId: 80002,
36 | bundlerProvider: new EtherspotBundler(
37 | 80002,
38 | bundlerApiKey,
39 | )
40 | }
41 | );
42 |
43 | const address = await modularSdk.getCounterFactualAddress();
44 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
45 | }
46 |
47 | main()
48 | .catch(console.error)
49 | .finally(() => process.exit());
--------------------------------------------------------------------------------
/.github/workflows/check-package-version.yml:
--------------------------------------------------------------------------------
1 | name: npm package version bump and changelog check
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 | jobs:
8 | start:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 | - name: Check if package version has been updated
14 | id: check
15 | uses: EndBug/version-check@v2.1.1
16 | with:
17 | token: ${{ secrets.GITHUB_TOKEN }}
18 | file-name: ./package.json
19 | - name: Log when package version changed
20 | if: steps.check.outputs.changed == 'true'
21 | run: 'echo "Yayy!! Version change found in commit ${{ steps.check.outputs.commit }}! New version: ${{ steps.check.outputs.version }} (${{ steps.check.outputs.type }})"'
22 | - name: Log when package version unchanged
23 | if: steps.check.outputs.changed == 'false'
24 | uses: mshick/add-pr-comment@v2
25 | with:
26 | message: |
27 | **Yooo! You forgot to bump the version in package.json!**
28 | allow-repeats: true
29 | - name: Exit if package version is not changed
30 | if: steps.check.outputs.changed == 'false'
31 | run: echo "No version change :/ Please update version in package.json!" && exit 1
32 | - name: Check if CHANGELOG.md is updated
33 | id: changelog-status
34 | run: |
35 | git fetch origin ${{ github.base_ref }}:${{ github.base_ref }}
36 | changed_files=$(git diff --name-only ${{ github.base_ref }} ${{ github.sha }} | grep CHANGELOG.md)
37 | if [ -n "$changed_files" ]; then
38 | echo "CHANGELOG.md has been changed."
39 | echo "$changed_files"
40 | else
41 | echo "CHANGELOG.md has not been changed." && exit 1
42 | fi
43 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/member.signature.body.hbs:
--------------------------------------------------------------------------------
1 | {{#unless hideSources}}
2 | {{> member.sources}}
3 | {{/unless}}
4 |
5 | {{> comment}}
6 |
7 | {{#if typeParameters}}
8 | Type parameters
9 | {{> typeParameters}}
10 | {{/if}}
11 |
12 | {{#if parameters}}
13 | Parameters
14 |
15 | {{#each parameters}}
16 | -
17 |
{{#compact}}
18 | {{#each flags}}
19 | {{this}}
20 | {{/each}}
21 | {{#if flags.isRest}}...{{/if}}
22 | {{name}}:
23 | {{#with type}}{{>type}}{{/with}}
24 | {{#if defaultValue}}
25 |
26 | =
27 | {{defaultValue}}
28 |
29 | {{/if}}
30 | {{/compact}}
31 |
32 | {{> comment}}
33 |
34 | {{#if type.declaration}}
35 | {{#with type.declaration}}
36 | {{> parameter}}
37 | {{/with}}
38 | {{/if}}
39 |
40 | {{/each}}
41 |
42 | {{/if}}
43 |
44 | {{#if type}}
45 | Returns {{#compact}}{{#with type}}{{>type}}{{/with}}{{/compact}}
46 |
47 | {{#if comment.returns}}
48 | {{#markdown}}{{{comment.returns}}}{{/markdown}}
49 | {{/if}}
50 |
51 | {{#if type.declaration}}
52 | {{#with type.declaration}}
53 | {{> parameter}}
54 | {{/with}}
55 | {{/if}}
56 | {{/if}}
57 |
--------------------------------------------------------------------------------
/src/sdk/pulse/utils.ts:
--------------------------------------------------------------------------------
1 | import { encodeAbiParameters, encodeFunctionData, Hex } from 'viem';
2 | import { SigHookInit } from './interfaces.js';
3 |
4 | export const HookMultiplexer = [
5 | {
6 | type: 'function',
7 | name: 'onInstall',
8 | inputs: [
9 | {
10 | name: 'data',
11 | type: 'bytes',
12 | internalType: 'bytes',
13 | },
14 | ],
15 | outputs: [],
16 | stateMutability: 'nonpayable',
17 | },
18 | {
19 | type: 'function',
20 | name: 'addHook',
21 | inputs: [
22 | {
23 | name: 'hook',
24 | type: 'address',
25 | internalType: 'address',
26 | },
27 | {
28 | name: 'hookType',
29 | type: 'bytes1',
30 | internalType: 'enum HookType',
31 | },
32 | ],
33 | outputs: [],
34 | stateMutability: 'nonpayable',
35 | },
36 | ] as const;
37 |
38 | export function getHookMultiPlexerInitData(
39 | globalHooks: Hex[] = [],
40 | valueHooks: Hex[] = [],
41 | delegatecallHooks: Hex[] = [],
42 | sigHooks: SigHookInit[] = [],
43 | targetSigHooks: SigHookInit[] = [],
44 | ): Hex {
45 | const abiType = [
46 | { type: 'address[]' },
47 | { type: 'address[]' },
48 | { type: 'address[]' },
49 | {
50 | type: 'tuple[]',
51 | components: [{ type: 'bytes4' }, { type: 'address[]' }],
52 | },
53 | {
54 | type: 'tuple[]',
55 | components: [{ type: 'bytes4' }, { type: 'address[]' }],
56 | },
57 | ];
58 | const encodedData = encodeAbiParameters(abiType, [
59 | globalHooks,
60 | valueHooks,
61 | delegatecallHooks,
62 | sigHooks,
63 | targetSigHooks,
64 | ] as any);
65 |
66 | const hookMultiplexerInitData = encodeFunctionData({
67 | abi: HookMultiplexer,
68 | args: [encodedData],
69 | functionName: 'onInstall',
70 | });
71 |
72 | return hookMultiplexerInitData;
73 | }
74 |
--------------------------------------------------------------------------------
/src/sdk/common/service.ts:
--------------------------------------------------------------------------------
1 | import { Subscription } from 'rxjs';
2 | import { Context } from '../context.js';
3 |
4 | export abstract class Service {
5 | protected context: Context;
6 | private inited = false;
7 | private destroyed = false;
8 | private attachedCounter = 0;
9 | private subscriptions: Subscription[] = [];
10 |
11 | init(context: Context): void {
12 | if (!this.inited) {
13 | this.inited = true;
14 | this.context = context;
15 |
16 | if (this.onInit) {
17 | this.onInit();
18 | }
19 |
20 | if (this.error$) {
21 | this.addSubscriptions(this.error$.subscribe());
22 | }
23 | }
24 |
25 | ++this.attachedCounter;
26 | }
27 |
28 | destroy(): void {
29 | if (!this.attachedCounter) {
30 | return;
31 | }
32 |
33 | --this.attachedCounter;
34 |
35 | if (!this.attachedCounter && !this.destroyed) {
36 | this.destroyed = true;
37 |
38 | this.removeSubscriptions();
39 |
40 | if (this.onDestroy) {
41 | this.onDestroy();
42 | }
43 | }
44 | }
45 |
46 | protected onInit?(): void;
47 |
48 | protected onDestroy?(): void;
49 |
50 | protected get error$(): Context['error$'] {
51 | return this.context.error$;
52 | }
53 |
54 | protected get services(): Context['services'] {
55 | return this.context.services;
56 | }
57 |
58 | protected addSubscriptions(...subscriptions: Subscription[]): void {
59 | this.subscriptions.push(...subscriptions.filter((subscription) => !!subscription));
60 | }
61 |
62 | protected removeSubscriptions(): void {
63 | for (const subscription of this.subscriptions) {
64 | subscription.unsubscribe();
65 | }
66 | this.subscriptions = [];
67 | }
68 |
69 | protected replaceSubscriptions(...subscriptions: Subscription[]): void {
70 | this.removeSubscriptions();
71 | this.addSubscriptions(...subscriptions);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: 🐛 Bug report
2 | description: Create a bug report to help us improve
3 | title: "[BUG] "
4 | body:
5 | - type: textarea
6 | id: describe
7 | attributes:
8 | label: Describe the bug
9 | description: A clear and concise description of what the bug is and steps to reproduce it.
10 | placeholder: This is what I'm seeing.
11 | validations:
12 | required: true
13 | - type: textarea
14 | id: expected
15 | attributes:
16 | label: Expected behavior
17 | description: A clear and concise description of what you expected to happen.
18 | placeholder: This is what should happen.
19 | validations:
20 | required: true
21 | - type: textarea
22 | id: steps
23 | attributes:
24 | label: Steps to reproduce
25 | description: List the steps to reproduce the behavior
26 | placeholder: |
27 | 1. Check logs and errors
28 | validations:
29 | required: false
30 | - type: textarea
31 | id: attachments
32 | attributes:
33 | label: Additional context
34 | description: If applicable, add screenshots, links or other context about the issue
35 | placeholder: |
36 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
37 | validations:
38 | required: false
39 | - type: dropdown
40 | attributes:
41 | label: Operating system
42 | description: What type of operating system are you running on?
43 | multiple: false
44 | options:
45 | - Linux
46 | - macOS
47 | - Windows
48 | validations:
49 | required: true
50 | - type: input
51 | id: version
52 | attributes:
53 | label: Prime SDK version or commit hash
54 | description: Which version or commit hash of Skandha are you running?
55 | placeholder: v0.0.1 or 214ee64
56 | validations:
57 | required: true
58 |
--------------------------------------------------------------------------------
/examples/sessionkeys/disable-sessionkey-module.ts:
--------------------------------------------------------------------------------
1 | import { EtherspotBundler, ModularSdk, SessionKeyValidator } from '../../src';
2 | import * as dotenv from 'dotenv';
3 | import { getViemAccount, sleep } from '../../src/sdk/common';
4 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
5 |
6 | dotenv.config();
7 |
8 | const sessionKey = '0x476595CD5ed26D40Fd299F266350e5E85A7DF0D3';
9 |
10 | // tsx examples/sessionkeys/disable-sessionkey-module.ts
11 | async function main() {
12 | const bundlerApiKey = 'etherspot_public_key';
13 |
14 | // initializating sdk...
15 | const modularSdk = generateModularSDKInstance(process.env.WALLET_PRIVATE_KEY as string,
16 | Number(process.env.CHAIN_ID), bundlerApiKey);
17 |
18 | // get address of EtherspotWallet
19 | const address: string = await modularSdk.getCounterFactualAddress();
20 |
21 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
22 |
23 | // get instance of SessionKeyValidator
24 | const sessionKeyModule = await SessionKeyValidator.create(modularSdk);
25 |
26 | const response = await sessionKeyModule.disableSessionKey(sessionKey);
27 |
28 | console.log('\x1b[33m%s\x1b[0m', `UserOpHash: `, response.userOpHash);
29 | console.log('\x1b[33m%s\x1b[0m', `SessionKey: `, response.sessionKey);
30 |
31 | // get transaction hash...
32 | console.log('Waiting for transaction...');
33 | let userOpsReceipt = null;
34 | const timeout = Date.now() + 60000; // 1 minute timeout
35 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
36 | await sleep(2);
37 | userOpsReceipt = await modularSdk.getUserOpReceipt(response.userOpHash);
38 | }
39 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
40 |
41 | const sessionKeys = await sessionKeyModule.getAssociatedSessionKeys();
42 | console.log('\x1b[33m%s\x1b[0m', `AssociatedSessionKeys: `, sessionKeys);
43 | }
44 |
45 | main()
46 | .catch(console.error)
47 | .finally(() => process.exit());
48 |
--------------------------------------------------------------------------------
/examples/scripts/commands/NFTTransfer.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import config from "../../config.json";
3 | import { printOp } from "../../../src/sdk/common/OperationUtils";
4 | import { sleep } from "../../../src/sdk/common";
5 | import { generateModularSDKInstance } from "../../helpers/sdk-helper";
6 | import { encodeFunctionData, getAddress, parseAbi } from "viem";
7 | import { erc721Abi } from "../../../src/sdk/common/abis";
8 |
9 | export default async function main(
10 | tknid: number,
11 | t: string,
12 | tkn: string,
13 | ) {
14 | const modularSdk = generateModularSDKInstance(
15 | config.signingKey,
16 | config.chainId,
17 | config.rpcProviderUrl
18 | );
19 | const address = await modularSdk.getCounterFactualAddress();
20 |
21 | const tokenId = tknid;
22 | const tokenAddress = getAddress(tkn);
23 | const to = getAddress(t);
24 | console.log(`Transferring NFT ${tknid} ...`);
25 |
26 | const erc721Data = encodeFunctionData({
27 | abi: parseAbi(erc721Abi),
28 | functionName: 'safeTransferFrom',
29 | args: [address, to, tokenId]
30 | });
31 |
32 | // clear the transaction batch
33 | await modularSdk.clearUserOpsFromBatch();
34 |
35 |
36 | await modularSdk.addUserOpsToBatch({to: tokenAddress, data: erc721Data});
37 | console.log(`Added transaction to batch`);
38 |
39 | const op = await modularSdk.estimate();
40 | console.log(`Estimated UserOp: ${await printOp(op)}`);
41 |
42 | // sign the userOp and sending to the bundler...
43 | const uoHash = await modularSdk.send(op);
44 | console.log(`UserOpHash: ${uoHash}`);
45 |
46 | // get transaction hash...
47 | console.log('Waiting for transaction...');
48 | let userOpsReceipt = null;
49 | const timeout = Date.now() + 60000; // 1 minute timeout
50 | while((userOpsReceipt == null) && (Date.now() < timeout)) {
51 | await sleep(2);
52 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
53 | }
54 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
55 | }
56 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/deep-compare.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '../../types/bignumber.js';
2 | import { isBigNumber } from './bignumber-utils.js';
3 |
4 | /**
5 | * @ignore
6 | */
7 | export function deepCompare(a: any, b: any): boolean {
8 | let result = false;
9 |
10 | const aType = typeof a;
11 | if (aType === typeof b) {
12 | switch (aType) {
13 | case 'object':
14 | if (a === null || b === null) {
15 | result = a === b;
16 | } else if (a === b) {
17 | result = true;
18 | } else if (isBigNumber(a) && isBigNumber(b)) {
19 | result = (a as BigNumber).eq(b);
20 | } else if (a instanceof Date && b instanceof Date) {
21 | result = a.getTime() === b.getTime();
22 | } else {
23 | const aIsArray = Array.isArray(a);
24 | const bIsArray = Array.isArray(b);
25 |
26 | if (aIsArray && bIsArray) {
27 | const aLength = a.length;
28 | const bLength = b.length;
29 |
30 | if (aLength === bLength) {
31 | result = true;
32 |
33 | for (let index = 0; index < aLength; index += 1) {
34 | if (!deepCompare(a[index], b[index])) {
35 | result = false;
36 | break;
37 | }
38 | }
39 | }
40 | } else if (!aIsArray && !bIsArray) {
41 | const aKeys = Object.keys(a);
42 | const bKeys = Object.keys(b);
43 |
44 | if (aKeys.length === bKeys.length) {
45 | result = true;
46 |
47 | for (const key of aKeys) {
48 | if (!deepCompare(a[key], b[key])) {
49 | result = false;
50 | break;
51 | }
52 | }
53 | }
54 | }
55 | }
56 | break;
57 |
58 | case 'function':
59 | result = true;
60 | break;
61 |
62 | default:
63 | result = a === b;
64 | }
65 | }
66 |
67 | return result;
68 | }
69 |
--------------------------------------------------------------------------------
/examples/scripts/commands/erc20Transfer.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import config from "../../config.json";
3 | import { printOp } from "../../../src/sdk/common/OperationUtils";
4 | import { sleep } from "../../../src/sdk/common";
5 | import { generateModularSDKInstance, getTokenMetaData } from "../../helpers/sdk-helper";
6 | import { encodeFunctionData, getAddress, parseAbi, parseUnits } from "viem";
7 | import { erc20Abi } from "../../../src/sdk/common/abis";
8 |
9 | export default async function main(
10 | tokenAddress: string,
11 | recipientAddress: string,
12 | transferAmount: string,
13 | ) {
14 | const modularSdk = generateModularSDKInstance(
15 | config.signingKey,
16 | config.chainId,
17 | config.rpcProviderUrl
18 | );
19 |
20 | const tokenMetadata = await getTokenMetaData(config.rpcProviderUrl, tokenAddress);
21 |
22 | const amount = parseUnits(transferAmount, tokenMetadata.decimal);
23 | console.log(`Transferring ${transferAmount} ${tokenMetadata.symbol}...`);
24 | const transferData = encodeFunctionData({
25 | functionName: 'transfer',
26 | abi: parseAbi(erc20Abi),
27 | args: [getAddress(recipientAddress), amount]
28 | });
29 |
30 | // clear the transaction batch
31 | await modularSdk.clearUserOpsFromBatch();
32 |
33 | await modularSdk.addUserOpsToBatch({to: tokenAddress, data: transferData});
34 | console.log(`Added transaction to batch`);
35 |
36 | const op = await modularSdk.estimate();
37 | console.log(`Estimated UserOp: ${await printOp(op)}`);
38 |
39 | // sign the UserOp and sending to the bundler...
40 | const uoHash = await modularSdk.send(op);
41 | console.log(`UserOpHash: ${uoHash}`);
42 |
43 | // get transaction hash...
44 | console.log('Waiting for transaction...');
45 | let userOpsReceipt = null;
46 | const timeout = Date.now() + 60000; // 1 minute timeout
47 | while((userOpsReceipt == null) && (Date.now() < timeout)) {
48 | await sleep(2);
49 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
50 | }
51 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
52 | }
53 |
--------------------------------------------------------------------------------
/examples/scripts/commands/erc20Approve.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import config from "../../config.json";
3 | import { printOp } from "../../../src/sdk/common/OperationUtils";
4 | import { sleep } from "../../../src/sdk/common";
5 | import { generateModularSDKInstance, getTokenMetaData } from "../../helpers/sdk-helper";
6 | import { encodeFunctionData, getAddress, parseAbi, parseUnits } from "viem";
7 | import { erc20Abi } from "../../../src/sdk/common/abis";
8 |
9 | export default async function main(
10 | tkn: string,
11 | s: string,
12 | amt: string,
13 | ) {
14 | // initializating sdk...
15 | const modularSdk = generateModularSDKInstance(
16 | config.signingKey,
17 | config.chainId,
18 | config.rpcProviderUrl
19 | );
20 | const address = await modularSdk.getCounterFactualAddress();
21 | console.log(`Etherspot address: ${address}`)
22 |
23 | const tokenMetadata = await getTokenMetaData(config.rpcProviderUrl, tkn);
24 | const amount = parseUnits(amt, tokenMetadata.decimal);
25 | const approveData = encodeFunctionData(
26 | {
27 | functionName: 'approve',
28 | abi: parseAbi(erc20Abi),
29 | args: [getAddress(s), amount]
30 | });
31 |
32 |
33 | console.log(`Approving ${amt} ${tokenMetadata.symbol}...`);
34 | // clear the transaction batch
35 | await modularSdk.clearUserOpsFromBatch();
36 |
37 | await modularSdk.addUserOpsToBatch({to: tkn, data: approveData});
38 | console.log(`Added transaction to batch`);
39 |
40 | const op = await modularSdk.estimate();
41 | console.log(`Estimated UserOp: ${await printOp(op)}`);
42 |
43 | // sign the userOp and sending to the bundler...
44 | const uoHash = await modularSdk.send(op);
45 | console.log(`UserOpHash: ${uoHash}`);
46 |
47 | // get transaction hash...
48 | console.log('Waiting for transaction...');
49 | let userOpsReceipt = null;
50 | const timeout = Date.now() + 60000; // 1 minute timeout
51 | while((userOpsReceipt == null) && (Date.now() < timeout)) {
52 | await sleep(2);
53 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
54 | }
55 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
56 | }
57 |
--------------------------------------------------------------------------------
/src/sdk/common/utils/hashing-utils.ts:
--------------------------------------------------------------------------------
1 | import { encodePacked, Hex } from "viem";
2 | import { isAddress } from "./viem-utils.js";
3 | import { BytesLike } from "../index.js";
4 | import { isHex as isAHex, stringToBytes} from 'viem';
5 |
6 | export function keccak256(data: BytesLike): string {
7 | let result = '';
8 |
9 | if (data) {
10 | switch (typeof data) {
11 | case 'string':
12 | if (isAddress(data)) {
13 | result = keccak256(encodePacked(['address'], [data as Hex]))
14 | } else if (isHex(data)) {
15 | result = keccak256(encodePacked(['bytes'], [data as Hex]));
16 | } else {
17 | result = keccak256(encodePacked(['string'], [data as Hex]));
18 | }
19 | break;
20 | case 'object': {
21 | //result = utils.solidityKeccak256(['bytes'], [data]);
22 | // TODO-LibraryFix - this needs debugging as its migrated from ethers
23 | result = keccak256(encodePacked(['bytes'], [data.toString() as Hex]));
24 | break;
25 | }
26 | }
27 | }
28 |
29 | return result;
30 | }
31 |
32 | export function isHex(hex: string, size = 0): boolean {
33 | let result = isAHex(hex);
34 |
35 | if (result && size > 0) {
36 | result = hex.length === size * 2 + 2;
37 | }
38 |
39 | return result;
40 | }
41 |
42 | export function toHexFromBytesLike(data: BytesLike): string {
43 | let result = '';
44 |
45 | if (data !== null) {
46 | switch (typeof data) {
47 | case 'string':
48 | if (isHex(data)) {
49 | result = data;
50 | } else {
51 | result = toHexFromBytesLike(stringToBytes(data));
52 | }
53 | break;
54 |
55 | case 'object':
56 | try {
57 | result = toHexFromBytesLike(data as any);
58 | } catch (err) {
59 | result = '';
60 | }
61 | break;
62 | }
63 | }
64 |
65 | if (!result) {
66 | throw new Error('invalid hex data');
67 | }
68 |
69 | return result;
70 | }
71 |
72 | export function concatHex(...hex: string[]): string {
73 | return hex.map((item, index) => (index ? item.slice(2) : item)).join('');
74 | }
75 |
--------------------------------------------------------------------------------
/examples/modules/uninstall-module.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { MODULE_TYPE, sleep } from '../../src/sdk/common';
3 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
4 |
5 | dotenv.config();
6 |
7 | // tsx examples/modules/uninstall-module.ts
8 | async function main() {
9 | const bundlerApiKey = 'etherspot_public_key';
10 |
11 | // initializating sdk...
12 | const modularSdk = generateModularSDKInstance(
13 | process.env.WALLET_PRIVATE_KEY as string,
14 | Number(process.env.CHAIN_ID), bundlerApiKey);
15 |
16 | // get address of EtherspotWallet
17 | const address: string = await modularSdk.getCounterFactualAddress();
18 |
19 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
20 |
21 | //this should be previous node address of the module to be uninstalled and the deinit data
22 | //deinit data is the data that is passed to the module to be uninstalled
23 | // here we need to call the function which can find out the address of previous node of the module to be uninstalled
24 | // and the deinit data can be 0x00 as default value
25 | const deInitDataDefault = '0x00';
26 |
27 | //generate deinit data...
28 | const deInitData = await modularSdk.generateModuleDeInitData(
29 | MODULE_TYPE.VALIDATOR,
30 | '0x9509aae8990bfA12BE09130BB822C37F3086863E',
31 | deInitDataDefault);
32 |
33 | console.log(`deinitData: ${deInitData}`);
34 |
35 | // default : 0xD6dc0A5Ca1EC90D1283A6d13642e8186059fF63B
36 | // 0x22A55192a663591586241D42E603221eac49ed09
37 | // 0xF4CDE8B11500ca9Ea108c5838DD26Ff1a4257a0c
38 | const uoHash = await modularSdk.uninstallModule(MODULE_TYPE.VALIDATOR,
39 | '0x9509aae8990bfA12BE09130BB822C37F3086863E', deInitData);
40 | console.log(`UserOpHash: ${uoHash}`);
41 |
42 | // get transaction hash...
43 | console.log('Waiting for transaction...');
44 | let userOpsReceipt = null;
45 | const timeout = Date.now() + 60000; // 1 minute timeout
46 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
47 | await sleep(2);
48 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
49 | }
50 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
51 | }
52 |
53 | main()
54 | .catch(console.error)
55 | .finally(() => process.exit());
56 |
--------------------------------------------------------------------------------
/examples/basics/transfer-funds.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { parseEther } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 |
9 | const recipient = '0x80a1874E1046B1cc5deFdf4D3153838B72fF94Ac'; // recipient wallet address
10 | const value = '0.0000001'; // transfer value
11 | const bundlerApiKey = 'etherspot_public_key';
12 |
13 | // tsx examples/basics/transfer-funds.ts
14 | async function main() {
15 | // initializating sdk...
16 | const modularSdk = generateModularSDKInstance(
17 | process.env.WALLET_PRIVATE_KEY as string,
18 | Number(process.env.CHAIN_ID), bundlerApiKey);
19 |
20 |
21 | // get address of EtherspotWallet...
22 | const address: string = await modularSdk.getCounterFactualAddress();
23 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
24 |
25 | // clear the transaction batch
26 | await modularSdk.clearUserOpsFromBatch();
27 |
28 | // add transactions to the batch
29 | const transactionBatch = await modularSdk.addUserOpsToBatch({ to: recipient, value: parseEther(value) });
30 | console.log('transactions: ', transactionBatch);
31 |
32 | // get balance of the account address
33 | const balance = await modularSdk.getNativeBalance();
34 |
35 | console.log('balances: ', balance);
36 |
37 | // estimate transactions added to the batch and get the fee data for the UserOp
38 | const op = await modularSdk.estimate();
39 | console.log(`Estimate UserOp: ${await printOp(op)}`);
40 |
41 | // sign the UserOp and sending to the bundler...
42 | const uoHash = await modularSdk.send(op);
43 | console.log(`UserOpHash: ${uoHash}`);
44 |
45 | // get transaction hash...
46 | console.log('Waiting for transaction...');
47 | let userOpsReceipt = null;
48 | const timeout = Date.now() + 1200000; // 1 minute timeout
49 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
50 | await sleep(2);
51 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
52 | }
53 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
54 | }
55 |
56 | main()
57 | .catch(console.error)
58 | .finally(() => process.exit());
59 |
--------------------------------------------------------------------------------
/examples/basics/callGasLimit.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { parseEther } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 |
9 | const recipient = '0x80a1874E1046B1cc5deFdf4D3153838B72fF94Ac'; // recipient wallet address
10 | const value = '0.0001'; // transfer value
11 | const bundlerApiKey = 'etherspot_public_key';
12 |
13 | async function main() {
14 | // initializating sdk...
15 | const modularSdk = generateModularSDKInstance(
16 | process.env.WALLET_PRIVATE_KEY as string,
17 | Number(process.env.CHAIN_ID), bundlerApiKey);
18 |
19 | // get address of EtherspotWallet...
20 | const address: string = await modularSdk.getCounterFactualAddress();
21 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
22 |
23 | // clear the transaction batch
24 | await modularSdk.clearUserOpsFromBatch();
25 |
26 | // add transactions to the batch
27 | const transactionBatch = await modularSdk.addUserOpsToBatch({ to: recipient, value: parseEther(value) });
28 | console.log('transactions: ', transactionBatch);
29 |
30 | // get balance of the account address
31 | const balance = await modularSdk.getNativeBalance();
32 |
33 | console.log('balances: ', balance);
34 |
35 | // estimate transactions added to the batch and get the fee data for the UserOp
36 | // passing callGasLimit as 40000 to manually set it
37 | const op = await modularSdk.estimate({ callGasLimit: 4000 });
38 | console.log(`Estimate UserOp: ${await printOp(op)}`);
39 |
40 | // sign the UserOp and sending to the bundler...
41 | const uoHash = await modularSdk.send(op);
42 | console.log(`UserOpHash: ${uoHash}`);
43 |
44 | // get transaction hash...
45 | console.log('Waiting for transaction...');
46 | let userOpsReceipt = null;
47 | const timeout = Date.now() + 60000; // 1 minute timeout
48 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
49 | await sleep(2);
50 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
51 | }
52 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
53 | }
54 |
55 | main()
56 | .catch(console.error)
57 | .finally(() => process.exit());
58 |
--------------------------------------------------------------------------------
/src/sdk/network/network.service.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 | import { NetworkConfig } from './index.js';
3 | import { ObjectSubject, Service, Exception } from '../common/index.js';
4 | import { Networks, CHAIN_ID_TO_NETWORK_NAME, SupportedNetworks, NetworkNames } from './constants.js';
5 | import { Network } from './interfaces.js';
6 |
7 | export class NetworkService extends Service {
8 | readonly network$ = new ObjectSubject(null);
9 | readonly chainId$: Observable;
10 | readonly defaultNetwork: Network;
11 | readonly supportedNetworks: Network[];
12 | readonly externalContractAddresses = new Map();
13 |
14 | constructor(defaultChainId?: number) {
15 | super();
16 | this.supportedNetworks = SupportedNetworks
17 | .map((chainId) => {
18 | const name = CHAIN_ID_TO_NETWORK_NAME[chainId];
19 | return !name
20 | ? null
21 | : {
22 | chainId,
23 | name,
24 | };
25 | })
26 | .filter((value) => !!value);
27 |
28 | if (!this.supportedNetworks.length) {
29 | throw new Exception('Invalid network config');
30 | }
31 |
32 | this.defaultNetwork = defaultChainId
33 | ? this.supportedNetworks.find(({ chainId }) => chainId === defaultChainId)
34 | : this.supportedNetworks[0];
35 |
36 | if (!this.defaultNetwork) {
37 | this.defaultNetwork = this.supportedNetworks.find(({ chainId }) => chainId === 1)
38 | }
39 |
40 | this.chainId$ = this.network$.observeKey('chainId');
41 | }
42 |
43 | get network(): Network {
44 | return this.network$.value;
45 | }
46 |
47 | get chainId(): number {
48 | return this.network ? this.network.chainId : null;
49 | }
50 |
51 | useDefaultNetwork(): void {
52 | this.network$.next(this.defaultNetwork);
53 | }
54 |
55 | switchNetwork(networkName: NetworkNames): void {
56 | this.network$.next(this.supportedNetworks.find(({ name }) => name === networkName) || null);
57 | }
58 |
59 | isNetworkSupported(chainId: number): boolean {
60 | return SupportedNetworks.includes(chainId);
61 | }
62 |
63 | getNetworkConfig(chainId: number): NetworkConfig {
64 | const networkConfig = Networks[chainId];
65 | if (!networkConfig) {
66 | throw new Error(`No network config found for network '${chainId}'`);
67 | }
68 | return networkConfig;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/examples/basics/transfer-nft.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { encodeFunctionData, parseAbi } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 |
9 | // add/change these values
10 | const recipient = '0xD129dB5e418e389c3F7D3ae0B8771B3f76799A52'; // recipient wallet address
11 | const tokenAddress = '0xe55C5793a52AF819fBf3e87a23B36708E6FDd2Cc';
12 | const tokenId = 4;
13 | const bundlerApiKey = 'etherspot_public_key';
14 |
15 | // tsx examples/basics/transfer-nft.ts
16 | async function main() {
17 | // initializating sdk...
18 | const modularSdk = generateModularSDKInstance(
19 | process.env.WALLET_PRIVATE_KEY as string,
20 | Number(process.env.CHAIN_ID), bundlerApiKey);
21 |
22 | // get address of EtherspotWallet...
23 | const address: string = await modularSdk.getCounterFactualAddress();
24 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
25 |
26 | const erc721Interface = [
27 | 'function safeTransferFrom(address _from, address _to, uint256 _tokenId)'
28 | ];
29 |
30 | const erc721Data = encodeFunctionData(
31 | {
32 | functionName: 'safeTransferFrom',
33 | abi: parseAbi(erc721Interface),
34 | args: [address, recipient, tokenId]
35 | });
36 |
37 | // clear the transaction batch
38 | await modularSdk.clearUserOpsFromBatch();
39 |
40 | // add transactions to the batch
41 | const userOpsBatch = await modularSdk.addUserOpsToBatch({ to: tokenAddress, data: erc721Data });
42 | console.log('transactions: ', userOpsBatch);
43 |
44 | // sign transactions added to the batch
45 | const op = await modularSdk.estimate();
46 | console.log(`Estimated UserOp: ${await printOp(op)}`);
47 |
48 | // sign the userOps and sending to the bundler...
49 | const uoHash = await modularSdk.send(op);
50 | console.log(`UserOpHash: ${uoHash}`);
51 |
52 | // get transaction hash...
53 | console.log('Waiting for transaction...');
54 | let userOpsReceipt = null;
55 | const timeout = Date.now() + 60000; // 1 minute timeout
56 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
57 | await sleep(2);
58 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
59 | }
60 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
61 | }
62 |
63 | main()
64 | .catch(console.error)
65 | .finally(() => process.exit());
66 |
--------------------------------------------------------------------------------
/examples/sessionkeys/rotate-sessionkey-module.ts:
--------------------------------------------------------------------------------
1 | import { EtherspotBundler, ModularSdk, SessionKeyValidator } from '../../src';
2 | import * as dotenv from 'dotenv';
3 | import { getViemAccount, sleep } from '../../src/sdk/common';
4 | import { KeyStore } from '../../src/sdk/SessionKeyValidator';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 |
9 | // tsx examples/sessionkeys/rotate-sessionkey-module.ts
10 | async function main() {
11 | const bundlerApiKey = 'etherspot_public_key';
12 |
13 | // initializating sdk...
14 | const modularSdk = generateModularSDKInstance(
15 | process.env.WALLET_PRIVATE_KEY as string,
16 | Number(process.env.CHAIN_ID), bundlerApiKey);
17 |
18 | // get address of EtherspotWallet
19 | const address: string = await modularSdk.getCounterFactualAddress();
20 |
21 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
22 |
23 | const token = process.env.TOKEN_ADDRESS as string; // token address
24 | const functionSelector = process.env.FUNCTION_SELECTOR as string;
25 | const spendingLimit = '1000000000000000000000';
26 | const validAfter = new Date().getTime();
27 | const validUntil = new Date().getTime() + 24 * 60 * 60 * 1000;
28 | const oldSessionKey = '0xa2d5Fd2DE86221EEFB616325ff976EF85E7a64aB';
29 |
30 | // get instance of SessionKeyValidator
31 | const sessionKeyModule = await SessionKeyValidator.create(modularSdk);
32 |
33 | const response = await sessionKeyModule.rotateSessionKey(
34 | token,
35 | functionSelector,
36 | spendingLimit,
37 | validAfter,
38 | validUntil,
39 | oldSessionKey,
40 | KeyStore.AWS
41 | );
42 |
43 | console.log('\x1b[33m%s\x1b[0m', `UserOpHash: `, response.userOpHash);
44 | console.log('\x1b[33m%s\x1b[0m', `SessionKey: `, response.sessionKey);
45 |
46 | // get transaction hash...
47 | console.log('Waiting for transaction...');
48 | let userOpsReceipt = null;
49 | const timeout = Date.now() + 60000; // 1 minute timeout
50 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
51 | await sleep(2);
52 | userOpsReceipt = await modularSdk.getUserOpReceipt(response.userOpHash);
53 | }
54 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
55 |
56 | const sessionKeys = await sessionKeyModule.getAssociatedSessionKeys();
57 | console.log('\x1b[33m%s\x1b[0m', `AssociatedSessionKeys: `, sessionKeys);
58 | }
59 |
60 | main()
61 | .catch(console.error)
62 | .finally(() => process.exit());
63 |
--------------------------------------------------------------------------------
/examples/basics/verify-signatures.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from 'dotenv';
2 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
3 | import { Address, createPublicClient, Hex, http } from 'viem';
4 | import { sepolia } from 'viem/chains';
5 |
6 | dotenv.config();
7 |
8 | // tsx examples/basics/get-address.ts
9 | async function main() {
10 | const bundlerApiKey = 'etherspot_public_key';
11 |
12 | // initializating sdk...
13 | const modularSdk = generateModularSDKInstance(
14 | process.env.WALLET_PRIVATE_KEY as string,
15 | Number(process.env.CHAIN_ID), bundlerApiKey);
16 |
17 | // get EtherspotWallet address...
18 | const address: string = await modularSdk.getCounterFactualAddress();
19 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
20 |
21 | const typedData = {
22 | domain: {
23 | name: 'EtherspotModular',
24 | version: '1.0.0',
25 | chainId: 11155111,
26 | verifyingContract: `0x${address.slice(2)}` as Address,
27 | },
28 | types: {
29 | Person: [
30 | { name: 'name', type: 'string' },
31 | { name: 'wallet', type: 'address' },
32 | ],
33 | Mail: [
34 | { name: 'from', type: 'Person' },
35 | { name: 'to', type: 'Person' },
36 | { name: 'contents', type: 'string' },
37 | ],
38 | },
39 | primaryType: 'Mail',
40 | message: {
41 | from: {
42 | name: 'Cow',
43 | wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
44 | },
45 | to: {
46 | name: 'Bob',
47 | wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
48 | },
49 | contents: 'Hello, Bob!',
50 | },
51 | }
52 |
53 | const signTypedData = await modularSdk.signTypedData(typedData);
54 | const sign = await modularSdk.signMessage({message: 'Hello'})
55 |
56 | console.log('signature: ', sign, signTypedData);
57 |
58 | const publicClient = createPublicClient({
59 | chain: sepolia,
60 | transport: http(`https://testnet-rpc.etherspot.io/v2/11155111?api-key=${bundlerApiKey}`),
61 | })
62 |
63 | console.log(await publicClient.verifyMessage({
64 | address: address as Address,
65 | signature: sign as Hex,
66 | message: 'Hello'
67 | }))
68 |
69 | console.log(await publicClient.verifyTypedData({
70 | address: address as Address,
71 | domain: typedData.domain,
72 | types: typedData.types,
73 | primaryType: 'Mail',
74 | message: typedData.message,
75 | signature: signTypedData as Hex,
76 | }))
77 |
78 | }
79 |
80 | main()
81 | .catch(console.error)
82 | .finally(() => process.exit());
83 |
--------------------------------------------------------------------------------
/.github-pages/theme/templates/reflection.hbs:
--------------------------------------------------------------------------------
1 | {{#with model}}
2 | {{#if hasComment}}
3 |
6 | {{/if}}
7 | {{/with}}
8 |
9 | {{#if model.typeParameters}}
10 |
11 | Type parameters
12 | {{#with model}}{{> typeParameters}}{{/with}}
13 |
14 | {{/if}}
15 |
16 | {{#if model.typeHierarchy}}
17 |
18 | Hierarchy
19 | {{#with model.typeHierarchy}}{{> hierarchy}}{{/with}}
20 |
21 | {{/if}}
22 |
23 | {{#if model.implementedTypes}}
24 |
25 | Implements
26 |
27 | {{#each model.implementedTypes}}
28 | - {{#compact}}{{> type}}{{/compact}}
29 | {{/each}}
30 |
31 |
32 | {{/if}}
33 |
34 | {{#if model.implementedBy}}
35 |
36 | Implemented by
37 |
38 | {{#each model.implementedBy}}
39 | - {{#compact}}{{> type}}{{/compact}}
40 | {{/each}}
41 |
42 |
43 | {{/if}}
44 |
45 | {{#if model.signatures}}
46 |
47 | Callable
48 | {{#with model}}{{> member.signatures}}{{/with}}
49 |
50 | {{/if}}
51 |
52 | {{#if model.indexSignature}}
53 |
54 | Indexable
55 | {{#compact}}
56 | [
57 | {{#each model.indexSignature.parameters}}
58 | {{name}}: {{#with type}}{{>type}}{{/with}}
59 | {{/each}}
60 | ]:
61 | {{#with model.indexSignature.type}}{{>type}}{{/with}}
62 | {{/compact}}
63 |
64 | {{#with model.indexSignature}}
65 | {{> comment}}
66 | {{/with}}
67 |
68 | {{#if model.indexSignature.type.declaration}}
69 | {{#with model.indexSignature.type.declaration}}
70 | {{> parameter}}
71 | {{/with}}
72 | {{/if}}
73 |
74 | {{/if}}
75 |
76 | {{#with model}}
77 | {{> index}}
78 | {{> members}}
79 | {{/with}}
80 |
--------------------------------------------------------------------------------
/examples/sessionkeys/enable-sessionkey-module.ts:
--------------------------------------------------------------------------------
1 | import { EtherspotBundler, ModularSdk, SessionKeyValidator } from '../../src';
2 | import * as dotenv from 'dotenv';
3 | import { getViemAccount, sleep } from '../../src/sdk/common';
4 | import { KeyStore } from '../../src/sdk/SessionKeyValidator';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 | const secondsInAMonth = 30 * 24 * 60 * 60; // 2592000 seconds
9 |
10 | // tsx examples/sessionkeys/enable-sessionkey-module.ts
11 | async function main() {
12 | const bundlerApiKey = 'etherspot_public_key';
13 |
14 | // initializating sdk...
15 | const modularSdk = generateModularSDKInstance(
16 | process.env.WALLET_PRIVATE_KEY as string,
17 | Number(process.env.CHAIN_ID), bundlerApiKey);
18 |
19 |
20 | // get address of EtherspotWallet
21 | const address: string = await modularSdk.getCounterFactualAddress();
22 |
23 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
24 |
25 | const token = process.env.TOKEN_ADDRESS as string; // token address
26 | const functionSelector = process.env.FUNCTION_SELECTOR as string;
27 | const spendingLimit = '1000000000000000000000';
28 | const validAfter = getEpochTimeInSeconds() + 31; // 10 seconds from now
29 | const validUntil = getEpochTimeInSeconds() + secondsInAMonth;
30 |
31 | // get instance of SessionKeyValidator
32 | const sessionKeyModule = await SessionKeyValidator.create(modularSdk);
33 |
34 | const response = await sessionKeyModule.enableSessionKey(
35 | token,
36 | functionSelector,
37 | spendingLimit,
38 | validAfter,
39 | validUntil,
40 | KeyStore.AWS
41 | );
42 |
43 | console.log('\x1b[33m%s\x1b[0m', `UserOpHash: `, response.userOpHash);
44 | console.log('\x1b[33m%s\x1b[0m', `SessionKey: `, response.sessionKey);
45 |
46 | // get transaction hash...
47 | console.log('Waiting for transaction...');
48 | let userOpsReceipt = null;
49 | const timeout = Date.now() + 60000; // 1 minute timeout
50 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
51 | await sleep(2);
52 | userOpsReceipt = await modularSdk.getUserOpReceipt(response.userOpHash);
53 | }
54 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
55 |
56 | const sessionKeys = await sessionKeyModule.getAssociatedSessionKeys();
57 | console.log('\x1b[33m%s\x1b[0m', `AssociatedSessionKeys: `, sessionKeys);
58 | }
59 |
60 | main()
61 | .catch(console.error)
62 | .finally(() => process.exit());
63 |
64 | const getEpochTimeInSeconds = () => Math.floor(new Date().getTime() / 1000);
65 |
--------------------------------------------------------------------------------
/src/sdk/base/calcPreVerificationGas.ts:
--------------------------------------------------------------------------------
1 |
2 | import { NotPromise, packUserOp } from '../common/index.js';
3 | import { Buffer } from 'buffer';
4 | import { toBytes, toHex } from 'viem';
5 | import { BaseAccountUserOperationStruct } from '../types/user-operation-types.js';
6 |
7 | export interface GasOverheads {
8 | /**
9 | * fixed overhead for entire handleOp bundle.
10 | */
11 | fixed: number;
12 |
13 | /**
14 | * per userOp overhead, added on top of the above fixed per-bundle.
15 | */
16 | perUserOp: number;
17 |
18 | /**
19 | * overhead for userOp word (32 bytes) block
20 | */
21 | perUserOpWord: number;
22 |
23 | // perCallDataWord: number
24 |
25 | /**
26 | * zero byte cost, for calldata gas cost calculations
27 | */
28 | zeroByte: number;
29 |
30 | /**
31 | * non-zero byte cost, for calldata gas cost calculations
32 | */
33 | nonZeroByte: number;
34 |
35 | /**
36 | * expected bundle size, to split per-bundle overhead between all ops.
37 | */
38 | bundleSize: number;
39 |
40 | /**
41 | * expected length of the userOp signature.
42 | */
43 | sigSize: number;
44 | }
45 |
46 | export const DefaultGasOverheads: GasOverheads = {
47 | fixed: 21000,
48 | perUserOp: 18300,
49 | perUserOpWord: 4,
50 | zeroByte: 4,
51 | nonZeroByte: 16,
52 | bundleSize: 1,
53 | sigSize: 65,
54 | };
55 |
56 | /**
57 | * calculate the preVerificationGas of the given UserOperation
58 | * preVerificationGas (by definition) is the cost overhead that can't be calculated on-chain.
59 | * it is based on parameters that are defined by the Ethereum protocol for external transactions.
60 | * @param userOp filled userOp to calculate. The only possible missing fields can be the signature and preVerificationGas itself
61 | * @param overheads gas overheads to use, to override the default values
62 | */
63 | export function calcPreVerificationGas(
64 | userOp: Partial,
65 | overheads?: Partial,
66 | ): number {
67 | const ov = { ...DefaultGasOverheads, ...(overheads ?? {}) };
68 | const p: NotPromise = {
69 | // dummy values, in case the UserOp is incomplete.
70 | preVerificationGas: 21000, // dummy value, just for calldata cost
71 | signature: toHex(Buffer.alloc(ov.sigSize, 1)), // dummy signature
72 | ...userOp,
73 | } as any;
74 |
75 | const packed = toBytes(packUserOp(p, false));
76 | const callDataCost = packed.map((x) => (x === 0 ? ov.zeroByte : ov.nonZeroByte)).reduce((sum, x) => sum + x);
77 | const ret = Math.round(callDataCost + ov.fixed / ov.bundleSize + ov.perUserOp + ov.perUserOpWord * packed.length);
78 | return ret;
79 | }
80 |
--------------------------------------------------------------------------------
/examples/paymaster/paymaster.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { parseEther } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 |
9 | const recipient = '0x80a1874E1046B1cc5deFdf4D3153838B72fF94Ac'; // recipient wallet address
10 | const value = '0.0000001'; // transfer value
11 | const apiKey = 'etherspot_public_key'; // Only testnets are available, if you need further assistance in setting up a paymaster service for your dapp, please reach out to us on discord or https://etherspot.fyi/arka/intro
12 | const bundlerApiKey = 'etherspot_public_key';
13 |
14 | // tsx examples/paymaster/paymaster.ts
15 | async function main() {
16 | // initializating sdk...
17 | const modularSdk = generateModularSDKInstance(
18 | process.env.WALLET_PRIVATE_KEY,
19 | Number(process.env.CHAIN_ID),
20 | bundlerApiKey
21 | );// Testnets dont need apiKey on bundlerProvider
22 |
23 | // get address of EtherspotWallet...
24 | const address: string = await modularSdk.getCounterFactualAddress();
25 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
26 |
27 | // clear the transaction batch
28 | await modularSdk.clearUserOpsFromBatch();
29 |
30 | // add transactions to the batch
31 | const transactionBatch = await modularSdk.addUserOpsToBatch({ to: recipient, value: parseEther(value) });
32 | console.log('transactions: ', transactionBatch);
33 |
34 | // get balance of the account address
35 | const balance = await modularSdk.getNativeBalance();
36 |
37 | console.log('balances: ', balance);
38 |
39 | // estimate transactions added to the batch and get the fee data for the UserOp
40 | const op = await modularSdk.estimate({
41 | paymasterDetails: { url: `https://arka.etherspot.io?apiKey=${apiKey}&chainId=${Number(process.env.CHAIN_ID)}`, context: { mode: 'sponsor' } }
42 | });
43 | console.log(`Estimate UserOp: ${await printOp(op)}`);
44 |
45 | // sign the UserOp and sending to the bundler...
46 | const uoHash = await modularSdk.send(op);
47 | console.log(`UserOpHash: ${uoHash}`);
48 |
49 | // get transaction hash...
50 | console.log('Waiting for transaction...');
51 | let userOpsReceipt = null;
52 | const timeout = Date.now() + 60000; // 1 minute timeout
53 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
54 | await sleep(2);
55 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
56 | }
57 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
58 | }
59 |
60 | main()
61 | .catch(console.error)
62 | .finally(() => process.exit());
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Etherspot Modular SDK
3 |
4 |
5 |
6 |

7 |
8 |
9 | Etherspot Modular SDK
10 |
11 |
12 |
13 |
14 | --------------
15 |
16 | >[!IMPORTANT]
17 | >This repo/software is under active development.
18 | >
19 | >[](https://www.npmjs.com/package/@etherspot/modular-sdk)
20 |
21 |
22 | ## 🐞 Etherspot Modular
23 |
24 | Etherspot Modular is a fully open source SDK which let's dapp developers easily get building with Account Abstraction.
25 |
26 | The SDK makes it incredibly easy to get up and running with Account Abstraction. From social logins to transaction batching, using an Etherspot smart wallet can give your dapp a web2 like interface to ensure the user has a seamless experience.
27 |
28 | ## ⚙ Get started
29 |
30 | You can either get started by installing the packages yourself here:
31 |
32 | ```bash
33 | npm i @etherspot/modular-sdk
34 | ```
35 |
36 | Or follow our introductory guide on our docs [here](https://etherspot.fyi/getting-started) which walk you through
37 | cloning down an example repo and setting up a dapp in your own environment.
38 |
39 | The testnet bundler API key `etherspot_public_key` is included in the example programs which is a public API key with rate limits, to get higher limits register to https://portal.etherspot.io
40 |
41 | ### Note: The smart wallet address differs on the `XDC Testnet` compared to other chains.
42 |
43 | ## 📖 Documentation
44 |
45 | - [Quick Start](https://etherspot.fyi/getting-started)
46 | - [Instantiate the SDK](https://etherspot.fyi/prime-sdk/instantiation)
47 | - [Running SDK examples](https://etherspot.fyi/examples/intro)
48 | - [Function List](https://etherspot.fyi/prime-sdk/function)
49 | - [Batching Transactions](https://etherspot.fyi/prime-sdk/batching-transactions)
50 |
51 | ## 🔗 Important Links
52 |
53 | - [Skandha Bundler](https://etherspot.fyi/skandha/intro)
54 | - [Arka Paymaster](https://etherspot.fyi/arka/intro)
55 | - [SDK Reference](https://sdk.etherspot.io/)
56 |
57 | ## 🏌️♂️ Contributions
58 |
59 | Please feel free to open issues or PRs to this repo.
60 |
61 | ## 🔐 Security
62 |
63 | To report security issues please follow [guide](./SECURITY.md)
64 |
65 | ## 💬 Contact
66 |
67 | If you have any questions or feedback about Etherspot Modular, please feel free to reach out to us.
68 |
69 | - [Follow on Twitter](https://twitter.com/etherspot)
70 | - [Join our discord](https://discord.etherspot.io/)
71 |
72 | ## 📄 License
73 |
74 | Licensed under the [MIT License](https://github.com/etherspot/etherspot-modular-sdk/blob/master/LICENSE).
75 |
--------------------------------------------------------------------------------
/src/sdk/common/getInstalledModules.ts:
--------------------------------------------------------------------------------
1 | import { Address, PublicClient, parseAbi, zeroAddress } from 'viem'
2 | import { ModuleType } from './types.js'
3 | import { VIEM_SENTINEL_ADDRESS } from './constants.js'
4 | import { isContract } from './utils/viem-utils.js'
5 | import { accountAbi } from './abis.js'
6 | import { DEFAULT_QUERY_PAGE_SIZE } from '../network/constants.js'
7 |
8 | export const getInstalledModules = async ({
9 | client,
10 | moduleAddress,
11 | moduleTypes = ['validator', 'executor', 'hook', 'fallback'],
12 | pageSize = DEFAULT_QUERY_PAGE_SIZE
13 | }: {
14 | client: PublicClient
15 | moduleAddress: Address
16 | moduleTypes?: ModuleType[]
17 | pageSize: number
18 | }): Promise => {
19 | const modules: Address[] = []
20 | if (await isContract({ client, address: moduleAddress })) {
21 | for (const moduleType of moduleTypes) {
22 | switch (moduleType) {
23 | case 'validator':
24 | const validators = await getModulesPaginated({
25 | client,
26 | functionName: 'getValidatorPaginated',
27 | walletAddress: moduleAddress,
28 | pageSize: pageSize
29 | })
30 | validators && modules.push(...validators)
31 | break
32 | case 'executor':
33 | const executors = await getModulesPaginated({
34 | client,
35 | functionName: 'getExecutorsPaginated',
36 | walletAddress: moduleAddress,
37 | pageSize: pageSize
38 | })
39 | executors && modules.push(...executors)
40 | break
41 | case 'hook':
42 | const activeHook = (await client.readContract({
43 | address: moduleAddress,
44 | abi: parseAbi(accountAbi),
45 | functionName: 'getActiveHook',
46 | })) as Address
47 | modules.push(activeHook)
48 | break
49 | case 'fallback':
50 | // todo: implement on account or use events
51 | }
52 | }
53 | } else {
54 | throw new Error('Account has no init code and is not deployed')
55 | }
56 | const onlyModules = modules.filter((module) => module !== zeroAddress)
57 | const uniqueModules = Array.from(new Set(onlyModules))
58 | return uniqueModules
59 | }
60 |
61 | export const getModulesPaginated = async ({
62 | client,
63 | functionName,
64 | walletAddress,
65 | pageSize = DEFAULT_QUERY_PAGE_SIZE
66 | }: {
67 | client: PublicClient
68 | functionName: string
69 | walletAddress: Address
70 | pageSize?: number
71 | }) => {
72 | const data = (await client.readContract({
73 | address: walletAddress,
74 | abi: parseAbi(accountAbi),
75 | functionName: functionName,
76 | args: [VIEM_SENTINEL_ADDRESS, pageSize],
77 | })) as [Address[], Address]
78 | return data[0]
79 | }
--------------------------------------------------------------------------------
/examples/scripts/commands/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { Command } from "commander";
3 | import address from "./address";
4 | import transfer from "./transfer";
5 | import erc20Transfer from "./erc20Transfer";
6 | import erc20Approve from "./erc20Approve";
7 | import batchErc20Transfer from "./batchErc20Transfer";
8 | import nftTransfer from './NFTTransfer';
9 |
10 | const program = new Command();
11 |
12 | program
13 | .name("ERC-4337 Etherspot Implementation")
14 | .description(
15 | "A collection of example scripts for working with ERC-4337 Etherspot Implementation"
16 | )
17 | .version("0.1.0");
18 |
19 | program
20 | .command("address")
21 | .description("Generate a counterfactual address.")
22 | .action(address);
23 |
24 | program
25 | .command("transfer")
26 | .description("Transfer ETH")
27 | .requiredOption("-t, --to ", "The recipient address")
28 | .requiredOption("-amt, --amount ", "Amount in ETH to transfer")
29 | .action(async (opts) =>
30 | transfer(opts.to, opts.amount)
31 | );
32 |
33 | program
34 | .command("erc20Transfer")
35 | .description("Transfer ERC-20 token")
36 | .requiredOption("-tkn, --token ", "The token address")
37 | .requiredOption("-t, --to ", "The recipient address")
38 | .requiredOption("-amt, --amount ", "Amount of the token to transfer")
39 | .action(async (opts) =>
40 | erc20Transfer(opts.token, opts.to, opts.amount)
41 | );
42 |
43 | program
44 | .command("erc20Approve")
45 | .description("Approve spender for ERC-20 token")
46 | .requiredOption("-tkn, --token ", "The token address")
47 | .requiredOption("-s, --spender ", "The spender address")
48 | .requiredOption("-amt, --amount ", "Amount of the token to transfer")
49 | .action(async (opts) =>
50 | erc20Approve(opts.token, opts.spender, opts.amount)
51 | );
52 |
53 | program
54 | .command("batchErc20Transfer")
55 | .description("Batch transfer ERC-20 token")
56 | .requiredOption("-tkn, --token ", "The token address")
57 | .requiredOption(
58 | "-t, --to ",
59 | "Comma separated list of recipient addresses"
60 | )
61 | .requiredOption("-amt, --amount ", "Amount of the token to transfer")
62 | .action(async (opts) =>
63 | batchErc20Transfer(opts.token, opts.to.split(","), opts.amount)
64 | );
65 |
66 | program
67 | .command("NFTTransfer")
68 | .description("Transfer ERC-721 token")
69 | .requiredOption("-tkn, --token ", "The token address")
70 | .requiredOption("-t, --to ", "The recipient address")
71 | .requiredOption("-tknid, --tokenId ", "tokenId of the NFT to transfer")
72 | .action(async (opts) =>
73 | nftTransfer(opts.token, opts.to, opts.tokenId)
74 | );
75 |
76 | program.parse();
77 |
--------------------------------------------------------------------------------
/examples/turnkey/transfer-funds.ts:
--------------------------------------------------------------------------------
1 | import { TurnkeyClient } from "@turnkey/http";
2 | import { ApiKeyStamper } from "@turnkey/sdk-server";
3 | import { createAccount } from "@turnkey/viem";
4 | import { EtherspotBundler, ModularSdk } from "../../src";
5 | import { createWalletClient, http, parseEther } from "viem";
6 | import { polygonAmoy } from "viem/chains";
7 |
8 | async function main() {
9 | const turnkeyClient = new TurnkeyClient(
10 | { baseUrl: "https://api.turnkey.com" },
11 | new ApiKeyStamper({
12 | apiPrivateKey: "", // turnkey api private key
13 | apiPublicKey: "" // turnkey api public key
14 | })
15 | );
16 |
17 | const turnkeyAccount = await createAccount({
18 | client: turnkeyClient,
19 | organizationId: "", // turnkey organization id
20 | signWith: "", // wallet address created using ./create-wallet.ts
21 | });
22 |
23 | const walletClient = createWalletClient({
24 | transport: http("https://testnet-rpc.etherspot.io/v2/80002"),
25 | account: turnkeyAccount,
26 | chain: polygonAmoy
27 | });
28 |
29 | const bundlerApiKey = 'etherspot_public_key';
30 |
31 | const modularSdk = new ModularSdk(
32 | walletClient,
33 | {
34 | chainId: 80002,
35 | bundlerProvider: new EtherspotBundler(
36 | 80002,
37 | bundlerApiKey,
38 | )
39 | }
40 | );
41 |
42 | const recipient = '0x09FD4F6088f2025427AB1e89257A44747081Ed59'; // recipient wallet address
43 | const value = '0.0000001'; // transfer value
44 |
45 | await modularSdk.clearUserOpsFromBatch();
46 |
47 |
48 | // add transactions to the batch
49 | const transactionBatch = await modularSdk.addUserOpsToBatch({ to: recipient, value: parseEther(value) });
50 | console.log('transactions: ', transactionBatch);
51 |
52 | // get balance of the account address
53 | const balance = await modularSdk.getNativeBalance();
54 | console.log('balances: ', balance);
55 |
56 | // estimate transactions added to the batch and get the fee data for the UserOp
57 | const op = await modularSdk.estimate();
58 | console.log(`Estimate UserOp:`, op);
59 |
60 | // sign the UserOp and sending to the bundler...
61 | const uoHash = await modularSdk.send(op);
62 | console.log(`UserOpHash: ${uoHash}`);
63 |
64 | // get transaction hash...
65 | console.log('Waiting for transaction...');
66 | let userOpsReceipt = null;
67 | const timeout = Date.now() + 1200000; // 1 minute timeout
68 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
69 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
70 | }
71 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
72 | }
73 | main()
74 | .catch(console.error)
75 | .finally(() => process.exit());
--------------------------------------------------------------------------------
/examples/modules/install-hook.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { MODULE_TYPE, sleep } from '../../src/sdk/common';
4 | import { encodeAbiParameters, encodeFunctionData, Hex, parseAbi } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 | import { getHookMultiPlexerInitData } from '../pulse/utils';
7 | import { accountAbi } from '../../src/sdk/common/abis';
8 |
9 | dotenv.config();
10 |
11 | const bundlerApiKey = 'etherspot_public_key';
12 | const CHAIN_ID = '1';
13 | const HOOK_MULTIPLEXER_ADDRESS = '0xDcA918dd23456d321282DF9507F6C09A50522136';
14 |
15 | // tsx examples/modules/install-hook.ts
16 | async function main() {
17 | // Init SDK
18 | const modularSdk = generateModularSDKInstance(
19 | process.env.WALLET_PRIVATE_KEY as string,
20 | Number(CHAIN_ID),
21 | bundlerApiKey,
22 | );
23 |
24 | // Get counterfactual of ModularEtherspotWallet...
25 | const address: Hex = (await modularSdk.getCounterFactualAddress()) as Hex;
26 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
27 | // Get native balance
28 | const balance = await modularSdk.getNativeBalance();
29 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet native balance: ${balance}`);
30 | // Clear existing UserOps from batch
31 | await modularSdk.clearUserOpsFromBatch();
32 |
33 | /*//////////////////////////////////////////////////////////////
34 | INSTALL HOOK MULTIPLEXER WITH CREDIBLE ACCOUNT MODULE - AS HOOK
35 | //////////////////////////////////////////////////////////////*/
36 |
37 | //Get HookMultiPlexer init data with CredibleAccountHook as global subhook
38 | let hmpInitData = getHookMultiPlexerInitData([]);
39 | const hmpInstallCalldata = encodeFunctionData({
40 | abi: parseAbi(accountAbi),
41 | functionName: 'installModule',
42 | args: [BigInt(MODULE_TYPE.HOOK), HOOK_MULTIPLEXER_ADDRESS, hmpInitData],
43 | });
44 | // // Add UserOp to batch
45 | await modularSdk.addUserOpsToBatch({ to: address, data: hmpInstallCalldata });
46 |
47 | /*//////////////////////////////////////////////////////////////
48 | ESTIMATE/SEND USER OP
49 | //////////////////////////////////////////////////////////////*/
50 |
51 | // Estimate UserOp
52 | const op = await modularSdk.estimate();
53 | console.log(`Estimate UserOp: ${await printOp(op)}`);
54 | // Send UserOp
55 | const uoHash = await modularSdk.send(op);
56 | console.log(`UserOpHash: ${uoHash}`);
57 | // Await transaction hash
58 | console.log('Waiting for transaction...');
59 | let userOpsReceipt = null;
60 | const timeout = Date.now() + 1200000; // 1 minute timeout
61 | while (userOpsReceipt == null && Date.now() < timeout) {
62 | await sleep(2);
63 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
64 | }
65 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
66 | }
67 |
68 | main()
69 | .catch(console.error)
70 | .finally(() => process.exit());
71 |
--------------------------------------------------------------------------------
/examples/scripts/commands/batchErc20Transfer.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import config from "../../config.json";
3 | import { printOp } from "../../../src/sdk/common/OperationUtils";
4 | import { sleep } from "../../../src/sdk/common";
5 | import { generateModularSDKInstance } from "../../helpers/sdk-helper";
6 | import { encodeFunctionData, getAddress, Hex, parseAbi, parseUnits, PublicClient } from "viem";
7 | import { erc20Abi } from "../../../src/sdk/common/abis";
8 |
9 | // This example requires several layers of calls:
10 | // EntryPoint
11 | // ┕> sender.executeBatch
12 | // ┕> token.transfer (recipient 1)
13 | // ⋮
14 | // ┕> token.transfer (recipient N)
15 | export default async function main(
16 | tkn: string,
17 | t: Array,
18 | amt: string,
19 | ) {
20 | const bundlerApiKey = 'etherspot_public_key';
21 | // initializating sdk...
22 | const modularSdk = generateModularSDKInstance(
23 | config.signingKey,
24 | config.chainId,
25 | bundlerApiKey
26 | );
27 |
28 | const address = await modularSdk.getCounterFactualAddress();
29 | console.log(`Etherspot address: ${address}`)
30 |
31 | const token = getAddress(tkn);
32 |
33 | const publicClient: PublicClient = modularSdk.getPublicClient();
34 |
35 | const symbol = await publicClient.readContract({
36 | address: token as Hex,
37 | abi: parseAbi(erc20Abi),
38 | functionName: 'symbol',
39 | args: []
40 | })
41 |
42 | const decimals = await publicClient.readContract({
43 | address: token as Hex,
44 | abi: parseAbi(erc20Abi),
45 | functionName: 'decimals',
46 | args: []
47 | })
48 |
49 | const amount = parseUnits(amt, decimals as number);
50 |
51 | // clear the transaction batch
52 | await modularSdk.clearUserOpsFromBatch();
53 |
54 | let dest: Array = [];
55 | let data: Array = [];
56 | t.map((addr) => addr.trim()).forEach((addr) => {
57 | dest = [...dest, token];
58 | data = [
59 | ...data,
60 | encodeFunctionData({
61 | functionName: 'transfer',
62 | abi: parseAbi(erc20Abi),
63 | args: [getAddress(addr), amount],
64 | })
65 | ];
66 | });
67 | console.log(
68 | `Batch transferring ${amt} ${symbol} to ${dest.length} recipients...`
69 | );
70 |
71 | for (let i = 0; i < dest.length; i++) {
72 | await modularSdk.addUserOpsToBatch({ to: dest[i], data: data[i] })
73 | }
74 |
75 | const op = await modularSdk.estimate();
76 | console.log(`Estimated UserOp: ${await printOp(op)}`);
77 |
78 | // sign the userop and sending to the bundler...
79 | const uoHash = await modularSdk.send(op);
80 | console.log(`UserOpHash: ${uoHash}`);
81 |
82 | // get transaction hash...
83 | console.log('Waiting for transaction...');
84 | let userOpsReceipt = null;
85 | const timeout = Date.now() + 60000; // 1 minute timeout
86 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
87 | await sleep(2);
88 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
89 | }
90 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
91 | }
92 |
--------------------------------------------------------------------------------
/examples/basics/add-guardians.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { encodeFunctionData, parseAbi } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 |
7 | dotenv.config();
8 |
9 | // tsx examples/basics/add-guardians.ts
10 | async function main() {
11 | const bundlerApiKey = 'etherspot_public_key';
12 |
13 | // initializating sdk...
14 | const modularSdk = generateModularSDKInstance(
15 | process.env.WALLET_PRIVATE_KEY as string,
16 | Number(process.env.CHAIN_ID), bundlerApiKey);
17 |
18 | // get address of EtherspotWallet
19 | const address: string = await modularSdk.getCounterFactualAddress();
20 |
21 | // update the addresses in this array with the guardian addresses you want to set
22 | const guardianAddresses: string[] = [
23 | '0xa8430797A27A652C03C46D5939a8e7698491BEd6',
24 | '0xaf2D76acc5B0e496f924B08491444076219F2f35',
25 | '0xBF1c0A9F3239f5e7D35cE562Af06c92FB7fdF0DF',
26 | ];
27 |
28 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
29 |
30 | const addGuardianInterface = ['function addGuardian(address _newGuardian)'];
31 |
32 | const addGuardianData1 =
33 | encodeFunctionData({
34 | functionName: 'addGuardian',
35 | abi: parseAbi(addGuardianInterface),
36 | args: [guardianAddresses[0]],
37 | });
38 | const addGuardianData2 =
39 | encodeFunctionData({
40 | functionName: 'addGuardian',
41 | abi: parseAbi(addGuardianInterface),
42 | args: [guardianAddresses[1]],
43 | });
44 | const addGuardianData3 = encodeFunctionData({
45 | functionName: 'addGuardian',
46 | abi: parseAbi(addGuardianInterface),
47 | args: [guardianAddresses[2]],
48 | });
49 | // clear the transaction batch
50 | await modularSdk.clearUserOpsFromBatch();
51 |
52 | // add transactions to the batch
53 | let userOpsBatch = await modularSdk.addUserOpsToBatch({ to: address, data: addGuardianData1 });
54 | userOpsBatch = await modularSdk.addUserOpsToBatch({ to: address, data: addGuardianData2 });
55 | userOpsBatch = await modularSdk.addUserOpsToBatch({ to: address, data: addGuardianData3 });
56 | console.log('transactions: ', userOpsBatch);
57 |
58 | // sign transactions added to the batch
59 | const op = await modularSdk.estimate();
60 | console.log(`Estimated UserOp: ${await printOp(op)}`);
61 |
62 | // sign the userOps and sending to the bundler...
63 | const uoHash = await modularSdk.send(op);
64 | console.log(`UserOpHash: ${uoHash}`);
65 |
66 | // get transaction hash...
67 | console.log('Waiting for transaction...');
68 | let userOpsReceipt = null;
69 | const timeout = Date.now() + 60000; // 1 minute timeout
70 | while (userOpsReceipt == null && Date.now() < timeout) {
71 | await sleep(2);
72 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
73 | }
74 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
75 | }
76 |
77 | main()
78 | .catch(console.error)
79 | .finally(() => process.exit());
80 |
--------------------------------------------------------------------------------
/examples/pulse/install-pulse-modules.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { MODULE_TYPE, sleep } from '../../src/sdk/common';
4 | import { encodeAbiParameters, encodeFunctionData, Hex, parseAbi } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 | import { getHookMultiPlexerInitData } from './utils';
7 | import { accountAbi } from '../../src/sdk/common/abis';
8 |
9 | dotenv.config();
10 |
11 | const bundlerApiKey = 'etherspot_public_key';
12 | const credibleAccountModule = "0xf47600D8dFef04269206255E53c8926519BA09a9";
13 | const resourceLockValidator = "0x";
14 | const hookMultiplexer = "0x2dbad2872b6aabd4dd3cd1eef7a46a241baa6cae";
15 |
16 | // tsx examples/paymaster/paymaster.ts
17 | async function main() {
18 | // initializating sdk...
19 | const modularSdk = generateModularSDKInstance(
20 | process.env.WALLET_PRIVATE_KEY,
21 | Number(process.env.CHAIN_ID),
22 | bundlerApiKey
23 | );// Testnets dont need apiKey on bundlerProvider
24 |
25 | // get address of EtherspotWallet...
26 | const address: Hex = (await modularSdk.getCounterFactualAddress()) as Hex;
27 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
28 |
29 | // get balance of the account address
30 | const balance = await modularSdk.getNativeBalance();
31 |
32 | console.log('balances: ', balance);
33 |
34 | // clear the transaction batch
35 | await modularSdk.clearUserOpsFromBatch();
36 |
37 | // install hook multiplexer with credible account module as subhook
38 | let initData = getHookMultiPlexerInitData([credibleAccountModule]);
39 | const hookMultiplexerModuleInstallCalldata = encodeFunctionData({
40 | abi: parseAbi(accountAbi),
41 | functionName: 'installModule',
42 | args: [BigInt(MODULE_TYPE.HOOK), hookMultiplexer, initData]
43 | });
44 | await modularSdk.addUserOpsToBatch({to: address, data: hookMultiplexerModuleInstallCalldata});
45 |
46 | // install creidble account module as validator
47 | initData = encodeAbiParameters([{ type: 'uint256' }], [BigInt(1)]);
48 | const credibleAccountModuleInstallCalldata = encodeFunctionData({
49 | abi: parseAbi(accountAbi),
50 | functionName: 'installModule',
51 | args: [BigInt(MODULE_TYPE.VALIDATOR), credibleAccountModule, initData]
52 | });
53 | await modularSdk.addUserOpsToBatch({to: address, data: credibleAccountModuleInstallCalldata});
54 |
55 | // install resource lock validator
56 |
57 | const op = await modularSdk.estimate();
58 | console.log(`Estimate UserOp: ${await printOp(op)}`);
59 | const uoHash = await modularSdk.send(op);
60 | console.log(`UserOpHash: ${uoHash}`);
61 |
62 | // get transaction hash...
63 | console.log('Waiting for transaction...');
64 | let userOpsReceipt = null;
65 | const timeout = Date.now() + 1200000; // 1 minute timeout
66 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
67 | await sleep(2);
68 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
69 | }
70 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
71 | }
72 |
73 | main()
74 | .catch(console.error)
75 | .finally(() => process.exit());
--------------------------------------------------------------------------------
/src/sdk/wallet/providers/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { Address, Hash, Hex, TransactionRequest, TypedDataDomain, WalletClient } from 'viem';
2 | import { UniversalProvider } from '@walletconnect/universal-provider';
3 | import { UniqueSubject } from '../../common/index.js';
4 | import { NetworkNames } from '../../network/index.js';
5 |
6 | export interface WalletProvider {
7 | readonly type?: string;
8 | readonly wallet?: WalletClient;
9 | readonly address: string;
10 | readonly address$?: UniqueSubject;
11 | readonly networkName?: NetworkNames;
12 | readonly networkName$?: UniqueSubject;
13 |
14 | signTypedData(msg: MessagePayload, validatorAddress?: Address, factoryAddress?: Address, initCode?: Hex): Promise;
15 |
16 | signMessage(message: string, validatorAddress?: Address, factoryAddress?: Address, initCode?: Hex): Promise;
17 |
18 | signUserOp(message: Hex): Promise;
19 |
20 | eth_requestAccounts(address?: string): Promise;
21 |
22 | eth_accounts(address?: string): Promise;
23 |
24 | eth_sendTransaction(transaction: TransactionRequest): Promise;
25 |
26 | eth_signTransaction(transaction: TransactionRequest): Promise;
27 | }
28 |
29 | export interface Web3Provider {
30 | send(payload: any, callback: (err: any, response?: any) => any): any;
31 | }
32 |
33 | // https://eips.ethereum.org/EIPS/eip-712#parameters
34 | export type MessagePayload = {
35 | domain: TypedDataDomain;
36 | types: Record;
37 | primaryType: string;
38 | message: any;
39 | };
40 |
41 | // https://eips.ethereum.org/EIPS/eip-712#definition-of-typed-structured-data-%F0%9D%95%8A
42 | type TypedProperty = {
43 | name: string;
44 | type: string;
45 | };
46 |
47 |
48 | export interface RequestArguments {
49 | method: string;
50 | params?: unknown[] | object;
51 | }
52 | export interface Web3eip1193Provider {
53 | request(args: RequestArguments): any;
54 | }
55 |
56 | export interface WalletConnectConnector {
57 | accounts: string[];
58 | chainId: number;
59 | signPersonalMessage(params: any[]): Promise;
60 | request(args: RequestArguments): Promise;
61 | on(event: string, callback: (error: Error | null, payload: any | null) => void): void;
62 | }
63 |
64 | export interface WalletLike {
65 | privateKey: string;
66 | }
67 |
68 | export declare class EthereumProvider {
69 | accounts: string[];
70 | signer: InstanceType;
71 | chainId: number;
72 | request(args: RequestArguments): Promise;
73 | sendAsync(args: RequestArguments, callback: (error: Error | null, response: any) => void): void;
74 | disconnect(): Promise;
75 | on(event: string, callback: (error: Error | null, payload: any | null) => void): void;
76 | once(event: string, callback: (error: Error | null, payload: any | null) => void): void;
77 | removeListener(event: string, callback: (error: Error | null, payload: any | null) => void): void;
78 | off(event: string, callback: (error: Error | null, payload: any | null) => void): void;
79 | readonly isWalletConnect?: boolean;
80 | }
81 |
82 | export type WalletProviderLike = string | WalletLike | WalletProvider | EthereumProvider | WalletClient;
83 |
--------------------------------------------------------------------------------
/examples/basics/transfer-erc20.ts:
--------------------------------------------------------------------------------
1 | import { EtherspotBundler, ModularSdk } from '../../src';
2 | import { printOp } from '../../src/sdk/common/OperationUtils';
3 | import { ERC20_ABI } from '../../src/sdk/helpers/abi/ERC20_ABI';
4 | import * as dotenv from 'dotenv';
5 | import { sleep } from '../../src/sdk/common';
6 | import { getPublicClient, getViemAccount } from '../../src/sdk/common/utils/viem-utils';
7 | import { encodeFunctionData, Hex, http, parseAbi, parseUnits } from 'viem';
8 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
9 | import { erc20Abi } from '../../src/sdk/common/abis';
10 |
11 | dotenv.config();
12 |
13 | // add/change these values
14 | const recipient = '0x80a1874E1046B1cc5deFdf4D3153838B72fF94Ac'; // recipient wallet address
15 | const value = '0.1'; // transfer value
16 | const tokenAddress = '0xDeDf3B40f8c44b1fF195F37F35c6b8199C7ee443'; // token address
17 | const bundlerApiKey = 'etherspot_public_key';
18 |
19 | // tsx examples/basics/transfer-erc20.ts
20 | async function main() {
21 | // initializating sdk...
22 | const bundlerProvider = new EtherspotBundler(Number(process.env.CHAIN_ID), bundlerApiKey);
23 | const modularSdk = generateModularSDKInstance(
24 | process.env.WALLET_PRIVATE_KEY as string,
25 | Number(process.env.CHAIN_ID), bundlerApiKey);
26 |
27 | // get address of EtherspotWallet...
28 | const address: string = await modularSdk.getCounterFactualAddress();
29 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
30 |
31 | const publicClient = getPublicClient({
32 | chainId: Number(process.env.CHAIN_ID),
33 | transport: http(bundlerProvider.url)
34 | });
35 |
36 | // get decimals from erc20 contract
37 |
38 | const decimals = await publicClient.readContract({
39 | address: tokenAddress as Hex,
40 | abi: parseAbi(erc20Abi),
41 | functionName: 'decimals',
42 | args: []
43 | })
44 |
45 | // get transferFrom encoded data
46 | const transactionData = encodeFunctionData({
47 | functionName: 'transfer',
48 | abi: parseAbi(ERC20_ABI),
49 | args: [recipient, parseUnits(value, decimals as number)]
50 | });
51 |
52 | // clear the transaction batch
53 | await modularSdk.clearUserOpsFromBatch();
54 |
55 | // add transactions to the batch
56 | const userOpsBatch = await modularSdk.addUserOpsToBatch({to: tokenAddress, data: transactionData});
57 | console.log('transactions: ', userOpsBatch);
58 |
59 | // estimate transactions added to the batch and get the fee data for the UserOp
60 | const op = await modularSdk.estimate();
61 | console.log(`Estimate UserOp: ${await printOp(op)}`);
62 |
63 | // sign the UserOp and sending to the bundler...
64 | const uoHash = await modularSdk.send(op);
65 | console.log(`UserOpHash: ${uoHash}`);
66 |
67 | // get transaction hash...
68 | console.log('Waiting for transaction...');
69 | let userOpsReceipt = null;
70 | const timeout = Date.now() + 60000; // 1 minute timeout
71 | while((userOpsReceipt == null) && (Date.now() < timeout)) {
72 | await sleep(2);
73 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
74 | }
75 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
76 | }
77 |
78 | main()
79 | .catch(console.error)
80 | .finally(() => process.exit());
81 |
--------------------------------------------------------------------------------
/.github-pages/theme/partials/parameter.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#if signatures}}
3 | -
4 |
5 | {{#each signatures}}
6 | - {{#compact}}
7 | {{> member.signature.title hideName=true }}
8 | {{/compact}}
9 | {{/each}}
10 |
11 |
12 |
13 | {{#each signatures}}
14 | - {{> member.signature.body hideSources=true }}
15 | {{/each}}
16 |
17 |
18 | {{/if}}
19 | {{#if indexSignature}}
20 | -
21 |
{{#compact}}
22 | [
23 | {{#each indexSignature.parameters}}
24 | {{#if flags.isRest}}...{{/if}}{{name}}: {{#with type}}{{>type}}{{/with}}
25 | {{/each}}
26 | ]:
27 | {{#with indexSignature.type}}{{>type}}{{/with}}
28 | {{/compact}}
29 |
30 | {{#with indexSignature}}
31 | {{> comment}}
32 | {{/with}}
33 |
34 | {{#if indexSignature.type.declaration}}
35 | {{#with indexSignature.type.declaration}}
36 | {{> parameter}}
37 | {{/with}}
38 | {{/if}}
39 |
40 | {{/if}}
41 | {{#each children}}
42 | -
43 | {{#if signatures}}
44 |
{{#compact}}
45 | {{#if flags.isRest}}...{{/if}}
46 | {{{wbr name}}}
47 |
48 | {{#if isOptional}}?{{/if}}
49 | :
50 |
51 | function
52 | {{/compact}}
53 |
54 | {{> member.signatures}}
55 | {{else}}
56 | {{#compact}}
57 | {{#each flags}}
58 | {{this}}
59 | {{/each}}
60 | {{#if flags.isRest}}...{{/if}}
61 | {{{wbr name}}}
62 |
63 | {{#if flags.isOptional}}?{{/if}}
64 | :
65 |
66 | {{#with type}}{{>type}}{{/with}}
67 | {{/compact}}
68 |
69 | {{> comment}}
70 |
71 | {{#if children}}
72 | {{> parameter}}
73 | {{/if}}
74 |
75 | {{#if type.declaration}}
76 | {{#with type.declaration}}
77 | {{> parameter}}
78 | {{/with}}
79 | {{/if}}
80 | {{/if}}
81 |
82 | {{/each}}
83 |
84 |
--------------------------------------------------------------------------------
/src/sdk/wallet/providers/walletClient.provider.ts:
--------------------------------------------------------------------------------
1 | import { Address, concat, encodeAbiParameters, Hash, hashMessage, hashTypedData, Hex, parseAbiParameters, toBytes, TransactionRequest, WalletClient } from 'viem';
2 | import { MessagePayload, WalletProvider } from './interfaces.js';
3 |
4 | export class WalletClientProvider implements WalletProvider {
5 | readonly type = 'WalletClient';
6 | readonly address: string;
7 | readonly accountAddress: string;
8 |
9 | readonly wallet: WalletClient;
10 |
11 | constructor(walletClient: WalletClient) {
12 | this.wallet = walletClient
13 |
14 | const { address } = this.wallet.account;
15 |
16 | this.address = address;
17 | }
18 |
19 | async signMessage(message: Hex, validatorAddress?: Address, factoryAddress?: Address, initCode?: Hex): Promise {
20 | const msg = toBytes(hashMessage({raw: toBytes(message)}));
21 | const signature: Hex = await this.wallet.signMessage({
22 | message: {raw: msg},
23 | account: this.wallet.account
24 | })
25 | if (initCode !== '0x') {
26 | const abiCoderResult = encodeAbiParameters(
27 | parseAbiParameters('address, bytes, bytes'),
28 | [factoryAddress, initCode, concat([validatorAddress, signature])]
29 | )
30 | return abiCoderResult + '6492649264926492649264926492649264926492649264926492649264926492'; //magicBytes
31 | }
32 | return concat([
33 | validatorAddress,
34 | signature
35 | ]);
36 | }
37 |
38 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
39 | async signTypedData(msg: MessagePayload, validatorAddress?: Address, factoryAddress?: Address, initCode?: Hex): Promise {
40 | const typedDataEncoder = hashTypedData({domain: msg.domain, types: msg.types, primaryType: msg.primaryType, message: msg.message});
41 | const signature = await this.wallet.signMessage({
42 | message: {raw: toBytes(typedDataEncoder)},
43 | account: this.wallet.account
44 | });
45 | if (initCode !== '0x') {
46 | const abiCoderResult = encodeAbiParameters(
47 | parseAbiParameters('address, bytes, bytes'),
48 | [factoryAddress, initCode, concat([validatorAddress, signature])]
49 | )
50 | return abiCoderResult + '6492649264926492649264926492649264926492649264926492649264926492'; //magicBytes
51 | }
52 | return concat([
53 | validatorAddress,
54 | signature]
55 | );
56 | }
57 |
58 | async eth_requestAccounts(address: string): Promise {
59 | return [address];
60 | }
61 |
62 | async eth_accounts(address: string): Promise {
63 | return [address];
64 | }
65 |
66 | async signUserOp(message: Hex): Promise {
67 | return this.wallet.signMessage({
68 | message: { raw: message },
69 | account: this.wallet.account
70 | });
71 | }
72 |
73 | async eth_sendTransaction(transaction: TransactionRequest): Promise {
74 | return this.wallet.sendTransaction({
75 | ...transaction,
76 | account: this.wallet.account,
77 | chain: this.wallet.chain,
78 | kzg: undefined
79 | });
80 | }
81 |
82 | async eth_signTransaction(transaction: TransactionRequest): Promise {
83 | return this.wallet.signTransaction({
84 | ...transaction,
85 | account: this.wallet.account,
86 | chain: this.wallet.chain,
87 | kzg: undefined
88 | });
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/examples/basics/custom-chain.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { defineChain, parseEther } from 'viem';
5 | import { EtherspotBundler, ModularSdk } from '../../src';
6 |
7 | dotenv.config();
8 |
9 | const recipient = '0x80a1874E1046B1cc5deFdf4D3153838B72fF94Ac'; // recipient wallet address
10 | const value = '0.0000001'; // transfer value
11 | const bundlerApiKey = 'etherspot_public_key';
12 | const bundlerUrl = 'https://testnet-rpc.etherspot.io/v2/84532'; // bundler url
13 | const chainId = 84532; // chain id
14 | const entryPointAddress = '0x0000000071727De22E5E9d8BAf0edAc6f37da032'; // entry point address
15 | const walletFactoryAddress = '0x38CC0EDdD3a944CA17981e0A19470d2298B8d43a'; // wallet factory address
16 | const bootstrapAddress = '0xCF2808eA7d131d96E5C73Eb0eCD8Dc84D33905C7'; // bootstrap address
17 | const multipleOwnerECDSAValidatorAddress = '0x0eA25BF9F313344d422B513e1af679484338518E'; // multi owner ECDSA validator factory address
18 |
19 | // tsx examples/basics/custom-chain.ts
20 | async function main() {
21 | // for custom chains, you can use the following code to create a chain object
22 | const chain = defineChain({
23 | id: chainId,
24 | name: 'Base sepolia Testnet',
25 | nativeCurrency: {
26 | decimals: 18,
27 | name: 'ETH',
28 | symbol: 'ETH',
29 | },
30 | rpcUrls: {
31 | default: {
32 | http: ['https://sepolia.base.org'], // RPC URL
33 | },
34 | },
35 | });
36 | // initializating sdk...
37 | const modularSdk = new ModularSdk(process.env.WALLET_PRIVATE_KEY as string, {
38 | chain: chain,
39 | chainId: chainId,
40 | bundlerProvider: new EtherspotBundler(chainId, bundlerApiKey, bundlerUrl),
41 | index: 0,
42 | entryPointAddress,
43 | walletFactoryAddress,
44 | bootstrapAddress,
45 | multipleOwnerECDSAValidatorAddress,
46 | rpcProviderUrl: bundlerUrl,
47 | });
48 |
49 | // get address of EtherspotWallet...
50 | const address: string = await modularSdk.getCounterFactualAddress();
51 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
52 |
53 | // clear the transaction batch
54 | await modularSdk.clearUserOpsFromBatch();
55 |
56 | // add transactions to the batch
57 | const transactionBatch = await modularSdk.addUserOpsToBatch({ to: recipient, value: parseEther(value) });
58 | console.log('transactions: ', transactionBatch);
59 |
60 | // get balance of the account address
61 | const balance = await modularSdk.getNativeBalance();
62 |
63 | console.log('balances: ', balance);
64 |
65 | // estimate transactions added to the batch and get the fee data for the UserOp
66 | const op = await modularSdk.estimate();
67 | console.log(`Estimate UserOp: ${await printOp(op)}`);
68 |
69 | // sign the UserOp and sending to the bundler...
70 | const uoHash = await modularSdk.send(op);
71 | console.log(`UserOpHash: ${uoHash}`);
72 |
73 | // get transaction hash...
74 | console.log('Waiting for transaction...');
75 | let userOpsReceipt: string | null = null;
76 | const timeout = Date.now() + 1200000; // 1 minute timeout
77 | while (userOpsReceipt == null && Date.now() < timeout) {
78 | await sleep(2);
79 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
80 | }
81 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
82 | }
83 |
84 | main()
85 | .catch(console.error)
86 | .finally(() => process.exit());
--------------------------------------------------------------------------------
/examples/basics/concurrent-userops.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { getPublicClient } from '../../src/sdk/common/utils/viem-utils';
5 | import { Hex, http, parseEther, PublicClient } from 'viem';
6 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
7 | import { BigNumber } from '../../src/sdk/types/bignumber';
8 |
9 | dotenv.config();
10 |
11 | const recipient = '0x80a1874E1046B1cc5deFdf4D3153838B72fF94Ac'; // recipient wallet address
12 | const value = '0.000001'; // transfer value
13 | const bundlerApiKey = 'etherspot_public_key';
14 |
15 | async function main() {
16 | // initializating sdk for index 0...
17 | const modularSdk = generateModularSDKInstance(
18 | process.env.WALLET_PRIVATE_KEY as string,
19 | Number(process.env.CHAIN_ID), bundlerApiKey);
20 |
21 | const publicClient = getPublicClient({
22 | chainId: Number(process.env.CHAIN_ID),
23 | transport: http(bundlerApiKey)
24 | }) as PublicClient;
25 |
26 | // get address of EtherspotWallet...
27 | const address: string = await modularSdk.getCounterFactualAddress();
28 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
29 |
30 | // get code
31 | const code = await publicClient.getCode({
32 | address: address as Hex
33 | });
34 |
35 | if (code.length <= 2) {
36 | console.log("Account must be created first");
37 | return;
38 | }
39 |
40 | // clear the transaction batch
41 | await modularSdk.clearUserOpsFromBatch();
42 |
43 | // add transactions to the batch
44 | const transactionBatch = await modularSdk.addUserOpsToBatch({to: recipient, value: parseEther(value)});
45 | console.log('transactions: ', transactionBatch);
46 |
47 | // get balance of the account address
48 | const balance = await modularSdk.getNativeBalance();
49 |
50 | console.log('balances: ', balance);
51 |
52 | // Note that usually Bundlers do not allow sending more than 10 concurrent userops from an unstaked entites (wallets, factories, paymaster)
53 | // Staked entities can send as many userops as they want
54 | let concurrentUseropsCount = 5;
55 | const userops = [];
56 | const uoHashes = [];
57 |
58 | while (--concurrentUseropsCount >= 0) {
59 | const op = await modularSdk.estimate({ key: BigNumber.from(concurrentUseropsCount) });
60 | console.log(`Estimate UserOp: ${await printOp(op)}`);
61 | userops.push(op);
62 | }
63 |
64 | console.log("Sending userops...");
65 | for (const op of userops) {
66 | const uoHash = await modularSdk.send(op);
67 | console.log(`UserOpHash: ${uoHash}`);
68 | uoHashes.push(uoHash);
69 | }
70 |
71 | console.log('Waiting for transactions...');
72 | const userOpsReceipts = new Array(uoHashes.length).fill(null);
73 | const timeout = Date.now() + 60000; // 1 minute timeout
74 | while((userOpsReceipts.some(receipt => receipt == null)) && (Date.now() < timeout)) {
75 | await sleep(2);
76 | for (let i = 0; i < uoHashes.length; ++i) {
77 | if (userOpsReceipts[i]) continue;
78 | const uoHash = uoHashes[i];
79 | userOpsReceipts[i] = await modularSdk.getUserOpReceipt(uoHash);
80 | }
81 | }
82 |
83 | if (userOpsReceipts.some(receipt => receipt != null)) {
84 | console.log('\x1b[33m%s\x1b[0m', `Transaction hashes: `);
85 | for (const uoReceipt of userOpsReceipts) {
86 | if (!uoReceipt) continue;
87 | console.log(uoReceipt.receipt.transactionHash);
88 | }
89 | } else {
90 | console.log("Could not submit any user op");
91 | }
92 | }
93 |
94 | main()
95 | .catch(console.error)
96 | .finally(() => process.exit());
97 |
--------------------------------------------------------------------------------
/src/sdk/common/abis.ts:
--------------------------------------------------------------------------------
1 |
2 | export const erc20Abi = [
3 | 'function approve(address spender, uint256 value) returns (bool)',
4 | 'function decimals() view returns (uint8)',
5 | 'function name() view returns (string)',
6 | 'function symbol() view returns (string)',
7 | 'function totalSupply() returns (uint256)',
8 | 'function balanceOf(address account) returns (uint256)',
9 | 'function allowance(address owner, address spender) returns (uint256)',
10 | 'function transfer(address to, uint256 value) returns (bool)',
11 | 'function transferFrom(address from, address to, uint256 value) returns (bool)'
12 | ];
13 |
14 | export const erc721Abi = [
15 | 'function balanceOf(address owner) view returns (uint256)',
16 | 'function ownerOf(uint256 tokenId) view returns (address)',
17 | 'function approve(address to, uint256 tokenId)',
18 | 'function getApproved(uint256 tokenId) view returns (address)',
19 | 'function setApprovalForAll(address operator, bool approved)',
20 | 'function isApprovedForAll(address owner, address operator) view returns (bool)',
21 | 'function transferFrom(address from, address to, uint256 tokenId)',
22 | 'function safeTransferFrom(address from, address to, uint256 tokenId)',
23 | 'function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)'
24 | ];
25 |
26 |
27 | export const factoryAbi = [
28 | 'function createAccount(bytes32 salt,bytes calldata initCode) returns (address)',
29 | 'function getAddress(bytes32 salt,bytes calldata initcode) view returns (address)'
30 | ]
31 |
32 | export const bootstrapAbi = [
33 | 'function singleInitMSA(address validator, bytes calldata data)',
34 | 'function initMSA(BootstrapConfig[] calldata $valdiators,BootstrapConfig[] calldata $executors,BootstrapConfig calldata _hook,BootstrapConfig[] calldata _fallbacks)',
35 | 'struct BootstrapConfig {address module;bytes data;}',
36 | ]
37 |
38 | export const modulesAbi = [
39 | 'function onInstall(bytes data)'
40 | ];
41 |
42 | export const entryPointAbi = [
43 | 'function getSenderAddress(bytes initCode) view returns (address)',
44 | 'function balanceOf(address account) view returns (uint256)',
45 | 'function getNonce(address sender, uint192 key) view returns (uint256)',
46 | 'function getDepositInfo(address account) view returns (uint256,bool,uint112,uint32,uint48)',
47 | 'event UserOperationEvent(bytes32 indexed userOpHash,address indexed sender,address indexed paymaster,uint256 nonce,bool success,uint256 actualGasCost,uint256 actualGasUsed)'
48 | ];
49 |
50 |
51 | export const accountAbi = [
52 | 'function execute(bytes32 mode,bytes executionCalldata)',
53 | 'function getActiveHook() external view returns (address hook)',
54 | 'function getValidatorPaginated(address cursor,uint256 size) returns (address[] memory, address)',
55 | 'function getExecutorsPaginated(address cursor,uint256 size) returns (address[] memory, address)',
56 | 'function installModule(uint256 moduleTypeId,address module,bytes calldata initData)',
57 | 'function uninstallModule(uint256 moduleTypeId,address module,bytes calldata deInitData)',
58 | 'function isModuleInstalled(uint256 moduleTypeId,address module,bytes calldata additionalContext) returns (bool)',
59 | 'function isOwner(address _address) returns (bool)'
60 | ];
61 |
62 | export const sessionKeyValidatorAbi = [
63 | 'function enableSessionKey(bytes calldata _sessionData)',
64 | 'function disableSessionKey(address _session)',
65 | 'function rotateSessionKey(address _oldSessionKey,bytes calldata _newSessionData)',
66 | 'function getAssociatedSessionKeys() returns (address[] memory keys)',
67 | 'function sessionData(address _sessionKey, address _account) returns (address token,bytes4 funcSelector,uint256 spendingLimit,uint48 validAfter,uint48 validUntil,bool live)'
68 | ];
69 |
70 |
--------------------------------------------------------------------------------
/examples/pulse/install-latest-pulse-modules.ts:
--------------------------------------------------------------------------------
1 | import { printOp } from '../../src/sdk/common/OperationUtils';
2 | import * as dotenv from 'dotenv';
3 | import { sleep } from '../../src/sdk/common';
4 | import { Hex } from 'viem';
5 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
6 | import { NETWORK_NAME_TO_CHAIN_ID, NetworkConfig, Networks, NetworkNames } from '../../src';
7 |
8 | dotenv.config();
9 |
10 | const bundlerApiKey = process.env.API_KEY || 'etherspot_public_key';
11 |
12 | // tsx examples/pulse/install-latest-pulse-modules.ts
13 | async function main() {
14 | const chainId: number = NETWORK_NAME_TO_CHAIN_ID[NetworkNames.Sepolia];
15 |
16 | // Init SDK
17 | const modularSdk = generateModularSDKInstance(process.env.WALLET_PRIVATE_KEY as string, chainId, bundlerApiKey);
18 |
19 | const networkConfig: NetworkConfig = Networks[chainId];
20 | const RESOURCE_LOCK_VALIDATOR_ADDRESS = '0x08B42e03c1beC06caa3811F503EBF2D58CaccE94' as Hex;
21 | const CREDIBLE_ACCOUNT_MODULE_ADDRESS = '0xc34D2E2D9Fa0aDbCd801F13563A1423858751A12' as Hex;
22 |
23 | // Get counterfactual of ModularEtherspotWallet...
24 | const address: Hex = (await modularSdk.getCounterFactualAddress()) as Hex;
25 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
26 |
27 | // Get native balance
28 | const balance = await modularSdk.getNativeBalance();
29 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet native balance: ${balance}`);
30 |
31 | /*//////////////////////////////////////////////////////////////
32 | PULSE ECOSYSTEM INSTALLATION
33 | //////////////////////////////////////////////////////////////*/
34 |
35 | console.log('\x1b[33m%s\x1b[0m', 'Starting Pulse ecosystem installation...');
36 |
37 | try {
38 | // Install the complete Pulse ecosystem using the new Pulse class
39 | const uoHash = await modularSdk.pulse.installPulseModules({
40 | credibleAccountModuleAddress: CREDIBLE_ACCOUNT_MODULE_ADDRESS,
41 | resourceLockValidatorAddress: RESOURCE_LOCK_VALIDATOR_ADDRESS,
42 | uninstallOldHookMultiplexer: false,
43 | });
44 |
45 | console.log(`PulseSetup UserOpHash: ${uoHash}`);
46 |
47 | // Await transaction hash
48 | console.log('Waiting for transaction...');
49 | let userOpsReceipt = null;
50 | const timeout = Date.now() + 300000; // 5 minute timeout
51 | while (userOpsReceipt == null && Date.now() < timeout) {
52 | await sleep(2);
53 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
54 | }
55 |
56 | if (userOpsReceipt) {
57 | console.log('\x1b[32m%s\x1b[0m', 'Pulse ecosystem installation completed successfully!');
58 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
59 |
60 | // Verify installation
61 | const installationStatus = await modularSdk.pulse.isPulseModulesInstalled({
62 | credibleAccountModuleAddress: CREDIBLE_ACCOUNT_MODULE_ADDRESS,
63 | resourceLockValidatorAddress: RESOURCE_LOCK_VALIDATOR_ADDRESS,
64 | });
65 |
66 | console.log('\x1b[33m%s\x1b[0m', 'Installation Status:', installationStatus);
67 |
68 | if (
69 | installationStatus.hookMultiPlexer &&
70 | installationStatus.credibleAccountValidator &&
71 | installationStatus.resourceLockValidator
72 | ) {
73 | console.log('\x1b[32m%s\x1b[0m', 'All Pulse modules installed successfully!');
74 | } else {
75 | console.log('\x1b[31m%s\x1b[0m', 'Some modules may not have been installed correctly');
76 | }
77 | } else {
78 | console.log('\x1b[31m%s\x1b[0m', 'Transaction timeout - please check transaction status manually');
79 | }
80 | } catch (error) {
81 | console.error('\x1b[31m%s\x1b[0m', 'Error during Pulse ecosystem installation:', error);
82 | throw error;
83 | }
84 | }
85 |
86 | main()
87 | .catch(console.error)
88 | .finally(() => process.exit());
89 |
--------------------------------------------------------------------------------
/examples/sessionkeys/transfer-erc20-session-key.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber, ethers } from 'ethers';
2 | import * as dotenv from 'dotenv';
3 | import { printOp } from 'src/sdk/common/OperationUtils';
4 | import { getViemAccount, sleep } from 'src/sdk/common';
5 | import { ERC20_ABI } from '../../src/sdk/helpers/abi/ERC20_ABI';
6 | import { ModularSdk, EtherspotBundler, SessionKeyValidator } from 'src';
7 | import { encodeFunctionData, parseAbi, parseUnits } from 'viem';
8 | import { generateModularSDKInstance } from '../helpers/sdk-helper';
9 |
10 | dotenv.config();
11 |
12 | // add/change these values
13 | const recipient = '0xdE79F0eF8A1268DAd0Df02a8e527819A3Cd99d40'; // recipient wallet address
14 | const value = '0.000001'; // transfer value
15 | const tokenAddress = process.env.TOKEN_ADDRESS as string; // token address
16 | const decimals = 18;
17 | const bundlerApiKey = 'etherspot_public_key';
18 | const erc20SessionKeyValidator = '0x22A55192a663591586241D42E603221eac49ed09';
19 |
20 | // tsx examples/sessionkeys/transfer-erc20-session-key.ts
21 | async function main() {
22 | // initializating sdk...
23 | const modularSdk = generateModularSDKInstance(
24 | process.env.WALLET_PRIVATE_KEY as string,
25 | Number(process.env.CHAIN_ID), bundlerApiKey);
26 |
27 | // get instance of SessionKeyValidator
28 | const sessionKeyModule = await SessionKeyValidator.create(modularSdk);
29 | console.log(`sessionKey SDK initialized`);
30 |
31 | // get address of EtherspotWallet...
32 | const address: string = await modularSdk.getCounterFactualAddress();
33 | console.log('\x1b[33m%s\x1b[0m', `EtherspotWallet address: ${address}`);
34 |
35 | // get transferFrom encoded data
36 | const transactionData = encodeFunctionData({
37 | functionName: 'transfer',
38 | abi: parseAbi(ERC20_ABI),
39 | args: [recipient, parseUnits(value, decimals as number)]
40 | });
41 |
42 | // clear the transaction batch
43 | await modularSdk.clearUserOpsFromBatch();
44 |
45 | // add transactions to the batch
46 | const userOpsBatch = await modularSdk.addUserOpsToBatch({ to: tokenAddress, data: transactionData });
47 | console.log('transactions: ', userOpsBatch);
48 |
49 | console.log(`erc20SessionKeyValidator ${erc20SessionKeyValidator} as BigNumber is: ${BigNumber.from(erc20SessionKeyValidator)}`);
50 |
51 | // estimate transactions added to the batch and get the fee data for the UserOp
52 | const op = await modularSdk.estimate({
53 | key: BigNumber.from(erc20SessionKeyValidator)
54 | });
55 |
56 | const nonceBig = BigNumber.from(op.nonce);
57 | console.log(`Nonce: ${nonceBig}`);
58 |
59 | console.log(`Estimate UserOp: ${await printOp(op)}`);
60 |
61 | // sign the UserOp using sessionKey
62 | const sessionKey = process.env.SESSION_KEY as string;
63 | console.log(`sessionKey: ${sessionKey}`);
64 |
65 | const signedUserOp = await sessionKeyModule.signUserOpWithSessionKey(sessionKey, op);
66 | console.log(`etherspot-modular-sdk -> Signed UserOp: ${signedUserOp.signature}`);
67 |
68 | console.log(`Signed UserOp: ${await printOp(signedUserOp)}`);
69 |
70 | console.log(`UserOpNonce is: ${BigNumber.from(signedUserOp.nonce)}`);
71 |
72 | const userOpHashFromSignedUserOp = await modularSdk.getUserOpHash(signedUserOp);
73 | console.log(`UserOpHash from Signed UserOp: ${userOpHashFromSignedUserOp}`);
74 |
75 | // sending to the bundler with isUserOpAlreadySigned true...
76 | const uoHash = await modularSdk.send(signedUserOp, true);
77 | console.log(`UserOpHash: ${uoHash}`);
78 |
79 | // get transaction hash...
80 | console.log('Waiting for transaction...');
81 | let userOpsReceipt = null;
82 | const timeout = Date.now() + 60000; // 1 minute timeout
83 | while ((userOpsReceipt == null) && (Date.now() < timeout)) {
84 | await sleep(2);
85 | userOpsReceipt = await modularSdk.getUserOpReceipt(uoHash);
86 | }
87 | console.log('\x1b[33m%s\x1b[0m', `Transaction Receipt: `, userOpsReceipt);
88 | }
89 |
90 | main()
91 | .catch(console.error)
92 | .finally(() => process.exit());
93 |
--------------------------------------------------------------------------------
14 | {{#each tags}} 15 |- {{tagName}}
16 | - {{#markdown}}{{{text}}}{{/markdown}}
17 | {{/each}}
18 |
19 | {{/if}} 20 |