Sponsor Gas
In the Quickstart, we covered an example that sends a user operation with gas sponsorship using our Gas Manager. In this guide, we’ll recap how to sponsor gas using the Gas Manager, and also show you how to use other paymaster providers as well.
Sponsor Gas with Gas Manager
1. Create gas policy
A gas manager policy is a set of rules that define which UOs are eligible for gas sponsorship. You can control which operations are eligible for sponsorship by defining rules:
- Spending rules: limit the amount of money or the number of user ops that can be sponsored by this policy
- Allowlist: restrict wallet addresses that are eligible for sponsorship. The policy will only sponsor gas for UOs that were sent by addresses on this list.
- Blocklist: ban certain addresses from receiving sponsorship under this policy
- Policy duration: define the duration of your policy and the sponsorship expiry period. This is the period for which the Gas Manager signature (paymaster data) will remain valid once it is generated.
To learn more about policy configuration, refer to the guide on setting up a gas manager policy.
Once you have decided on policy rules for your app, create a policy in the Gas Manager dashboard.
Now you should have a Gas policy created with a policy id you can use to sponsor gas for your users.
2. Create a Smart Account Client
Now you can create a Smart Account Client which is configured to sponsor gas.
import {
function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy,
function createAlchemySmartAccountClient<TChain extends Chain = Chain, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined, TContext extends UserOperationContext | undefined = UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<TChain, TAccount, TContext>): AlchemySmartAccountClient<TChain, TAccount, Record<string, never>, TContext>createAlchemySmartAccountClient,
const sepolia: Chainsepolia,
} from "@account-kit/infra";
import { function createLightAccount<TTransport extends Transport = Transport, TSigner extends SmartAccountSigner = SmartAccountSigner<any>, TLightAccountVersion extends LightAccountVersion<"LightAccount"> = "v2.0.0">(config: CreateLightAccountParams<TTransport, TSigner, TLightAccountVersion>): Promise<LightAccount<TSigner, TLightAccountVersion>>createLightAccount } from "@account-kit/smart-contracts";
// You can replace this with any signer you'd like
// We're using a LocalAccountSigner to generate a local key to sign with
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
const const alchemyTransport: AlchemyTransportalchemyTransport = function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({
apiKey: stringapiKey: "YOUR_API_KEY",
});
export const const client: {
[x: string]: never;
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">;
... 85 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}client = createAlchemySmartAccountClient<Chain, LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">, UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<...>): {
...;
}createAlchemySmartAccountClient({
transport: AlchemyTransportThe RPC transport
transport: const alchemyTransport: AlchemyTransportalchemyTransport,
policyId?: string | string[] | undefinedpolicyId: "YOUR_POLICY_ID",
chain?: Chain | undefinedChain for the client.
chain: const sepolia: Chainsepolia,
account?: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0"> | undefinedaccount: await createLightAccount<AlchemyTransport, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">(config: CreateLightAccountParams<...>): Promise<...>createLightAccount({
chain: Chainchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport: const alchemyTransport: AlchemyTransportalchemyTransport,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
}),
});
3. Send a user operation with gas sponsorship
import { import clientclient } from "./client";
const { const hash: anyhash } = await import clientclient.anysendUserOperation({
uo: {
target: string;
data: string;
value: bigint;
}uo: {
target: stringtarget: "0xTARGET_ADDRESS",
data: stringdata: "0x",
value: bigintvalue: 0n,
},
});
import {
function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy,
function createAlchemySmartAccountClient<TChain extends Chain = Chain, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined, TContext extends UserOperationContext | undefined = UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<TChain, TAccount, TContext>): AlchemySmartAccountClient<TChain, TAccount, Record<string, never>, TContext>createAlchemySmartAccountClient,
const sepolia: Chainsepolia,
} from "@account-kit/infra";
import { function createLightAccount<TTransport extends Transport = Transport, TSigner extends SmartAccountSigner = SmartAccountSigner<any>, TLightAccountVersion extends LightAccountVersion<"LightAccount"> = "v2.0.0">(config: CreateLightAccountParams<TTransport, TSigner, TLightAccountVersion>): Promise<LightAccount<TSigner, TLightAccountVersion>>createLightAccount } from "@account-kit/smart-contracts";
// You can replace this with any signer you'd like
// We're using a LocalAccountSigner to generate a local key to sign with
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
const const alchemyTransport: AlchemyTransportalchemyTransport = function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({
apiKey: stringapiKey: "YOUR_API_KEY",
});
export const const client: {
[x: string]: never;
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">;
... 85 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}client = createAlchemySmartAccountClient<Chain, LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">, UserOperationContext | undefined>(params: AlchemySmartAccountClientConfig<...>): {
...;
}createAlchemySmartAccountClient({
transport: AlchemyTransportThe RPC transport
transport: const alchemyTransport: AlchemyTransportalchemyTransport,
policyId?: string | string[] | undefinedpolicyId: "YOUR_POLICY_ID",
chain?: Chain | undefinedChain for the client.
chain: const sepolia: Chainsepolia,
account?: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0"> | undefinedaccount: await createLightAccount<AlchemyTransport, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">(config: CreateLightAccountParams<...>): Promise<...>createLightAccount({
chain: Chainchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport: const alchemyTransport: AlchemyTransportalchemyTransport,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
}),
});
Sponsor Gas with 3rd-party paymaster
If you’re using a 3rd party paymaster to sponsor gas, but our Bundler RPC endpoints, the set up is a bit more involved, but easily doable.
Prerequisite: install aa-sdk/core
Because the Smart Account Client exported from @account-kit/infra
assumes you’re using our infra for both gas sponsorship and bundler RPCs, you’ll need to install @aa-sdk/core
so you can create more configurable clients.
ERC-7677 compliant paymasters
If your paymaster is ERC-7677 compliant, we export a paymaster middleware that makes it really easy to integrate with that paymaster.
import {
function createSmartAccountClient<TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined, TContext extends UserOperationContext | undefined = UserOperationContext | undefined>(config: SmartAccountClientConfig<TTransport, TChain, TAccount, TContext>): SmartAccountClient<TTransport, TChain, TAccount>createSmartAccountClient,
function erc7677Middleware<TContext extends Record<string, any> | undefined = Record<string, any> | undefined>(params?: Erc7677MiddlewareParams<TContext>): Required<Pick<ClientMiddlewareConfig, "dummyPaymasterAndData" | "paymasterAndData">>Middleware function for interacting with ERC-7677 enabled clients. It supports resolving paymaster and data fields for user operations. This middleware assumes that your RPC provider supports the ERC-7677 methods (pm_getPaymasterStubData and pm_getPaymasterData).
erc7677Middleware,
const split: (params: SplitTransportParams) => CustomTransportThe Split Transport allows you to split RPC traffic for specific methods across different RPC providers. This is done by specifying the methods you want handled specially as overrides and providing a fallback transport for all other methods.
split,
} from "@aa-sdk/core";
import {
const sepolia: Chainsepolia,
const alchemyFeeEstimator: (transport: AlchemyTransport) => ClientMiddlewareFnFunction that estimates the transaction fees using Alchemy methods for a given client. It fetches the latest block and estimates the max priority fee per gas, applying any overrides or fee options provided.
alchemyFeeEstimator,
const createAlchemyPublicRpcClient: ({ transport, chain, }: {
transport: AlchemyTransport;
chain: Chain | undefined;
}) => ClientWithAlchemyMethodsCreates an Alchemy public RPC client with the provided chain, connection configuration, and optional fetch options. The client has alchemy methods and can dynamically update HTTP headers.
createAlchemyPublicRpcClient,
function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy,
} from "@account-kit/infra";
import { function createLightAccount<TTransport extends Transport = Transport, TSigner extends SmartAccountSigner = SmartAccountSigner<any>, TLightAccountVersion extends LightAccountVersion<"LightAccount"> = "v2.0.0">(config: CreateLightAccountParams<TTransport, TSigner, TLightAccountVersion>): Promise<LightAccount<TSigner, TLightAccountVersion>>createLightAccount } from "@account-kit/smart-contracts";
// You can replace this with any signer you'd like
// We're using a LocalAccountSigner to generate a local key to sign with
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>http, function custom<provider extends EthereumProvider>(provider: provider, config?: CustomTransportConfig): CustomTransportcustom } from "viem";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
// 1. create an alchemy rpc client
const const alchemyTransport: AlchemyTransportalchemyTransport = function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({
apiKey: stringapiKey: "API_KEY",
});
const const alchemyRpcClient: ClientWithAlchemyMethodsalchemyRpcClient = function createAlchemyPublicRpcClient({ transport, chain, }: {
transport: AlchemyTransport;
chain: Chain | undefined;
}): ClientWithAlchemyMethodsCreates an Alchemy public RPC client with the provided chain, connection configuration, and optional fetch options. The client has alchemy methods and can dynamically update HTTP headers.
createAlchemyPublicRpcClient({
chain: Chain | undefinedchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport: const alchemyTransport: AlchemyTransportalchemyTransport,
});
// 2. create a split transport to route traffic between the paymaster and the bundler
const const transport: CustomTransporttransport = function split(params: SplitTransportParams): CustomTransportThe Split Transport allows you to split RPC traffic for specific methods across different RPC providers. This is done by specifying the methods you want handled specially as overrides and providing a fallback transport for all other methods.
split({
SplitTransportParams.overrides: {
methods: string[];
transport: Transport;
}[]overrides: [
{
methods: string[]methods: ["pm_getPaymasterStubData", "pm_getPaymasterData"],
transport: Transporttransport: http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>http("PAYMASTER_URL"),
},
],
SplitTransportParams.fallback: Transportfallback: const alchemyTransport: AlchemyTransportalchemyTransport,
});
// 3. create smart account client
export const const client: {
[x: string]: unknown;
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}client = createSmartAccountClient<CustomTransport, Chain, LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">, UserOperationContext>(config: {
...;
}): {
...;
}createSmartAccountClient({
transport: CustomTransportThe RPC transport
transport,
chain?: Chain | undefinedChain for the client.
chain: const sepolia: Chainsepolia,
account?: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0"> | undefinedaccount: await createLightAccount<AlchemyTransport, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">(config: CreateLightAccountParams<...>): Promise<...>createLightAccount({
chain: Chainchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport: const alchemyTransport: AlchemyTransportalchemyTransport,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
}),
// this is required to get correct fee estimates when using our Bundler RPC
feeEstimator?: ClientMiddlewareFn<UserOperationContext> | undefinedfeeEstimator: function alchemyFeeEstimator(transport: AlchemyTransport): ClientMiddlewareFnFunction that estimates the transaction fees using Alchemy methods for a given client. It fetches the latest block and estimates the max priority fee per gas, applying any overrides or fee options provided.
alchemyFeeEstimator(const alchemyRpcClient: ClientWithAlchemyMethodsalchemyRpcClient),
...erc7677Middleware<Record<string, any> | undefined>(params?: Erc7677MiddlewareParams<Record<string, any> | undefined, keyof EntryPointRegistryBase<unknown>> | undefined): Required<Pick<ClientMiddlewareConfig, "dummyPaymasterAndData" | "paymasterAndData">>Middleware function for interacting with ERC-7677 enabled clients. It supports resolving paymaster and data fields for user operations. This middleware assumes that your RPC provider supports the ERC-7677 methods (pm_getPaymasterStubData and pm_getPaymasterData).
erc7677Middleware(),
});
Other 3rd Party Paymasters
If your paymaster provider is not ERC-7677 compliant, the setup is very similar, but you’ll need to provide your own paymasterAndData
and dummyPaymasterAndData
middleware overrides
to handle the paymaster calls. In this example, we’ll show you how to use Stackup to provide ERC-20 gas payments.
import {
function createSmartAccountClient<TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined, TContext extends UserOperationContext | undefined = UserOperationContext | undefined>(config: SmartAccountClientConfig<TTransport, TChain, TAccount, TContext>): SmartAccountClient<TTransport, TChain, TAccount>createSmartAccountClient,
const split: (params: SplitTransportParams) => CustomTransportThe Split Transport allows you to split RPC traffic for specific methods across different RPC providers. This is done by specifying the methods you want handled specially as overrides and providing a fallback transport for all other methods.
split,
function deepHexlify(obj: any): anyRecursively converts all values in an object to hex strings
deepHexlify,
function resolveProperties<T>(object: Deferrable<T>): Promise<T>Await all of the properties of a Deferrable object
resolveProperties,
} from "@aa-sdk/core";
import {
const sepolia: Chainsepolia,
const alchemyFeeEstimator: (transport: AlchemyTransport) => ClientMiddlewareFnFunction that estimates the transaction fees using Alchemy methods for a given client. It fetches the latest block and estimates the max priority fee per gas, applying any overrides or fee options provided.
alchemyFeeEstimator,
const createAlchemyPublicRpcClient: ({ transport, chain, }: {
transport: AlchemyTransport;
chain: Chain | undefined;
}) => ClientWithAlchemyMethodsCreates an Alchemy public RPC client with the provided chain, connection configuration, and optional fetch options. The client has alchemy methods and can dynamically update HTTP headers.
createAlchemyPublicRpcClient,
function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy,
} from "@account-kit/infra";
import { function createLightAccount<TTransport extends Transport = Transport, TSigner extends SmartAccountSigner = SmartAccountSigner<any>, TLightAccountVersion extends LightAccountVersion<"LightAccount"> = "v2.0.0">(config: CreateLightAccountParams<TTransport, TSigner, TLightAccountVersion>): Promise<LightAccount<TSigner, TLightAccountVersion>>createLightAccount } from "@account-kit/smart-contracts";
// You can replace this with any signer you'd like
// We're using a LocalAccountSigner to generate a local key to sign with
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>http, function custom<provider extends EthereumProvider>(provider: provider, config?: CustomTransportConfig): CustomTransportcustom } from "viem";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
// 1. create an alchemy rpc client
const const alchemyTransport: AlchemyTransportalchemyTransport = function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({
apiKey: stringapiKey: "API_KEY",
});
const const alchemyRpcClient: ClientWithAlchemyMethodsalchemyRpcClient = function createAlchemyPublicRpcClient({ transport, chain, }: {
transport: AlchemyTransport;
chain: Chain | undefined;
}): ClientWithAlchemyMethodsCreates an Alchemy public RPC client with the provided chain, connection configuration, and optional fetch options. The client has alchemy methods and can dynamically update HTTP headers.
createAlchemyPublicRpcClient({
chain: Chain | undefinedchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport: const alchemyTransport: AlchemyTransportalchemyTransport,
});
// 2. create a split transport to route traffic between the paymaster and the bundler
const const transport: CustomTransporttransport = function split(params: SplitTransportParams): CustomTransportThe Split Transport allows you to split RPC traffic for specific methods across different RPC providers. This is done by specifying the methods you want handled specially as overrides and providing a fallback transport for all other methods.
split({
SplitTransportParams.overrides: {
methods: string[];
transport: Transport;
}[]overrides: [
{
methods: string[]methods: ["pm_sponsorUserOperation"],
// TODO: Replace with your stackup API key here (https://docs.stackup.sh/docs/paymaster-api)
transport: Transporttransport: http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>http("https://api.stackup.sh/v1/paymaster/STACKUP_API_KEY"),
},
],
SplitTransportParams.fallback: Transportfallback: const alchemyTransport: AlchemyTransportalchemyTransport,
});
// 3. create smart account client
export const const client: {
[x: string]: unknown;
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}client = createSmartAccountClient<CustomTransport, Chain, LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">, UserOperationContext>(config: {
...;
}): {
...;
}createSmartAccountClient({
transport: CustomTransportThe RPC transport
transport,
chain?: Chain | undefinedChain for the client.
chain: const sepolia: Chainsepolia,
account?: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0"> | undefinedaccount: await createLightAccount<AlchemyTransport, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>, "v2.0.0">(config: CreateLightAccountParams<...>): Promise<...>createLightAccount({
chain: Chainchain: const sepolia: Chainsepolia,
transport: AlchemyTransporttransport: const alchemyTransport: AlchemyTransportalchemyTransport,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
}),
// Bypasses alchemy gas estimation and instead uses Stackup for gas estimation
// If your paymaster doesn't provide gas estimation, then don't replace the gasEstimator
gasEstimator?: ClientMiddlewareFn<UserOperationContext> | undefinedgasEstimator: async (userOp: Deferrable<UserOperationStruct<TEntryPointVersion>>userOp) => ({
...userOp: Deferrable<UserOperationStruct<TEntryPointVersion>>userOp,
callGasLimit?: PromiseOrValue<UserOperationStruct<TEntryPointVersion>["callGasLimit"]> | undefinedcallGasLimit: "0x0",
preVerificationGas?: PromiseOrValue<UserOperationStruct<TEntryPointVersion>["preVerificationGas"]> | undefinedpreVerificationGas: "0x0",
verificationGasLimit?: PromiseOrValue<UserOperationStruct<TEntryPointVersion>["verificationGasLimit"]> | undefinedverificationGasLimit: "0x0",
}),
// this is required to get correct fee estimates when using our Bundler RPC
feeEstimator?: ClientMiddlewareFn<UserOperationContext> | undefinedfeeEstimator: function alchemyFeeEstimator(transport: AlchemyTransport): ClientMiddlewareFnFunction that estimates the transaction fees using Alchemy methods for a given client. It fetches the latest block and estimates the max priority fee per gas, applying any overrides or fee options provided.
alchemyFeeEstimator(const alchemyRpcClient: ClientWithAlchemyMethodsalchemyRpcClient),
dummyPaymasterAndData?: ClientMiddlewareFn<UserOperationContext> | undefineddummyPaymasterAndData: async (userop: Deferrable<UserOperationStruct<TEntryPointVersion>>userop) => ({
...userop: Deferrable<UserOperationStruct<TEntryPointVersion>>userop,
paymasterAndData: stringpaymasterAndData: "0x",
}),
paymasterAndData?: ClientMiddlewareFn<UserOperationContext> | undefinedpaymasterAndData: async (userop: Deferrable<UserOperationStruct<TEntryPointVersion>>userop, { client: C extends MiddlewareClientclient, account: TAccount extends SmartContractAccountaccount }) => {
const const pmResponse: anypmResponse: any = await client: C extends MiddlewareClientclient.request: <undefined, {
method: "eth_sendUserOperation";
params: [UserOperationRequest, `0x${string}`];
} | {
method: "eth_estimateUserOperationGas";
params: [UserOperationRequest, `0x${string}`, (RpcStateOverride | undefined)?];
} | ... 44 more ... | {
...;
}, string | ... 14 more ... | null>(args: {
method: "eth_sendUserOperation";
params: [UserOperationRequest, `0x${string}`];
} | ... 45 more ... | {
...;
}, options?: EIP1193RequestOptions | undefined) => Promise<...>Request function wrapped with friendly error handling
request({
// @ts-ignore
method: "pm_sponsorUserOperation"method: "pm_sponsorUserOperation",
params: [any, `0x${string}`, {
type: string;
}]params: [
function deepHexlify(obj: any): anyRecursively converts all values in an object to hex strings
deepHexlify(await resolveProperties<UserOperationStruct<TEntryPointVersion>>(object: Deferrable<UserOperationStruct<TEntryPointVersion>>): Promise<...>Await all of the properties of a Deferrable object
resolveProperties(userop: Deferrable<UserOperationStruct<TEntryPointVersion>>userop)),
account: TAccount extends SmartContractAccountaccount.getEntryPoint: () => EntryPointDef<keyof EntryPointRegistryBase<unknown>>getEntryPoint().address: `0x${string}`address,
{
// @ts-ignore
type: stringtype: "payg", // Replace with ERC20 context based on stackups documentation
},
],
});
return {
...userop: Deferrable<UserOperationStruct<TEntryPointVersion>>userop,
...const pmResponse: anypmResponse,
};
},
});