├── .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 |
      4 | {{#if shortText}} 5 |
      6 | {{#markdown}}{{{shortText}}}{{/markdown}} 7 |
      8 | {{/if}} 9 | {{#if text}} 10 | {{#markdown}}{{{text}}}{{/markdown}} 11 | {{/if}} 12 | {{#if tags}} 13 |
      14 | {{#each tags}} 15 |
      {{tagName}}
      16 |
      {{#markdown}}{{{text}}}{{/markdown}}
      17 | {{/each}} 18 |
      19 | {{/if}} 20 |
      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 | 3 |
      4 |

      Legend

      5 |
      6 | {{#each legend}} 7 |
        8 | {{#each .}} 9 |
      • {{name}}
      • 10 | {{/each}} 11 |
      12 | {{/each}} 13 |
      14 |
      15 | 16 | 17 | {{#unless settings.hideGenerator}} 18 |
      19 |

      Generated using TypeDoc

      20 |
      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 | 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 |
      4 | {{> comment}} 5 |
      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 | >[![npm](https://img.shields.io/npm/v/@etherspot/modular-sdk)](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 | --------------------------------------------------------------------------------