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
prepareDepositTransactions
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.
Parameters
- amount:
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
});
}
prepareApproveTokensTransaction
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.
Parameters
- amount:
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
prepareCreateChannelTransaction
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.
Parameters
- params:
[CreateChannelParams](../types.md#2-channel-creation)
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
});
prepareDepositAndCreateChannelTransactions
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.
Parameters
- depositAmount:
bigint
- params:
[CreateChannelParams](../types.md#2-channel-creation)
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
prepareCheckpointChannelTransaction
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.
Parameters
- params:
[CheckpointChannelParams](../types.md#3-channel-operations)
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
});
prepareChallengeChannelTransaction
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.
Parameters
- params:
[ChallengeChannelParams](../types.md#3-channel-operations)
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
});
prepareResizeChannelTransaction
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.
Parameters
- params:
[ResizeChannelParams](../types.md#3-channel-operations)
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
prepareCloseChannelTransaction
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.
Parameters
- params:
[CloseChannelParams](../types.md#4-channel-closing)
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
prepareWithdrawalTransaction
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.
Parameters
- amount:
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
Batch Related Operations
Use prepareDepositAndCreateChannelTransactions
to batch deposit and channel creation into a single user operation.
Handle Approvals
For ERC20 tokens, prepareDepositTransactions
will include an approval transaction if needed. Always process all returned transactions.
State Signing
Even when using Account Abstraction, state signatures are handled separately using the stateWalletClient
(or walletClient
if not specified).
Error Handling
The preparation methods throw the same errors as their execution counterparts, so use the same error handling patterns.
Check Token Allowances
Before preparing token operations, you can check if approval is needed:
const allowance = await client.getTokenAllowance();
if (allowance < amount)
Gas Estimation
When using Account Abstraction, gas estimation is typically handled by the AA provider, but you can request estimates if needed.
Limitations
- 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.