diff --git a/packages/account/src/BaseAccount.ts b/packages/account/src/BaseAccount.ts index 8248550b0..b127600fd 100644 --- a/packages/account/src/BaseAccount.ts +++ b/packages/account/src/BaseAccount.ts @@ -8,7 +8,7 @@ import { packUserOp } from '@biconomy/common' import { IBundler, UserOpResponse } from '@biconomy/bundler' import { IPaymaster } from '@biconomy/paymaster' -import { EntryPoint_v100, SmartAccount_v100 } from '@biconomy/common' +import { EntryPoint_v100, SmartAccount_v100, Logger } from '@biconomy/common' import { SmartAccountConfig, Overrides } from './utils/Types' type UserOperationKey = keyof UserOperation @@ -18,12 +18,21 @@ export abstract class SmartAccount implements ISmartAccount { paymaster!: IPaymaster initCode: string = '0x' proxy!: SmartAccount_v100 + owner!: string provider!: JsonRpcProvider entryPoint!: EntryPoint_v100 chainId!: ChainId signer!: Signer + smartAccountConfig: SmartAccountConfig - constructor(readonly smartAccountConfig: SmartAccountConfig) { } + constructor(_smartAccountConfig: SmartAccountConfig) { + this.smartAccountConfig = _smartAccountConfig + } + + + setEntryPointAddress(entryPointAddress: string){ + this.smartAccountConfig.entryPointAddress = entryPointAddress + } private validateUserOp(userOp: Partial, requiredFields: UserOperationKey[]): boolean { for (let field of requiredFields) { @@ -66,7 +75,7 @@ export abstract class SmartAccount implements ISmartAccount { userOp = { ...userOp, ...overrides } } - console.log('userOp in estimation', userOp); + Logger.log('userOp in estimation', userOp); // Defining the keys that are related that can be overrides const overrideGasFields: UserOperationKey[] = [ @@ -237,7 +246,7 @@ export abstract class SmartAccount implements ISmartAccount { */ async sendUserOp(userOp: Partial): Promise { let userOperation = await this.signUserOp(userOp) - const bundlerResponse = await this.sendUserOp(userOperation) + const bundlerResponse = await this.sendSignedUserOp(userOperation) return bundlerResponse } diff --git a/packages/account/src/BiconomySmartAccount.ts b/packages/account/src/BiconomySmartAccount.ts index 56c87152d..040d0a845 100644 --- a/packages/account/src/BiconomySmartAccount.ts +++ b/packages/account/src/BiconomySmartAccount.ts @@ -1,20 +1,22 @@ import { JsonRpcProvider } from '@ethersproject/providers' -import {ethers, BigNumberish, BytesLike, BigNumber } from 'ethers' +import { ethers, BigNumberish, BytesLike, BigNumber } from 'ethers' import { SmartAccount } from './BaseAccount' import { + Logger, NODE_CLIENT_URL, RPC_PROVIDER_URLS, - EntryPoint_v100__factory, SmartAccountFactory_v100, - SmartAccountFactory_v100__factory, - SmartAccount_v100__factory + getEntryPointContract, + getSAFactoryContract, + getSAProxyContract } from '@biconomy/common' import { BiconomySmartAccountConfig, Overrides } from './utils/Types' -import { UserOperation, Transaction } from '@biconomy/core-types' +import { UserOperation, Transaction, SmartAccountType } from '@biconomy/core-types' import NodeClient from '@biconomy/node-client' import INodeClient from '@biconomy/node-client' import { IBiconomySmartAccount } from 'interfaces/IBiconomySmartAccount' import { + ISmartAccount, SupportedChainsResponse, BalancesResponse, BalancesDto, @@ -23,13 +25,15 @@ import { SmartAccountsResponse, SCWTransactionResponse } from '@biconomy/node-client' -import { ENTRYPOINT_ADDRESSES, BICONOMY_FACTORY_ADDRESSES } from './utils/Constants' +import { ENTRYPOINT_ADDRESSES, BICONOMY_FACTORY_ADDRESSES, BICONOMY_IMPLEMENTATION_ADDRESSES, DEFAULT_ENTRYPOINT_ADDRESS } from './utils/Constants' export class BiconomySmartAccount extends SmartAccount implements IBiconomySmartAccount { - private factory: SmartAccountFactory_v100 + private factory!: SmartAccountFactory_v100 private nodeClient: INodeClient - private accountIndex!: Number + private accountIndex!: number private address!: string + private smartAccountInfo!: ISmartAccount + private _isInitialised!: boolean constructor(readonly biconomySmartAccountConfig: BiconomySmartAccountConfig) { const { @@ -43,28 +47,25 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart nodeClientUrl } = biconomySmartAccountConfig - const _entryPointAddress = entryPointAddress ?? ENTRYPOINT_ADDRESSES.default - const _factoryAddress = factoryAddress ?? BICONOMY_FACTORY_ADDRESSES.default + const _entryPointAddress = entryPointAddress ?? DEFAULT_ENTRYPOINT_ADDRESS super({ bundler, entryPointAddress: _entryPointAddress }) const _rpcUrl = rpcUrl ?? RPC_PROVIDER_URLS[chainId] - - if (!_rpcUrl){ + + if (!_rpcUrl) { throw new Error(`Chain Id ${chainId} is not supported. Please refer to the following link for supported chains list https://docs.biconomy.io/build-with-biconomy-sdk/gasless-transactions#supported-chains`) } this.provider = new JsonRpcProvider(_rpcUrl) - this.entryPoint = EntryPoint_v100__factory.connect(_entryPointAddress, this.provider) - this.factory = SmartAccountFactory_v100__factory.connect(_factoryAddress, this.provider) this.nodeClient = new NodeClient({ txServiceUrl: nodeClientUrl ?? NODE_CLIENT_URL }) this.signer = signer if (paymaster) { this.paymaster = paymaster } - if ( bundler ) - this.bundler = bundler + if (bundler) + this.bundler = bundler } /** * @description This function will initialise BiconomyAccount class state @@ -74,38 +75,97 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart try { this.isProviderDefined() this.isSignerDefined() - await this.setAccountIndex(accountIndex) + this.owner = await this.signer.getAddress() this.chainId = await this.provider.getNetwork().then((net) => net.chainId) + await this.initializeAccountAtIndex(accountIndex) + this._isInitialised = true } catch (error) { - console.error(`Failed to call init: ${error}`); + Logger.error(`Failed to call init: ${error}`); throw error } return this } - async setAccountIndex(accountIndex: number): Promise { + private isInitialized(): boolean{ + if (!this._isInitialised) + throw new Error('BiconomySmartAccount is not initialized. Please call init() on BiconomySmartAccount instance before interacting with any other function') + return true + } + + private setProxyContractState(){ + if ( !BICONOMY_IMPLEMENTATION_ADDRESSES[this.smartAccountInfo.implementationAddress] ) + throw new Error('Could not find attached implementation address against your smart account. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.') + const proxyInstanceDto = { + smartAccountType: SmartAccountType.BICONOMY, + version: BICONOMY_IMPLEMENTATION_ADDRESSES[this.address], + contractAddress: this.address, + provider: this.provider + } + this.proxy = getSAProxyContract(proxyInstanceDto) + } + + private setEntryPointContractState(){ + const _entryPointAddress = this.smartAccountInfo.entryPointAddress + this.setEntryPointAddress(_entryPointAddress) + if ( !ENTRYPOINT_ADDRESSES[_entryPointAddress] ) + throw new Error('Could not find attached entrypoint address against your smart account. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.') + const entryPointInstanceDto = { + smartAccountType: SmartAccountType.BICONOMY, + version: ENTRYPOINT_ADDRESSES[_entryPointAddress], + contractAddress: _entryPointAddress, + provider: this.provider + } + this.entryPoint = getEntryPointContract(entryPointInstanceDto) + } + + private setFactoryContractState(){ + const _factoryAddress = this.smartAccountInfo.factoryAddress + if ( !BICONOMY_FACTORY_ADDRESSES[_factoryAddress] ) + throw new Error('Could not find attached factory address against your smart account. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.') + const factoryInstanceDto = { + smartAccountType: SmartAccountType.BICONOMY, + version: BICONOMY_FACTORY_ADDRESSES[_factoryAddress], + contractAddress: _factoryAddress, + provider: this.provider + } + this.factory = getSAFactoryContract(factoryInstanceDto) + } + + private async setContractsState() { + this.setProxyContractState() + this.setEntryPointContractState() + this.setFactoryContractState() + } + + async initializeAccountAtIndex(accountIndex: number): Promise { this.accountIndex = accountIndex this.address = await this.getSmartAccountAddress(accountIndex) - this.proxy = await SmartAccount_v100__factory.connect(this.address, this.provider) + await this.setContractsState() + await this.setInitCode(this.accountIndex) } async getSmartAccountAddress(accountIndex: number = 0): Promise { try { this.isSignerDefined() - await this.getInitCode(accountIndex) - const address = await this.factory.getAddressForCounterFactualAccount( - await this.signer.getAddress(), - ethers.BigNumber.from(accountIndex) - ) - return address + let smartAccountsList: ISmartAccount[] = (await this.getSmartAccountsByOwner({ + chainId: this.chainId, + owner: this.owner + })).data + if ( !smartAccountsList ) + throw new Error('Failed to get smart account address. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.') + smartAccountsList = smartAccountsList.filter((smartAccount: ISmartAccount) => { return accountIndex === smartAccount.index }) + if (smartAccountsList.length === 0) + throw new Error('Failed to get smart account address. Please raise an issue on https://github.com/bcnmy/biconomy-client-sdk for further investigation.') + this.smartAccountInfo = smartAccountsList[0] + return this.smartAccountInfo.smartAccountAddress } catch (error) { - console.error(`Failed to get smart account address: ${error}`); + Logger.error(`Failed to get smart account address: ${error}`); throw error } } - async getInitCode(accountIndex: number = 0): Promise { + private async setInitCode(accountIndex: number = 0): Promise { this.initCode = ethers.utils.hexConcat([ this.factory.address, this.factory.interface.encodeFunctionData('deployCounterFactualAccount', [ @@ -131,6 +191,7 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart * @returns */ getExecuteCallData(to: string, value: BigNumberish, data: BytesLike): string { + this.isInitialized() this.isProxyDefined() const executeCallData = this.proxy.interface.encodeFunctionData('executeCall', [to, value, data]) return executeCallData @@ -143,12 +204,16 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart * @returns */ getExecuteBatchCallData(to: Array, value: Array, data: Array): string { + this.isInitialized() this.isProxyDefined() const executeBatchCallData = this.proxy.interface.encodeFunctionData('executeBatchCall', [to, value, data]) return executeBatchCallData } async buildUserOp(transactions: Transaction[], overrides?: Overrides): Promise> { + this.isInitialized() + // TODO: validate to, value and data fields + // TODO: validate overrides if supplied const to = transactions.map((element: Transaction) => element.to) const data = transactions.map((element: Transaction) => element.data ?? '0x') const value = transactions.map((element: Transaction) => element.value ?? BigNumber.from('0')) @@ -172,7 +237,9 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart initCode: nonce.eq(0) ? this.initCode : '0x', callData: callData } + userOp = await this.estimateUserOpGas(userOp, overrides) + Logger.log('userOp after estimation ', userOp) userOp.paymasterAndData = await this.getPaymasterAndData(userOp) return userOp } diff --git a/packages/account/src/index.ts b/packages/account/src/index.ts index 022cd41ec..a75e2469f 100644 --- a/packages/account/src/index.ts +++ b/packages/account/src/index.ts @@ -2,3 +2,4 @@ export * from './interfaces/IBaseAccount' export * from './utils/Types' export * from './BaseAccount' export * from './BiconomySmartAccount' +export * from './utils/Constants' diff --git a/packages/account/src/interfaces/IBiconomySmartAccount.ts b/packages/account/src/interfaces/IBiconomySmartAccount.ts index 2fce19646..ac177ce83 100644 --- a/packages/account/src/interfaces/IBiconomySmartAccount.ts +++ b/packages/account/src/interfaces/IBiconomySmartAccount.ts @@ -9,9 +9,13 @@ import { SCWTransactionResponse } from '@biconomy/node-client' import { Overrides } from '../utils/Types' +import { BigNumberish, BytesLike } from 'ethers' + export interface IBiconomySmartAccount { - setAccountIndex(accountIndex: number): void + initializeAccountAtIndex(accountIndex: number): void + getExecuteCallData(to: string, value: BigNumberish, data: BytesLike): string + getExecuteBatchCallData(to: Array, value: Array, data: Array): string buildUserOp(transactions: Transaction[], overrides?: Overrides): Promise> getAllTokenBalances(balancesDto: BalancesDto): Promise getTotalBalanceInUsd(balancesDto: BalancesDto): Promise diff --git a/packages/account/src/utils/Constants.ts b/packages/account/src/utils/Constants.ts index b4a3fb98f..542df05a2 100644 --- a/packages/account/src/utils/Constants.ts +++ b/packages/account/src/utils/Constants.ts @@ -1,15 +1,29 @@ import { ChainId } from '@biconomy/core-types' +import { EntrypointAddresses, BiconomyFactories, BiconomyImplementation } from './Types' -export const ENTRYPOINT_ADDRESSES = { - 'V0_0_5': '0x27a4Db290B89AE3373ce4313cBEaE72112Ae7Da9', - 'V0_0_6': '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', - default: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' + + // will always be latest entrypoint address +export const DEFAULT_ENTRYPOINT_ADDRESS = '0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789' +export const ENTRYPOINT_ADDRESSES: EntrypointAddresses = { + '0x27a4db290b89ae3373ce4313cbeae72112ae7da9': 'V0_0_5', + '0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789': 'V0_0_6' } -export const BICONOMY_FACTORY_ADDRESSES = { - 'V2_0_0': '0x000000F9eE1842Bb72F6BBDD75E6D3d4e3e9594C', - default: '0x000000F9eE1842Bb72F6BBDD75E6D3d4e3e9594C' + +// will always be latest factory address +export const DEFAULT_BICONOMY_FACTORY_ADDRESS = '0x000000f9ee1842bb72f6bbdd75e6d3d4e3e9594c' +export const BICONOMY_FACTORY_ADDRESSES: BiconomyFactories = { + '0x000000f9ee1842bb72f6bbdd75e6d3d4e3e9594c': 'V1_0_0' } + +export const BICONOMY_IMPLEMENTATION_ADDRESSES: BiconomyImplementation = { + '0x00006b7e42e01957da540dc6a8f7c30c4d816af5': 'V1_0_0' +} + +// will always be latest implementation address +export const DEFAULT_BICONOMY_IMPLEMENTATION_ADDRESS = '0x00006b7e42e01957da540dc6a8f7c30c4d816af5' + + export const EIP1559_UNSUPPORTED_NETWORKS: Array = [97, 56, 1442, 1101] diff --git a/packages/account/src/utils/Types.ts b/packages/account/src/utils/Types.ts index e735cfd37..d072de706 100644 --- a/packages/account/src/utils/Types.ts +++ b/packages/account/src/utils/Types.ts @@ -5,6 +5,18 @@ import { IBundler } from '@biconomy/bundler' import { IPaymaster } from '@biconomy/paymaster' +export type EntrypointAddresses = { + [address: string]: string +} + +export type BiconomyFactories = { + [address: string]: string +} + +export type BiconomyImplementation = { + [address: string]: string +} + export type SmartAccountConfig = { entryPointAddress: string bundler?: IBundler diff --git a/packages/bundler/src/Bundler.ts b/packages/bundler/src/Bundler.ts index da40f014c..d20be40d6 100644 --- a/packages/bundler/src/Bundler.ts +++ b/packages/bundler/src/Bundler.ts @@ -2,7 +2,7 @@ import { IBundler } from "./interfaces/IBundler"; import { UserOperation, ChainId } from '@biconomy/core-types' import { GetUserOperationResponse, GetUserOpByHashResponse, Bundlerconfig, UserOpResponse, EstimateUserOpGasResponse, UserOpReceipt, SendUserOpResponse, UserOpGasResponse, UserOpByHashResponse } from "./types/Types" import { resolveProperties } from 'ethers/lib/utils' -import { deepHexlify, getTimestampInSeconds, RPC_PROVIDER_URLS } from '@biconomy/common' +import { deepHexlify, getTimestampInSeconds, Logger, RPC_PROVIDER_URLS } from '@biconomy/common' import { HttpMethod, sendRequest } from './utils/httpRequests' import { transformUserOP } from './utils/HelperFunction' import { UserOpReceiptIntervals } from './utils/Constants' @@ -30,19 +30,20 @@ export class Bundler implements IBundler { * @returns Promise */ async estimateUserOpGas(userOp: UserOperation): Promise { - // TODO: will be removed once full userOp requirement is removed from bundler side + // bundler require these dummy values for estimation + // TODO: dapp/dev need to take dummy signature as well that we will pass to bundler. as signature depends on smart contract implementation const dummpyUserop = { - callGasLimit: '0', - verificationGasLimit: '0', - preVerificationGas: '0', + callGasLimit: '90000', + verificationGasLimit: '3000000', + preVerificationGas: '46856', maxFeePerGas: '0', maxPriorityFeePerGas: '0', paymasterAndData: '0x', - signature: '0x' + signature: '0x73c3ac716c487ca34bb858247b5ccf1dc354fbaabdd089af3b2ac8e78ba85a4959a2d76250325bd67c11771c31fccda87c33ceec17cc0de912690521bb95ffcb1b' } const userOperation = { ...dummpyUserop, ...userOp } userOp = transformUserOP(userOperation) - console.log('userOp sending for fee estimate ', userOp); + Logger.log('userOp sending for fee estimate ', userOp); const bundlerUrl = this.getBundlerUrl() diff --git a/packages/common/src/Constants.ts b/packages/common/src/Constants.ts index 2850ac173..a892701d7 100644 --- a/packages/common/src/Constants.ts +++ b/packages/common/src/Constants.ts @@ -14,4 +14,4 @@ export const RPC_PROVIDER_URLS: { [key in ChainId]?: string } = { [ChainId.ARBITRUM_GOERLI_TESTNET] : 'https://goerli-rollup.arbitrum.io/rpc', [ChainId.ARBITRUM_ONE_MAINNET] : 'https://rpc.ankr.com/arbitrum', [ChainId.ARBITRUM_NOVA_MAINNET] : 'https://nova.arbitrum.io/rpc' -}; \ No newline at end of file +} \ No newline at end of file diff --git a/packages/common/src/ContractsInstances.ts b/packages/common/src/ContractsInstances.ts new file mode 100644 index 000000000..8b2dd2730 --- /dev/null +++ b/packages/common/src/ContractsInstances.ts @@ -0,0 +1,61 @@ +import { SmartAccountType } from '@biconomy/core-types' +import { JsonRpcProvider } from '@ethersproject/providers' +import { + EntryPoint_v100, + EntryPoint_v100__factory, + SmartAccount_v100, SmartAccountFactory_v100, + SmartAccountFactory_v100__factory, + SmartAccount_v100__factory +} from './typechain' + +export type GetContractInstanceDto = { + smartAccountType: SmartAccountType, + version: string, + contractAddress: string, + provider: JsonRpcProvider +} + +export function getSAProxyContract(contractInstanceDto: GetContractInstanceDto): SmartAccount_v100 { + const { smartAccountType, version, contractAddress, provider } = contractInstanceDto + switch (version) { + case 'V1_0_0': + if (smartAccountType === SmartAccountType.BICONOMY) { + return SmartAccount_v100__factory.connect(contractAddress, provider); + } + break; + default: + return SmartAccount_v100__factory.connect(contractAddress, provider); + } + throw new Error('Invalid version or smartAccountType provided for proxy contract instance'); + +} + +export function getSAFactoryContract(contractInstanceDto: GetContractInstanceDto): SmartAccountFactory_v100 { + const { smartAccountType, version, contractAddress, provider } = contractInstanceDto + + switch (version) { + case 'V1_0_0': + if (smartAccountType === SmartAccountType.BICONOMY) { + return SmartAccountFactory_v100__factory.connect(contractAddress, provider) + } + break; + default: + return SmartAccountFactory_v100__factory.connect(contractAddress, provider) + } + throw new Error('Invalid version or smartAccountType provided for factory contract instance'); +} + +export function getEntryPointContract(contractInstanceDto: GetContractInstanceDto): EntryPoint_v100 { + const { smartAccountType, version, contractAddress, provider } = contractInstanceDto + + switch (version) { + case 'V0_0_5': + if (smartAccountType === SmartAccountType.BICONOMY) { + return EntryPoint_v100__factory.connect(contractAddress, provider) + } + break; + default: + return EntryPoint_v100__factory.connect(contractAddress, provider) + } + throw new Error('Invalid version or smartAccountType provided for entrypoint contract instance'); +} \ No newline at end of file diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index d21dda3ac..f74c05006 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -7,3 +7,4 @@ export * from './httpRequests' export * from './typechain/index' export * from './Utils' export * from './Constants' +export * from './ContractsInstances' diff --git a/packages/core-types/src/SmartAccountTypes.ts b/packages/core-types/src/SmartAccountTypes.ts index 2287d03c1..82e62b32f 100644 --- a/packages/core-types/src/SmartAccountTypes.ts +++ b/packages/core-types/src/SmartAccountTypes.ts @@ -140,3 +140,7 @@ export type TransactionBatchDto = { transactions: Transaction[] chainId?: ChainId } + +export enum SmartAccountType { + BICONOMY +} \ No newline at end of file