@account-kit/infra
Version:
adapters for @aa-sdk/core for interacting with alchemy services
206 lines (194 loc) • 6.83 kB
text/typescript
import {
ChainNotFoundError,
clientHeaderTrack,
createSmartAccountClient,
isSmartAccountWithSigner,
type Prettify,
type SmartAccountClient,
type SmartAccountClientActions,
type SmartAccountClientConfig,
type SmartAccountClientRpcSchema,
type SmartContractAccount,
type SmartContractAccountWithSigner,
type UserOperationContext,
} from "@aa-sdk/core";
import { type Chain } from "viem";
import {
alchemy,
convertHeadersToObject,
type AlchemyTransport,
} from "../alchemyTransport.js";
import { getDefaultUserOperationFeeOptions } from "../defaults.js";
import { alchemyFeeEstimator } from "../middleware/feeEstimator.js";
import { alchemyGasAndPaymasterAndDataMiddleware } from "../middleware/gasManager.js";
import { alchemyUserOperationSimulator } from "../middleware/userOperationSimulator.js";
import {
alchemyActions,
type AlchemySmartAccountClientActions,
} from "./decorators/smartAccount.js";
import type { AlchemyRpcSchema } from "./types.js";
import { headersUpdate } from "../alchemyTrackerHeaders.js";
import type { PolicyToken } from "../middleware/gasManager.js";
export function getSignerTypeHeader<
TAccount extends SmartContractAccountWithSigner,
>(account: TAccount) {
return { "Alchemy-Aa-Sdk-Signer": account.getSigner().signerType };
}
// #region AlchemySmartAccountClientConfig
export type AlchemySmartAccountClientConfig<
chain extends Chain | undefined = Chain | undefined,
account extends SmartContractAccount | undefined =
| SmartContractAccount
| undefined,
context extends UserOperationContext | undefined =
| UserOperationContext
| undefined,
> = {
account?: account;
useSimulation?: boolean;
policyId?: string | string[];
policyToken?: PolicyToken;
} & Pick<
SmartAccountClientConfig<AlchemyTransport, chain, account, context>,
| "customMiddleware"
| "feeEstimator"
| "gasEstimator"
| "signUserOperation"
| "transport"
| "chain"
| "opts"
>;
// #endregion AlchemySmartAccountClientConfig
export type BaseAlchemyActions<
chain extends Chain | undefined = Chain | undefined,
account extends SmartContractAccount | undefined =
| SmartContractAccount
| undefined,
context extends UserOperationContext | undefined =
| UserOperationContext
| undefined,
> = SmartAccountClientActions<chain, account, context> &
AlchemySmartAccountClientActions<account, context>;
export type AlchemySmartAccountClient_Base<
chain extends Chain | undefined = Chain | undefined,
account extends SmartContractAccount | undefined =
| SmartContractAccount
| undefined,
actions extends Record<string, unknown> = Record<string, unknown>,
context extends UserOperationContext | undefined =
| UserOperationContext
| undefined,
> = Prettify<
SmartAccountClient<
AlchemyTransport,
chain,
account,
actions & BaseAlchemyActions<chain, account, context>,
[...SmartAccountClientRpcSchema, ...AlchemyRpcSchema],
context
>
>;
export type AlchemySmartAccountClient<
chain extends Chain | undefined = Chain | undefined,
account extends SmartContractAccount | undefined =
| SmartContractAccount
| undefined,
actions extends Record<string, unknown> = Record<string, unknown>,
context extends UserOperationContext | undefined =
| UserOperationContext
| undefined,
> = Prettify<AlchemySmartAccountClient_Base<chain, account, actions, context>>;
export 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>;
/**
* Creates an Alchemy smart account client using the provided configuration options, including account details, gas manager configuration, and custom middleware.
*
* @example
* ```ts
* import { createAlchemySmartAccountClient, alchemy } from "@account-kit/infra";
* import { sepolia } from "@account-kit/infra/chain";
*
* const client = createAlchemySmartAccountClient({
* chain: sepolia,
* transport: alchemy({ apiKey: "your-api-key" }),
* });
* ```
*
* @param {AlchemySmartAccountClientConfig} config The configuration for creating the Alchemy smart account client
* @returns {AlchemySmartAccountClient} An instance of `AlchemySmartAccountClient` configured based on the provided options
*/
export function createAlchemySmartAccountClient(
config: AlchemySmartAccountClientConfig,
): AlchemySmartAccountClient {
if (!config.chain) {
throw new ChainNotFoundError();
}
const feeOptions =
config.opts?.feeOptions ?? getDefaultUserOperationFeeOptions(config.chain);
const scaClient = createSmartAccountClient({
account: config.account,
transport: config.transport,
chain: config.chain,
type: "AlchemySmartAccountClient",
opts: {
...config.opts,
feeOptions,
},
feeEstimator: config.feeEstimator ?? alchemyFeeEstimator(config.transport),
gasEstimator: config.gasEstimator,
customMiddleware: async (struct, args) => {
if (isSmartAccountWithSigner(args.account)) {
config.transport.updateHeaders(getSignerTypeHeader(args.account));
}
return config.customMiddleware
? config.customMiddleware(struct, args)
: struct;
},
...(config.policyId
? alchemyGasAndPaymasterAndDataMiddleware({
policyId: config.policyId,
policyToken: config.policyToken,
transport: config.transport,
gasEstimatorOverride: config.gasEstimator,
feeEstimatorOverride: config.feeEstimator,
})
: {}),
userOperationSimulator: config.useSimulation
? alchemyUserOperationSimulator(config.transport)
: undefined,
signUserOperation: config.signUserOperation,
addBreadCrumb(breadcrumb: string) {
const oldConfig = config.transport.config;
const dynamicFetchOptions = config.transport.dynamicFetchOptions;
const newTransport = alchemy({ ...oldConfig });
newTransport.updateHeaders(
headersUpdate(breadcrumb)(
convertHeadersToObject(dynamicFetchOptions?.headers),
),
);
return createAlchemySmartAccountClient({
...config,
transport: newTransport,
}) as any;
},
})
.extend(alchemyActions)
.extend((client_) => ({
addBreadcrumb(breadcrumb: string) {
return clientHeaderTrack(client_, breadcrumb);
},
}));
if (config.account && isSmartAccountWithSigner(config.account)) {
config.transport.updateHeaders(getSignerTypeHeader(config.account));
}
return scaClient;
}