Skip to content

Commit

Permalink
chore: add paymaster
Browse files Browse the repository at this point in the history
  • Loading branch information
joepegler committed Sep 3, 2024
1 parent b2a3e28 commit c3c4a37
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 51 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ CHAIN_ID=84532
RPC_URL=
BUNDLER_URL=
BICONOMY_SDK_DEBUG=false
RUN_PLAYGROUND=false
RUN_PLAYGROUND=false
PAYMASTER_URL=
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"name": "paymaster (tree-shaking)",
"path": "./dist/_esm/paymaster/index.js",
"limit": "15 kB",
"import": "{ createPaymaster }",
"import": "{ toPaymaster }",
"ignore": ["node:fs", "fs"]
}
]
52 changes: 39 additions & 13 deletions src/account/NexusSmartAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import {
type IHybridPaymaster,
type IPaymaster,
Paymaster,
PaymasterMode,
type SponsorUserOperationDto
} from "../paymaster/index.js"
import {
Expand Down Expand Up @@ -152,10 +151,6 @@ export class NexusSmartAccount extends BaseSmartContractAccount {
this.paymaster = new Paymaster({
paymasterUrl: nexusSmartAccountConfig.paymasterUrl
})
} else if (nexusSmartAccountConfig.biconomyPaymasterApiKey) {
this.paymaster = new Paymaster({
paymasterUrl: `https://paymaster.biconomy.io/api/v1/${nexusSmartAccountConfig.chain.id}/${nexusSmartAccountConfig.biconomyPaymasterApiKey}`
})
} else {
this.paymaster = nexusSmartAccountConfig.paymaster
}
Expand Down Expand Up @@ -328,7 +323,7 @@ export class NexusSmartAccount extends BaseSmartContractAccount {
*
* const amountInWei = await smartAccount.getGasEstimates([tx, tx], {
* paymasterServiceData: {
* mode: PaymasterMode.SPONSORED,
* mode: "SPONSORED",
* },
* });
*
Expand Down Expand Up @@ -495,14 +490,14 @@ export class NexusSmartAccount extends BaseSmartContractAccount {
* ],
* account.address, // Default recipient used if no recipient is present in the withdrawal request
* {
* paymasterServiceData: { mode: PaymasterMode.SPONSORED },
* paymasterServiceData: { mode: "SPONSORED" },
* }
* );
*
* // OR to withdraw all of the native token, leaving no dust in the smart account
*
* const { wait } = await smartAccount.withdraw([], account.address, {
* paymasterServiceData: { mode: PaymasterMode.SPONSORED },
* paymasterServiceData: { mode: "SPONSORED" },
* });
*
* const { success } = await wait();
Expand Down Expand Up @@ -906,13 +901,13 @@ export class NexusSmartAccount extends BaseSmartContractAccount {
* data: encodedCall
* }
*
* const feeQuotesResponse: FeeQuotesOrDataResponse = await smartAccount.getTokenFees(transaction, { paymasterServiceData: { mode: PaymasterMode.ERC20 } });
* const feeQuotesResponse: FeeQuotesOrDataResponse = await smartAccount.getTokenFees(transaction, { paymasterServiceData: { mode: "ERC20" } });
*
* const userSeletedFeeQuote = feeQuotesResponse.feeQuotes?.[0];
*
* const { wait } = await smartAccount.sendTransaction(transaction, {
* paymasterServiceData: {
* mode: PaymasterMode.ERC20,
* mode: "ERC20",
* feeQuote: userSeletedFeeQuote,
* spender: feeQuotesResponse.tokenPaymasterAddress,
* },
Expand Down Expand Up @@ -976,7 +971,7 @@ export class NexusSmartAccount extends BaseSmartContractAccount {
to: await this.getAddress()
},
{
paymasterServiceData: { mode: PaymasterMode.ERC20 }
paymasterServiceData: { mode: "ERC20" }
}
)

Expand Down Expand Up @@ -1209,7 +1204,7 @@ export class NexusSmartAccount extends BaseSmartContractAccount {
bundlerUrl: `https://bundler.biconomy.io/api/v2/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44`,
chainId: 84532
});
const response = await smartAccount.transferOwnership(newOwner, DEFAULT_ECDSA_OWNERSHIP_MODULE, {paymasterServiceData: {mode: PaymasterMode.SPONSORED}});
const response = await smartAccount.transferOwnership(newOwner, DEFAULT_ECDSA_OWNERSHIP_MODULE, {paymasterServiceData: {mode: "SPONSORED"}});
walletClient = createWalletClient({
newOwnerAccount,
Expand Down Expand Up @@ -1438,9 +1433,40 @@ export class NexusSmartAccount extends BaseSmartContractAccount {

userOp = await this.estimateUserOpGas(userOp)

if (buildUseropDto?.paymasterServiceData?.mode === "SPONSORED") {
userOp = await this.getPaymasterAndData(
userOp,
buildUseropDto?.paymasterServiceData
)
}

return userOp
}

private async getPaymasterAndData(
userOp: Partial<UserOperationStruct>,
paymasterServiceData: PaymasterUserOperationDto
): Promise<UserOperationStruct> {
const paymaster = this
.paymaster as IHybridPaymaster<PaymasterUserOperationDto>
const paymasterData = await paymaster.getPaymasterAndData(
userOp,
paymasterServiceData
)
const userOpStruct = {
...userOp,
...paymasterData,
callGasLimit: BigInt(userOp.callGasLimit ?? 0n),
verificationGasLimit: BigInt(userOp.verificationGasLimit ?? 0n),
preVerificationGas: BigInt(userOp.preVerificationGas ?? 0n),
sender: (await this.getAddress()) as Hex,
paymasterAndData: undefined
// paymasterAndData: paymasterData?.paymasterAndData as Hex
} as UserOperationStruct

return userOpStruct
}

private validateUserOpAndPaymasterRequest(
userOp: Partial<UserOperationStruct>,
tokenPaymasterRequest: BiconomyTokenPaymasterRequest
Expand Down Expand Up @@ -1612,7 +1638,7 @@ export class NexusSmartAccount extends BaseSmartContractAccount {
*
* // If you want to use a paymaster...
* const { wait } = await smartAccount.deploy({
* paymasterServiceData: { mode: PaymasterMode.SPONSORED },
* paymasterServiceData: { mode: "SPONSORED" },
* });
*
* // Or if you can't use a paymaster send native token to this address:
Expand Down
3 changes: 1 addition & 2 deletions src/account/utils/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type {
FeeQuotesOrDataDto,
IPaymaster,
PaymasterFeeQuote,
PaymasterMode,
SmartAccountData,
SponsorUserOperationDto
} from "../../paymaster"
Expand Down Expand Up @@ -256,7 +255,7 @@ export type InitilizationData = {
export type PaymasterUserOperationDto = SponsorUserOperationDto &
FeeQuotesOrDataDto & {
/** mode: sponsored or erc20 */
mode: PaymasterMode
mode: "SPONSORED" | "ERC20"
/** Always recommended, especially when using token paymaster */
calculateGasLimits?: boolean
/** Expiry duration in seconds */
Expand Down
49 changes: 25 additions & 24 deletions src/paymaster/BiconomyPaymaster.ts → src/paymaster/Paymaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import {
type Transaction,
type UserOperationStruct,
sendRequest
} from "../account"
} from "../account/index.js"
import { deepHexlify } from "../bundler/index.js"
import type { IHybridPaymaster } from "./interfaces/IHybridPaymaster.js"
import { ADDRESS_ZERO, ERC20_ABI, MAX_UINT256 } from "./utils/Constants.js"
import { getTimestampInSeconds } from "./utils/Helpers.js"
import {
type FeeQuotesOrDataDto,
type FeeQuotesOrDataResponse,
type Hex,
type JsonRpcResponse,
type PaymasterAndDataResponse,
type PaymasterConfig,
type PaymasterFeeQuote,
PaymasterMode,
type SponsorUserOperationDto
import type {
FeeQuotesOrDataDto,
FeeQuotesOrDataResponse,
Hex,
JsonRpcResponse,
PaymasterAndDataResponse,
PaymasterConfig,
PaymasterFeeQuote,
SponsorUserOperationDto
} from "./utils/Types.js"

const defaultPaymasterConfig: PaymasterConfig = {
Expand All @@ -29,9 +29,7 @@ const defaultPaymasterConfig: PaymasterConfig = {
/**
* @dev Hybrid - Generic Gas Abstraction paymaster
*/
export class BiconomyPaymaster
implements IHybridPaymaster<SponsorUserOperationDto>
{
export class Paymaster implements IHybridPaymaster<SponsorUserOperationDto> {
paymasterConfig: PaymasterConfig

constructor(config: PaymasterConfig) {
Expand Down Expand Up @@ -163,7 +161,7 @@ export class BiconomyPaymaster
): Promise<FeeQuotesOrDataResponse> {
// const userOp = await this.prepareUserOperation(_userOp)

let mode: PaymasterMode | null = null
let mode: "SPONSORED" | "ERC20" | null = null
let expiryDuration: number | null = null
const calculateGasLimits = paymasterServiceData.calculateGasLimits ?? true
let preferredToken: string | null = null
Expand Down Expand Up @@ -230,7 +228,7 @@ export class BiconomyPaymaster
)

if (response?.result) {
if (response.result.mode === PaymasterMode.ERC20) {
if (response.result.mode === "ERC20") {
const feeQuotesResponse: Array<PaymasterFeeQuote> =
response.result.feeQuotes
const paymasterAddress: Hex = response.result.paymasterAddress
Expand All @@ -240,7 +238,7 @@ export class BiconomyPaymaster
tokenPaymasterAddress: paymasterAddress
}
}
if (response.result.mode === PaymasterMode.SPONSORED) {
if (response.result.mode === "SPONSORED") {
const paymasterAndData: Hex = response.result.paymasterAndData
const preVerificationGas = response.result.preVerificationGas
const verificationGasLimit = response.result.verificationGasLimit
Expand All @@ -267,7 +265,7 @@ export class BiconomyPaymaster
// Note: we may not throw if we include strictMode off and return paymasterData '0x'.
if (
!this.paymasterConfig.strictMode &&
paymasterServiceData.mode === PaymasterMode.SPONSORED &&
paymasterServiceData.mode === "SPONSORED" &&
(error?.message.includes("Smart contract data not found") ||
error?.message.includes("No policies were set"))
// can also check based on error.code being -32xxx
Expand Down Expand Up @@ -317,7 +315,7 @@ export class BiconomyPaymaster
let webhookData: Record<string, any> | null = null
let expiryDuration: number | null = null

if (mode === PaymasterMode.ERC20) {
if (mode === "ERC20") {
if (
!paymasterServiceData?.feeTokenAddress &&
paymasterServiceData?.feeTokenAddress === ADDRESS_ZERO
Expand All @@ -336,6 +334,8 @@ export class BiconomyPaymaster

// Note: The idea is before calling this below rpc, userOp values presense and types should be in accordance with how we call eth_estimateUseropGas on the bundler

const hexlifiedUserOp = deepHexlify(userOp)

try {
const response: JsonRpcResponse = await sendRequest(
{
Expand All @@ -344,7 +344,7 @@ export class BiconomyPaymaster
body: {
method: "pm_sponsorUserOperation",
params: [
userOp,
hexlifiedUserOp,
{
mode: mode,
calculateGasLimits: calculateGasLimits,
Expand Down Expand Up @@ -401,9 +401,10 @@ export class BiconomyPaymaster
return "0x"
}

public static async create(
config: PaymasterConfig
): Promise<BiconomyPaymaster> {
return new BiconomyPaymaster(config)
public static async create(config: PaymasterConfig): Promise<Paymaster> {
return new Paymaster(config)
}
}

export const toPaymaster = Paymaster.create
export default toPaymaster
5 changes: 2 additions & 3 deletions src/paymaster/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { BiconomyPaymaster } from "./BiconomyPaymaster.js"
import { Paymaster } from "./Paymaster.js"
export * from "./interfaces/IPaymaster.js"
export * from "./interfaces/IHybridPaymaster.js"
export * from "./utils/Types.js"
export * from "./BiconomyPaymaster.js"
export * from "./Paymaster.js"

export const Paymaster = BiconomyPaymaster
export const createPaymaster = Paymaster.create
9 changes: 2 additions & 7 deletions src/paymaster/utils/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type PaymasterConfig = {

export type SponsorUserOperationDto = {
/** mode: sponsored or erc20 */
mode: PaymasterMode
mode: "SPONSORED" | "ERC20"
/** Always recommended, especially when using token paymaster */
calculateGasLimits?: boolean
/** Expiry duration in seconds */
Expand All @@ -38,7 +38,7 @@ export type SponsorUserOperationDto = {

export type FeeQuotesOrDataDto = {
/** mode: sponsored or erc20 */
mode?: PaymasterMode
mode?: "SPONSORED" | "ERC20"
/** Expiry duration in seconds */
expiryDuration?: number
/** Always recommended, especially when using token paymaster */
Expand Down Expand Up @@ -121,8 +121,3 @@ export type PaymasterAndDataResponse = {
/* Value used by inner account execution */
callGasLimit: number
}

export enum PaymasterMode {
ERC20 = "ERC20",
SPONSORED = "SPONSORED"
}
Loading

0 comments on commit c3c4a37

Please sign in to comment.