Abstract Accounts
Using NitroliteClient with Account Abstraction
Using with Abstract Accounts
The NitroliteClient
provides special support for ERC-4337 Account Abstraction through the txPreparer
object. This allows dApps using smart contract wallets to prepare transactions without executing them, enabling batching and other advanced patterns.
Transaction Preparer Overview
The txPreparer
is a property of the NitroliteClient
that provides methods for preparing transaction data without sending it to the blockchain. Each method returns one or more PreparedTransaction
objects that can be used with Account Abstraction providers.
import { NitroliteClient } from '@erc7824/nitrolite';
const client = new NitroliteClient({/* config */});
// Instead of: await client.deposit(amount)
const txs = await client.txPreparer.prepareDepositTransactions(amount);
Transaction Preparation Methods
These methods allow you to prepare transactions for the entire channel lifecycle without executing them.
1. Deposit Methods
<MethodDetails
name="prepareDepositTransactions"
description="Prepares deposit transactions for sending tokens to the custody contract. May include an ERC-20 approval transaction if the current allowance is insufficient. Returns an array of transactions that must all be executed for the deposit to succeed."
params={[{ name: "amount", type: "bigint" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)[]>
}
example={`// Prepare deposit transaction(s) - may include ERC20 approval
const txs = await client.txPreparer.prepareDepositTransactions(1000000n);
// For each prepared transaction for (const tx of txs) { await aaProvider.sendUserOperation({ target: tx.to, data: tx.data, value: tx.value || 0n }); }`} />
<MethodDetails
name="prepareApproveTokensTransaction"
description="Prepares a transaction to approve the custody contract to spend ERC-20 tokens. This is useful when you want to separate approval from the actual deposit operation or when implementing a custom approval flow."
params={[{ name: "amount", type: "bigint" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)>
}
example={`// Prepare approval transaction
const tx = await client.txPreparer.prepareApproveTokensTransaction(2000000n);
// Send through your AA provider await aaProvider.sendUserOperation({ target: tx.to, data: tx.data });`} />
2. Channel Creation Methods
<MethodDetails
name="prepareCreateChannelTransaction"
description="Prepares a transaction for creating a new state channel with the specified initial allocation. This transaction calls the custody contract to establish a new channel with the given parameters without executing it immediately."
params={[{ name: "params", type: "CreateChannelParams" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)>
}
example={`// Prepare channel creation transaction
const tx = await client.txPreparer.prepareCreateChannelTransaction({
initialAllocationAmounts: [700000n, 300000n],
stateData: '0x1234'
});
// Send it through your Account Abstraction provider await aaProvider.sendUserOperation({ target: tx.to, data: tx.data, value: tx.value || 0n });`} />
<MethodDetails
name="prepareDepositAndCreateChannelTransactions"
description="Combines deposit and channel creation into a sequence of prepared transactions. This is ideal for batching with Account Abstraction to create a streamlined onboarding experience. Returns an array of transactions that may include token approval, deposit, and channel creation."
params={[
{ name: "depositAmount", type: "bigint" },
{ name: "params", type: "CreateChannelParams" }
]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)[]>
}
example={`// Prepare deposit + channel creation (potentially 3 txs: approve, deposit, create)
const txs = await client.txPreparer.prepareDepositAndCreateChannelTransactions(
1000000n,
{
initialAllocationAmounts: [700000n, 300000n],
stateData: '0x1234'
}
);
// Bundle these transactions into a single UserOperation await aaProvider.sendUserOperation({ userOperations: txs.map(tx => ({ target: tx.to, data: tx.data, value: tx.value || 0n })) });`} />
3. Channel Operation Methods
<MethodDetails
name="prepareCheckpointChannelTransaction"
description="Prepares a transaction to checkpoint a channel state on-chain. This creates an immutable record of the channel state that both parties have agreed to, which is useful for security and dispute resolution."
params={[{ name: "params", type: "CheckpointChannelParams" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)>
}
example={`// Prepare checkpoint transaction
const tx = await client.txPreparer.prepareCheckpointChannelTransaction({
channelId: '0x...',
candidateState: state
});
await aaProvider.sendUserOperation({ target: tx.to, data: tx.data });`} />
<MethodDetails
name="prepareChallengeChannelTransaction"
description="Prepares a transaction to challenge a channel with a candidate state. This is used when the counterparty is unresponsive, allowing you to force progress in the dispute resolution process."
params={[{ name: "params", type: "ChallengeChannelParams" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)>
}
example={`// Prepare challenge transaction
const tx = await client.txPreparer.prepareChallengeChannelTransaction({
channelId: '0x...',
candidateState: state
});
await aaProvider.sendUserOperation({ target: tx.to, data: tx.data });`} />
<MethodDetails
name="prepareResizeChannelTransaction"
description="Prepares a transaction to adjust the total funds allocated to a channel. This allows you to add more funds to a channel that's running low or reduce locked funds when less capacity is needed."
params={[{ name: "params", type: "ResizeChannelParams" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)>
}
example={`// Prepare resize transaction
const tx = await client.txPreparer.prepareResizeChannelTransaction({
channelId: '0x...',
candidateState: state
});
await aaProvider.sendUserOperation({ target: tx.to, data: tx.data });`} />
4. Channel Closing Methods
<MethodDetails
name="prepareCloseChannelTransaction"
description="Prepares a transaction to close a channel on-chain using a mutually agreed final state. This transaction unlocks funds according to the agreed allocations and makes them available for withdrawal."
params={[{ name: "params", type: "CloseChannelParams" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)>
}
example={`// Prepare close channel transaction
const tx = await client.txPreparer.prepareCloseChannelTransaction({
finalState: {
channelId: '0x...',
stateData: '0x...',
allocations: [allocation1, allocation2],
version: 10n,
serverSignature: signature
}
});
await aaProvider.sendUserOperation({ target: tx.to, data: tx.data });`} />
5. Withdrawal Methods
<MethodDetails
name="prepareWithdrawalTransaction"
description="Prepares a transaction to withdraw tokens previously deposited into the custody contract back to the user's wallet. This is the final step in the channel lifecycle, allowing users to reclaim their funds after channels have been closed."
params={[{ name: "amount", type: "bigint" }]}
returns={Promise<[PreparedTransaction](../types.md#preparedtransaction)>
}
example={`// Prepare withdrawal transaction
const tx = await client.txPreparer.prepareWithdrawalTransaction(500000n);
await aaProvider.sendUserOperation({ target: tx.to, data: tx.data });`} />
Understanding PreparedTransaction
The PreparedTransaction
type is the core data structure returned by all transaction preparation methods. It contains all the information needed to construct a transaction or UserOperation:
type PreparedTransaction = {
// Target contract address
to: Address;
// Contract call data
data?: Hex;
// ETH value to send (0n for token operations)
value?: bigint;
};
Each PreparedTransaction
represents a single contract call that can be:
- Executed directly - If you're using a standard EOA wallet
- Bundled into a UserOperation - For account abstraction providers
- Batched with other transactions - For advanced use cases
Integration Examples
The Nitrolite transaction preparer can be integrated with any Account Abstraction provider. Here are examples with popular AA SDKs:
With Safe Account Abstraction SDK
import { NitroliteClient } from '@erc7824/nitrolite';
import { AccountAbstraction } from '@safe-global/account-abstraction-kit-poc';
// Initialize clients
const client = new NitroliteClient({/* config */});
const aaKit = new AccountAbstraction(safeProvider);
// Prepare transaction
const tx = await client.txPreparer.prepareCreateChannelTransaction({
initialAllocationAmounts: [700000n, 300000n],
stateData: '0x1234'
});
// Send through AA provider
const safeTransaction = await aaKit.createTransaction({
transactions: [{
to: tx.to,
data: tx.data,
value: tx.value?.toString() || '0'
}]
});
const txResponse = await aaKit.executeTransaction(safeTransaction);
With Biconomy SDK
import { NitroliteClient } from '@erc7824/nitrolite';
import { BiconomySmartAccountV2 } from "@biconomy/account";
// Initialize clients
const client = new NitroliteClient({/* config */});
const smartAccount = await BiconomySmartAccountV2.create({/* config */});
// Prepare transaction
const txs = await client.txPreparer.prepareDepositAndCreateChannelTransactions(
1000000n,
{
initialAllocationAmounts: [700000n, 300000n],
stateData: '0x1234'
}
);
// Build user operation
const userOp = await smartAccount.buildUserOp(
txs.map(tx => ({
to: tx.to,
data: tx.data,
value: tx.value || 0n
}))
);
// Send user operation
const userOpResponse = await smartAccount.sendUserOp(userOp);
await userOpResponse.wait();
Advanced Use Cases
The transaction preparer is especially powerful when combined with advanced Account Abstraction features.
Batching Multiple Operations
One of the main advantages of Account Abstraction is the ability to batch multiple operations into a single transaction:
// Collect prepared transactions from different operations
const preparedTxs = [];
// 1. Add token approval if needed
const allowance = await client.getTokenAllowance();
if (allowance < totalNeeded) {
const approveTx = await client.txPreparer.prepareApproveTokensTransaction(totalNeeded);
preparedTxs.push(approveTx);
}
// 2. Add deposit
const depositTx = await client.txPreparer.prepareDepositTransactions(amount);
preparedTxs.push(...depositTx);
// 3. Add channel creation
const createChannelTx = await client.txPreparer.prepareCreateChannelTransaction(params);
preparedTxs.push(createChannelTx);
// 4. Execute all as a batch with your AA provider
await aaProvider.sendUserOperation({
userOperations: preparedTxs.map(tx => ({
target: tx.to,
data: tx.data,
value: tx.value || 0n
}))
});
Gas Sponsoring
Account Abstraction enables gas sponsorship, where someone else pays for the transaction gas:
// Prepare transaction
const tx = await client.txPreparer.prepareCreateChannelTransaction(params);
// Use a sponsored transaction
await paymasterProvider.sponsorTransaction({
target: tx.to,
data: tx.data,
value: tx.value || 0n,
user: userAddress
});
Session Keys
Some AA wallets support session keys, which are temporary keys with limited permissions:
// Create a session key with permissions only for specific operations
const sessionKeyData = await aaWallet.createSessionKey({
permissions: [
{
target: client.addresses.custody,
// Only allow specific functions
functionSelector: [
"0xdeposit(...)",
"0xwithdraw(...)"
]
}
],
expirationTime: Date.now() + 3600 * 1000 // 1 hour
});
// Use the session key to prepare and send transactions
const tx = await client.txPreparer.prepareDepositTransactions(amount);
await aaWallet.executeWithSessionKey(sessionKeyData, {
target: tx.to,
data: tx.data,
value: tx.value || 0n
});
Best Practices
Limitations
:::caution Important
- The transaction preparer doesn't handle sequencing or nonce management - that's the responsibility of your AA provider.
- Some operations (like checkpointing) require signatures from all participants, which must be collected separately from the transaction preparation. :::