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
123 changes: 95 additions & 28 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 _isInitialised!: boolean

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,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<void> {
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<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
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 })
tomarsachin2271 marked this conversation as resolved.
Show resolved Hide resolved
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<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 +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
Expand All @@ -143,12 +204,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 +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)
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'
6 changes: 5 additions & 1 deletion 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
initializeAccountAtIndex(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
15 changes: 8 additions & 7 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 @@ -30,19 +30,20 @@ export class Bundler implements IBundler {
* @returns Promise<UserOpGasPricesResponse>
*/
async estimateUserOpGas(userOp: UserOperation): Promise<UserOpGasResponse> {
// 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()

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