Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feat/versioning #188

Merged
merged 12 commits into from
Jun 17, 2023
17 changes: 13 additions & 4 deletions packages/account/src/BaseAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<UserOperation>, requiredFields: UserOperationKey[]): boolean {
for (let field of requiredFields) {
Expand Down Expand Up @@ -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[] = [
Expand Down Expand Up @@ -237,7 +246,7 @@ export abstract class SmartAccount implements ISmartAccount {
*/
async sendUserOp(userOp: Partial<UserOperation>): Promise<UserOpResponse> {
let userOperation = await this.signUserOp(userOp)
const bundlerResponse = await this.sendUserOp(userOperation)
const bundlerResponse = await this.sendSignedUserOp(userOperation)
return bundlerResponse
}

Expand Down
119 changes: 92 additions & 27 deletions packages/account/src/BiconomySmartAccount.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 isInited!: boolean
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename isInited to isInitialized

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so there is already a function with same name that i am using to verify either init is called or not. for this reason, i kept is variable name as isInited

  private isInitialized(): boolean{
    if (!this.isInited)
    throw new Error('BiconomySmartAccount is not initialized. Please call init() on BiconomySmartAccount before interacting with any other function')
    return true
  }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can just keep boolean called initialized


constructor(readonly biconomySmartAccountConfig: BiconomySmartAccountConfig) {
const {
Expand All @@ -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
Expand All @@ -74,38 +75,95 @@ 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.setAccountIndex(accountIndex)
this.isInited = true
} catch (error) {
console.error(`Failed to call init: ${error}`);
Logger.error(`Failed to call init: ${error}`);
throw error
}

return this
}

private isInitialized(): boolean{
if (!this.isInited)
throw new Error('BiconomySmartAccount is not initialized. Please call init() on BiconomySmartAccount before interacting with any other function')
tomarsachin2271 marked this conversation as resolved.
Show resolved Hide resolved
return true
}

private setProxyContractState(){
if ( !BICONOMY_IMPLEMENTATION_ADDRESSES[this.smartAccountInfo.implementationAddress] )
throw new Error('Could not find attach implementation address again your smart account. Please generate support ticket for further investegation.')
tomarsachin2271 marked this conversation as resolved.
Show resolved Hide resolved
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 attach entrypoint address again your smart account. Please generate support ticket for further investegation.')
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 attach factory address again your smart account. Please generate support ticket for further investegation.')
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 setAccountIndex(accountIndex: number): Promise<void> {
this.accountIndex = accountIndex
this.address = await this.getSmartAccountAddress(accountIndex)
this.proxy = await SmartAccount_v100__factory.connect(this.address, this.provider)
await this.setContractsState()
tomarsachin2271 marked this conversation as resolved.
Show resolved Hide resolved
await this.setInitCode(this.accountIndex)
}

async getSmartAccountAddress(accountIndex: number = 0): Promise<string> {
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
smartAccountsList = smartAccountsList.filter((smartAccount: ISmartAccount) => { return accountIndex === smartAccount.index })
tomarsachin2271 marked this conversation as resolved.
Show resolved Hide resolved
if (smartAccountsList.length === 0)
throw new Error('Failed to get smart account address')
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<string> {
private async setInitCode(accountIndex: number = 0): Promise<string> {
this.initCode = ethers.utils.hexConcat([
this.factory.address,
this.factory.interface.encodeFunctionData('deployCounterFactualAccount', [
Expand All @@ -131,6 +189,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
Expand All @@ -143,12 +202,16 @@ export class BiconomySmartAccount extends SmartAccount implements IBiconomySmart
* @returns
*/
getExecuteBatchCallData(to: Array<string>, value: Array<BigNumberish>, data: Array<BytesLike>): string {
this.isInitialized()
this.isProxyDefined()
const executeBatchCallData = this.proxy.interface.encodeFunctionData('executeBatchCall', [to, value, data])
return executeBatchCallData
}

async buildUserOp(transactions: Transaction[], overrides?: Overrides): Promise<Partial<UserOperation>> {
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'))
Expand All @@ -172,7 +235,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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These Logger.log across all files should be called based on a debug flag in class constructor.... SDK should not print any logs on its own unless developer has enabled it using debug flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so logger only logs if debug is enabled. this check is already there in log function

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From where is the Logger class getting the debug flag?

userOp.paymasterAndData = await this.getPaymasterAndData(userOp)
return userOp
}
Expand Down
1 change: 1 addition & 0 deletions packages/account/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './interfaces/IBaseAccount'
export * from './utils/Types'
export * from './BaseAccount'
export * from './BiconomySmartAccount'
export * from './utils/Constants'
4 changes: 4 additions & 0 deletions packages/account/src/interfaces/IBiconomySmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
getExecuteCallData(to: string, value: BigNumberish, data: BytesLike): string
getExecuteBatchCallData(to: Array<string>, value: Array<BigNumberish>, data: Array<BytesLike>): string
buildUserOp(transactions: Transaction[], overrides?: Overrides): Promise<Partial<UserOperation>>
getAllTokenBalances(balancesDto: BalancesDto): Promise<BalancesResponse>
getTotalBalanceInUsd(balancesDto: BalancesDto): Promise<UsdBalanceResponse>
Expand Down
28 changes: 21 additions & 7 deletions packages/account/src/utils/Constants.ts
Original file line number Diff line number Diff line change
@@ -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<ChainId> = [97, 56, 1442, 1101]
12 changes: 12 additions & 0 deletions packages/account/src/utils/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/bundler/src/Bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -42,7 +42,7 @@ export class Bundler implements IBundler {
}
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()

Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
};
}
Loading