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: gas offset param for sendTransaction and sendUserOp #474

Merged
merged 10 commits into from
May 9, 2024
9 changes: 2 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,7 @@
"default": "./_cjs/modules/index.js"
}
},
"files": [
"dist/*",
"README.md"
],
"files": ["dist/*", "README.md"],
"scripts": {
"format": "biome format . --write",
"lint": "biome check .",
Expand Down Expand Up @@ -105,9 +102,7 @@
"viem": "^2"
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
"extends": ["@commitlint/config-conventional"]
},
"simple-git-hooks": {
"pre-commit": "bun run format && bun run lint:fix",
Expand Down
158 changes: 157 additions & 1 deletion src/account/BiconomySmartAccountV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import type {
import {
addressEquals,
compareChainIds,
convertToFactor,
isNullOrUndefined,
isValidRpcUrl,
packUserOp
Expand Down Expand Up @@ -978,7 +979,7 @@ export class BiconomySmartAccountV2 extends BaseSmartContractAccount {
return this.getPaymasterAndData(partialUserOp, {
...paymasterServiceData,
feeTokenAddress: feeQuote.tokenAddress,
calculateGasLimits: true // Always recommended and especially when using token paymaster
calculateGasLimits: paymasterServiceData.calculateGasLimits ?? true // Always recommended and especially when using token paymaster
VGabriel45 marked this conversation as resolved.
Show resolved Hide resolved
})
}
if (paymasterServiceData?.preferredToken) {
Expand Down Expand Up @@ -1402,6 +1403,40 @@ export class BiconomySmartAccountV2 extends BaseSmartContractAccount {
* const { waitForTxHash } = await smartAccount.sendTransaction(transaction);
* const { transactionHash, userOperationReceipt } = await wait();
*
* @remarks
* This example shows how to increase the estimated gas values for a transaction using `gasOffset` parameter.
* @example
* import { createClient } from "viem"
* import { createSmartAccountClient } from "@biconomy/account"
* import { createWalletClient, http } from "viem";
* import { polygonAmoy } from "viem/chains";
*
* const signer = createWalletClient({
* account,
* chain: polygonAmoy,
* transport: http(),
* });
*
* const smartAccount = await createSmartAccountClient({ signer, bundlerUrl }); // Retrieve bundler url from dashboard
* const encodedCall = encodeFunctionData({
* abi: parseAbi(["function safeMint(address to) public"]),
* functionName: "safeMint",
* args: ["0x..."],
* });
*
* const transaction = {
* to: nftAddress,
* data: encodedCall
* }
*
* const { waitForTxHash } = await smartAccount.sendTransaction(transaction, {
* gasOffset: {
* verificationGasLimitOffsetPct: 25, // 25% increase for the already estimated gas limit
* preVerificationGasOffsetPct: 10 // 10% increase for the already estimated gas limit
* }
* });
* const { transactionHash, userOperationReceipt } = await wait();
*
*/
async sendTransaction(
manyOrOneTransactions: Transaction | Transaction[],
Expand Down Expand Up @@ -1513,14 +1548,135 @@ export class BiconomySmartAccountV2 extends BaseSmartContractAccount {
userOp.maxFeePerGas = gasFeeValues?.maxFeePerGas as Hex
userOp.maxPriorityFeePerGas = gasFeeValues?.maxPriorityFeePerGas as Hex

if (buildUseropDto.gasOffset) {
userOp = await this.estimateUserOpGas(userOp)

const {
verificationGasLimitOffsetPct,
preVerificationGasOffsetPct,
callGasLimitOffsetPct,
maxFeePerGasOffsetPct,
maxPriorityFeePerGasOffsetPct
} = buildUseropDto.gasOffset
userOp.verificationGasLimit = toHex(
Number.parseInt(
(
Number(userOp.verificationGasLimit ?? 0) *
convertToFactor(verificationGasLimitOffsetPct)
).toString()
)
)
userOp.preVerificationGas = toHex(
Number.parseInt(
(
Number(userOp.preVerificationGas ?? 0) *
convertToFactor(preVerificationGasOffsetPct)
).toString()
)
)
userOp.callGasLimit = toHex(
Number.parseInt(
(
Number(userOp.callGasLimit ?? 0) *
convertToFactor(callGasLimitOffsetPct)
).toString()
)
)
userOp.maxFeePerGas = toHex(
Number.parseInt(
(
Number(userOp.maxFeePerGas ?? 0) *
convertToFactor(maxFeePerGasOffsetPct)
).toString()
)
)
userOp.maxPriorityFeePerGas = toHex(
Number.parseInt(
(
Number(userOp.maxPriorityFeePerGas ?? 0) *
convertToFactor(maxPriorityFeePerGasOffsetPct)
).toString()
)
)

userOp = await this.getPaymasterUserOp(userOp, {
...buildUseropDto.paymasterServiceData,
calculateGasLimits: false
})
return userOp
}
if (buildUseropDto.paymasterServiceData.calculateGasLimits === false) {
userOp = await this.estimateUserOpGas(userOp)
}

userOp = await this.getPaymasterUserOp(
userOp,
buildUseropDto.paymasterServiceData
)

return userOp
}

userOp = await this.estimateUserOpGas(userOp)

if (buildUseropDto?.gasOffset) {
if (buildUseropDto?.paymasterServiceData) {
userOp = await this.getPaymasterUserOp(userOp, {
...buildUseropDto.paymasterServiceData,
calculateGasLimits: false
})
}

const {
verificationGasLimitOffsetPct,
preVerificationGasOffsetPct,
callGasLimitOffsetPct,
maxFeePerGasOffsetPct,
maxPriorityFeePerGasOffsetPct
} = buildUseropDto.gasOffset
userOp.verificationGasLimit = toHex(
Number.parseInt(
(
Number(userOp.verificationGasLimit ?? 0) *
convertToFactor(verificationGasLimitOffsetPct)
).toString()
)
)
userOp.preVerificationGas = toHex(
Number.parseInt(
(
Number(userOp.preVerificationGas ?? 0) *
convertToFactor(preVerificationGasOffsetPct)
).toString()
)
)
userOp.callGasLimit = toHex(
Number.parseInt(
(
Number(userOp.callGasLimit ?? 0) *
convertToFactor(callGasLimitOffsetPct)
).toString()
)
)
userOp.maxFeePerGas = toHex(
Number.parseInt(
(
Number(userOp.maxFeePerGas ?? 0) *
convertToFactor(maxFeePerGasOffsetPct)
).toString()
)
)
userOp.maxPriorityFeePerGas = toHex(
Number.parseInt(
(
Number(userOp.maxPriorityFeePerGas ?? 0) *
convertToFactor(maxPriorityFeePerGasOffsetPct)
).toString()
)
)

return userOp
}
if (buildUseropDto?.paymasterServiceData) {
userOp = await this.getPaymasterUserOp(
userOp,
Expand Down
67 changes: 38 additions & 29 deletions src/account/utils/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,26 +178,27 @@ export type BiconomySmartAccountV2ConfigConstructorProps =
ResolvedBundlerProps &
ResolvedValidationProps

/**
* Represents options for building a user operation.
* @typedef BuildUserOpOptions
* @property {GasOffset} [gasOffset] - Increment gas values by giving an offset, the given value will be an increment to the current estimated gas values, not an override.
* @property {ModuleInfo} [params] - Parameters relevant to the module, mostly relevant to sessions.
* @property {NonceOptions} [nonceOptions] - Options for overriding the nonce.
* @property {boolean} [forceEncodeForBatch] - Whether to encode the user operation for batch.
* @property {PaymasterUserOperationDto} [paymasterServiceData] - Options specific to transactions that involve a paymaster.
* @property {SimulationType} [simulationType] - Determine which parts of the transaction a bundler will simulate: "validation" | "validation_and_execution".
* @property {StateOverrideSet} [stateOverrideSet] - For overriding the state.
* @property {boolean} [useEmptyDeployCallData] - Set to true if the transaction is being used only to deploy the smart contract, so "0x" is set as the user operation call data.
*/
export type BuildUserOpOptions = {
/** overrides: Explicitly set gas values */
// overrides?: Overrides;
/** Not currently in use */
// skipBundlerGasEstimation?: boolean;
/** params relevant to the module, mostly relevant to sessions */
gasOffset?: GasOffsetPct
params?: ModuleInfo
/** nonceOptions: For overriding the nonce */
nonceOptions?: NonceOptions
/** forceEncodeForBatch: For encoding the user operation for batch */
forceEncodeForBatch?: boolean
/** paymasterServiceData: Options specific to transactions that involve a paymaster */
paymasterServiceData?: PaymasterUserOperationDto
/** simulationType: Determine which parts of the tx a bundler will simulate: "validation" | "validation_and_execution". */
simulationType?: SimulationType
/** dummy pnd override */
dummyPndOverride?: BytesLike
/** stateOverrideSet: For overriding the state */
stateOverrideSet?: StateOverrideSet
/** set to true if the tx is being used *only* to deploy the smartContract, so "0x" is set as the userOp.callData */
dummyPndOverride?: BytesLike
useEmptyDeployCallData?: boolean
}

Expand All @@ -210,21 +211,30 @@ export type NonceOptions = {

export type SimulationType = "validation" | "validation_and_execution"

export type Overrides = {
/* Value used by inner account execution */
callGasLimit?: Hex
/* Actual gas used by the validation of this UserOperation */
verificationGasLimit?: Hex
/* Gas overhead of this UserOperation */
preVerificationGas?: Hex
/* Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) */
maxFeePerGas?: Hex
/* Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) */
maxPriorityFeePerGas?: Hex
/* Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster ("0x" for self-sponsored transaction) */
paymasterData?: Hex
/* Data passed into the account along with the nonce during the verification step */
signature?: Hex
/**
* Represents an offset percentage value used for gas-related calculations.
* @remarks
* This type defines offset percentages for various gas-related parameters. Each percentage represents a proportion of the current estimated gas values.
* For example:
* - A value of `1` represents a 1% offset.
* - A value of `100` represents a 100% offset.
* @public
*/
/**
* Represents an object containing offset percentages for gas-related parameters.
* @typedef GasOffsetPct
* @property {number} [callGasLimitOffsetPct] - Percentage offset for the gas limit used by inner account execution.
* @property {number} [verificationGasLimitOffsetPct] - Percentage offset for the actual gas used by the validation of a UserOperation.
* @property {number} [preVerificationGasOffsetPct] - Percentage offset representing the gas overhead of a UserOperation.
* @property {number} [maxFeePerGasOffsetPct] - Percentage offset for the maximum fee per gas (similar to EIP-1559 max_fee_per_gas).
* @property {number} [maxPriorityFeePerGasOffsetPct] - Percentage offset for the maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas).
*/
export type GasOffsetPct = {
callGasLimitOffsetPct?: number
verificationGasLimitOffsetPct?: number
preVerificationGasOffsetPct?: number
maxFeePerGasOffsetPct?: number
maxPriorityFeePerGasOffsetPct?: number
}

export type InitilizationData = {
Expand Down Expand Up @@ -263,7 +273,6 @@ export type InitializeV2Data = {

export type EstimateUserOpGasParams = {
userOp: Partial<UserOperationStruct>
// overrides?: Overrides;
/** Currrently has no effect */
// skipBundlerGasEstimation?: boolean;
/** paymasterServiceData: Options specific to transactions that involve a paymaster */
Expand Down
19 changes: 19 additions & 0 deletions src/account/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,22 @@ export const wrapSignatureWith6492 = ({
"0x6492649264926492649264926492649264926492649264926492649264926492"
])
}

export function percentage(partialValue: number, totalValue: number) {
return (100 * partialValue) / totalValue
}

export function convertToFactor(percentage: number | undefined): number {
// Check if the input is within the valid range
if (percentage) {
if (percentage < 1 || percentage > 100) {
throw new Error("The percentage value should be between 1 and 100.")
}

// Calculate the factor
const factor = percentage / 100 + 1

return factor
}
return 1
}
9 changes: 6 additions & 3 deletions src/paymaster/BiconomyPaymaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,12 @@ export class BiconomyPaymaster

if (response?.result) {
const paymasterAndData = response.result.paymasterAndData
const preVerificationGas = response.result.preVerificationGas
const verificationGasLimit = response.result.verificationGasLimit
const callGasLimit = response.result.callGasLimit
const preVerificationGas =
response.result.preVerificationGas ?? _userOp.preVerificationGas
const verificationGasLimit =
response.result.verificationGasLimit ?? _userOp.verificationGasLimit
const callGasLimit =
response.result.callGasLimit ?? _userOp.callGasLimit
return {
paymasterAndData: paymasterAndData,
preVerificationGas: preVerificationGas,
Expand Down
Loading
Loading