Send User Operations
Once your users have been authenticated, you can start sending user operations!
Single user operation
In the below example, we use LightAccount
as the underlying Smart Contract
type. You can also use MultiOwnerModularAccount
if you want to provide your
users with an ERC-6900 compliant modular account, or you can use
MultiOwnerLightAccount
if you want to support an account with multiple
owners.
import { function watchSmartAccountClient<TAccount extends SupportedAccountTypes, TChain extends Chain | undefined = Chain | undefined>(params: GetSmartAccountClientParams<TChain, TAccount>, config: AlchemyAccountsConfig): (onChange: (client: GetSmartAccountClientResult<TChain, SupportedAccount<TAccount>>) => void) => () => voidWatches for changes to the smart account client and triggers the provided callback when a change is detected.
watchSmartAccountClient } from "@account-kit/core";
import { import configconfig } from "./config.js";
// How you actually store this state variable
// depends on the framework you're using
let let clientState: anyclientState;
// The watch smart account client will handle all of the possible state changes
// that can impact this client:
// - Signer status
// - Account instantiation
// - Chain changes
const const clientSubscription: () => voidclientSubscription = watchSmartAccountClient<"LightAccount", Chain | undefined>(params: GetSmartAccountClientParams<Chain | undefined, "LightAccount">, config: AlchemyAccountsConfig): (onChange: (client: GetSmartAccountClientResult<...>) => void) => () => voidWatches for changes to the smart account client and triggers the provided callback when a change is detected.
watchSmartAccountClient(
{
type: "LightAccount"type: "LightAccount",
},
import configconfig,
)((clientState_: GetSmartAccountClientResult<Chain | undefined, LightAccount<AlchemySigner>>clientState_) => {
let clientState: anyclientState = clientState_: GetSmartAccountClientResult<Chain | undefined, LightAccount<AlchemySigner>>clientState_;
});
if (let clientState: undefinedclientState == null || let clientState: neverclientState.anyisLoadingClient) {
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stdout
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout
See util.format()
for more information.
log("Loading...");
}
const const client: anyclient = let clientState: undefinedclientState.anyclient;
await const client: anyclient.anysendUserOperation({
uo: {
target: string;
data: string;
value: bigint;
}uo: {
target: stringtarget: "0xtarget",
data: stringdata: "0x",
value: bigintvalue: 0n,
},
});
import { const createConfig: (params: CreateConfigProps) => AlchemyAccountsConfigCreates an AlchemyAccountsConfig object that can be used in conjunction with the actions exported from @account-kit/core
.
The config contains core and client stores that can be used to manage account state in your application.
createConfig } from "@account-kit/core";
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, const sepolia: Chainsepolia } from "@account-kit/infra";
export const const config: AlchemyAccountsConfigconfig = function createConfig(params: CreateConfigProps): AlchemyAccountsConfigCreates an AlchemyAccountsConfig object that can be used in conjunction with the actions exported from @account-kit/core
.
The config contains core and client stores that can be used to manage account state in your application.
createConfig({
transport: AlchemyTransporttransport: 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" }),
chain: Chainchain: const sepolia: Chainsepolia,
// optional if you want to sponsor gas
policyId?: string | string[] | undefinedpolicyId: "YOUR_POLICY_ID",
});
Batch user operation
It’s also possible to send user operations in batch using the same approach!
import { function watchSmartAccountClient<TAccount extends SupportedAccountTypes, TChain extends Chain | undefined = Chain | undefined>(params: GetSmartAccountClientParams<TChain, TAccount>, config: AlchemyAccountsConfig): (onChange: (client: GetSmartAccountClientResult<TChain, SupportedAccount<TAccount>>) => void) => () => voidWatches for changes to the smart account client and triggers the provided callback when a change is detected.
watchSmartAccountClient } from "@account-kit/core";
import { import configconfig } from "./config.js";
// How you actually store this state variable
// depends on the framework you're using
let let clientState: anyclientState;
// The watch smart account client will handle all of the possible state changes
// that can impact this client:
// - Signer status
// - Account instantiation
// - Chain changes
const const clientSubscription: () => voidclientSubscription = watchSmartAccountClient<"LightAccount", Chain | undefined>(params: GetSmartAccountClientParams<Chain | undefined, "LightAccount">, config: AlchemyAccountsConfig): (onChange: (client: GetSmartAccountClientResult<...>) => void) => () => voidWatches for changes to the smart account client and triggers the provided callback when a change is detected.
watchSmartAccountClient(
{
type: "LightAccount"type: "LightAccount",
},
import configconfig,
)((clientState_: GetSmartAccountClientResult<Chain | undefined, LightAccount<AlchemySigner>>clientState_) => {
let clientState: anyclientState = clientState_: GetSmartAccountClientResult<Chain | undefined, LightAccount<AlchemySigner>>clientState_;
});
if (let clientState: undefinedclientState == null || let clientState: neverclientState.anyisLoadingClient) {
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stdout
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout
See util.format()
for more information.
log("Loading...");
}
const const client: anyclient = let clientState: undefinedclientState.anyclient;
await const client: anyclient.anysendUserOperation({
uo: {
target: string;
data: string;
value: bigint;
}[]uo: [
{
target: stringtarget: "0xtarget",
data: stringdata: "0x",
value: bigintvalue: 0n,
},
{
target: stringtarget: "0xtarget",
data: stringdata: "0x",
value: bigintvalue: 0n,
},
],
});
import { const createConfig: (params: CreateConfigProps) => AlchemyAccountsConfigCreates an AlchemyAccountsConfig object that can be used in conjunction with the actions exported from @account-kit/core
.
The config contains core and client stores that can be used to manage account state in your application.
createConfig } from "@account-kit/core";
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, const sepolia: Chainsepolia } from "@account-kit/infra";
export const const config: AlchemyAccountsConfigconfig = function createConfig(params: CreateConfigProps): AlchemyAccountsConfigCreates an AlchemyAccountsConfig object that can be used in conjunction with the actions exported from @account-kit/core
.
The config contains core and client stores that can be used to manage account state in your application.
createConfig({
transport: AlchemyTransporttransport: 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" }),
chain: Chainchain: const sepolia: Chainsepolia,
// optional if you want to sponsor gas
policyId?: string | string[] | undefinedpolicyId: "YOUR_POLICY_ID",
});