
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@etherspot/transaction-kit
Advanced tools
The framework-agnostic Etherspot Transaction Kit that makes blockchain transactions feel like a walk in the park! 🌳
Ever felt like blockchain transactions were more complex than explaining quantum physics to a cat? Well, fret no more! TransactionKit is here to turn your transaction woes into smooth sailing. Choose between Etherspot's Modular SDK for traditional smart accounts or cutting-edge EIP-7702 delegated EOAs - this library brings you a delightful, method-chained API that makes sending transactions as easy as ordering coffee. ☕
TransactionKit is designed to work across the entire JavaScript ecosystem:
# Using npm
npm install @etherspot/transaction-kit
# Using yarn
yarn add @etherspot/transaction-kit
# Using pnpm (because we're modern like that)
pnpm add @etherspot/transaction-kit
Here's how to send a simple transaction with the modular smart account - it's easier than making toast! 🍞
import { TransactionKit } from '@etherspot/transaction-kit';
import { createWalletClient, custom } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { polygon } from 'viem/chains';
// Set up your wallet provider (this is just an example)
const account = privateKeyToAccount('0x...your-private-key...');
const client = createWalletClient({
account,
chain: polygon,
transport: custom(window.ethereum!),
});
// Initialize TransactionKit
const kit = TransactionKit({
provider: client,
chainId: 137, // Polygon mainnet
bundlerApiKey: 'your-bundler-api-key', // Optional but recommended
walletMode: 'modular', // Optional: this is the default
});
// Send a transaction - it's that simple!
const sendTransaction = async () => {
try {
// Create and name your transaction
const transaction = kit
.transaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6', // Recipient address
value: '1000000000000000000', // 1 ETH in wei
chainId: 137, // Polygon
})
.name({ transactionName: 'my-first-tx' });
// Estimate the transaction cost
const estimate = await transaction.estimate();
console.log('Transaction cost:', estimate.cost);
// Send the transaction
const result = await transaction.send();
if (result.isSentSuccessfully) {
console.log('🎉 Transaction sent successfully!');
console.log('Transaction hash:', result.userOpHash);
} else {
console.log('❌ Transaction failed:', result.errorMessage);
}
} catch (error) {
console.error('Something went wrong:', error);
}
};
For users who want to use EIP-7702 delegated EOAs:
import { TransactionKit } from '@etherspot/transaction-kit';
// Initialize TransactionKit
const kit = TransactionKit({
chainId: 137, // Polygon mainnet
privateKey: '0x...your-private-key...', // Required for EIP-7702 (either privateKey or viemLocalAccount)
bundlerApiKey: 'your-bundler-api-key', // Optional but recommended
walletMode: 'delegatedEoa', // Required for EIP-7702
});
// Send a transaction with delegated EOA
const sendDelegatedTransaction = async () => {
try {
// Check if EOA is delegated
const isDelegated = await kit.isDelegateSmartAccountToEoa(137);
if (!isDelegated) {
// Delegate EOA to smart account first
await kit.delegateSmartAccountToEoa({
chainId: 137,
delegateImmediately: true,
});
}
// Create and send transaction
const transaction = kit
.transaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: '1000000000000000000', // 1 ETH
chainId: 137,
})
.name({ transactionName: 'delegated-tx' });
const result = await transaction.send();
if (result.isSentSuccessfully) {
console.log('🎉 Delegated EOA transaction sent!');
}
} catch (error) {
console.error('Transaction failed:', error);
}
};
Want to send multiple transactions at once? We've got you covered! 🎯
// Create multiple transactions and add them to a batch
const sendBatchTransactions = async () => {
try {
// First transaction
kit
.transaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: '500000000000000000', // 0.5 ETH
chainId: 137, // Optional but recommended for batched transaction
})
.name({ transactionName: 'tx1' })
.addToBatch({ batchName: 'my-batch' });
// Second transaction
kit
.transaction({
to: '0x1234567890123456789012345678901234567890',
value: '300000000000000000', // 0.3 ETH
chainId: 137, // Optional but recommended for batched transaction
})
.name({ transactionName: 'tx2' })
.addToBatch({ batchName: 'my-batch' });
// Send the entire batch
const result = await kit.sendBatches();
if (result.isSentSuccessfully) {
console.log('🎉 Batch sent successfully!');
Object.entries(result.batches).forEach(([batchName, batchResult]) => {
console.log(`Batch "${batchName}":`, batchResult.userOpHash);
});
}
} catch (error) {
console.error('Batch failed:', error);
}
};
// Update existing transactions
const updateTransaction = () => {
const namedTx = kit.name({ transactionName: 'my-tx' });
// Update the transaction details
namedTx
.transaction({
to: '0xNewAddress123456789012345678901234567890',
value: '2000000000000000000', // 2 ETH
chainId: 137, // Optional
})
.update();
};
// Remove transactions or batches
const cleanup = () => {
// Remove a specific transaction
kit.name({ transactionName: 'my-tx' }).remove();
// Remove an entire batch
kit.batch({ batchName: 'my-batch' }).remove();
};
// Get wallet address
const getWalletAddress = async () => {
const address = await kit.getWalletAddress(137); // Polygon
console.log('Your wallet address:', address);
};
// Enable debug mode for troubleshooting
kit.setDebugMode(true);
For EIP-7702 specific functionalities:
// Initialize with delegated EOA mode
const kit = TransactionKit({
chainId: 137, // Polygon
privateKey: '0x...your-private-key...', // Required for EIP-7702 (either privateKey or viemLocalAccount)
bundlerApiKey: 'your-bundler-api-key',
walletMode: 'delegatedEoa',
});
// Check if EOA is delegated to a smart account
const isDelegated = await kit.isDelegateSmartAccountToEoa(137);
console.log('Is EOA delegated:', isDelegated);
// Delegate EOA to smart account (if not already delegated)
if (!isDelegated) {
const delegationResult = await kit.delegateSmartAccountToEoa({
chainId: 137,
delegateImmediately: true, // Set to false to get the authorization object and not execute
});
console.log('Delegation result:', delegationResult);
console.log('EOA Address:', delegationResult.eoaAddress);
console.log('Delegate Address:', delegationResult.delegateAddress);
console.log('Already installed:', delegationResult.isAlreadyInstalled);
}
// Send transactions using delegated EOA
const sendWithDelegatedEoa = async () => {
const transaction = kit
.transaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: '1000000000000000000', // 1 ETH
chainId: 137,
})
.name({ transactionName: 'delegated-tx' });
const result = await transaction.send();
if (result.isSentSuccessfully) {
console.log('🎉 Delegated EOA transaction sent!');
console.log('UserOp Hash:', result.userOpHash);
}
};
// Remove delegation (if needed)
const removeDelegation = async () => {
const undelegationResult = await kit.undelegateSmartAccountToEoa({
chainId: 137,
delegateImmediately: true, // Set to false to get the authorization object and not execute
});
console.log('Undelegation result:', undelegationResult);
};
You can perform EOA delegation and a transaction atomically by passing a freshly signed authorization to estimate(), send(), estimateBatches(), or sendBatches().
// 1) Get a signed authorization without executing the installation
const { authorization } = await kit.delegateSmartAccountToEoa({
chainId: 137,
delegateImmediately: false,
});
if (!authorization) {
// Already delegated, proceed without the authorization parameter
}
// 2) Create and name a transaction on the SAME chain as authorization.chainId
kit
.transaction({
to: '0x000000000000000000000000000000000000dEaD',
value: '100000000000000',
chainId: authorization?.chainId ?? 137,
})
.name({ transactionName: 'txWithAuth' });
// 3) Estimate or send by passing the authorization (delegation will be executed within the UserOp)
const named = kit.name({ transactionName: 'txWithAuth' });
const estimate = await named.estimate({ authorization });
const result = await named.send({ authorization });
// 1) Get a signed authorization without executing the installation
const { authorization } = await kit.delegateSmartAccountToEoa({
chainId: 137,
delegateImmediately: false,
});
if (!authorization) {
// Already delegated, proceed without the authorization parameter
}
// 2) Create transactions on the SAME chain as authorization.chainId
kit
.transaction({
to: '0x000000000000000000000000000000000000dEaD',
value: '100000000000000',
chainId: authorization?.chainId ?? 137,
})
.name({ transactionName: 'batch-tx1' })
.addToBatch({ batchName: 'auth-batch' });
kit
.transaction({
to: '0x000000000000000000000000000000000000beef',
value: '200000000000000',
chainId: authorization?.chainId ?? 137,
})
.name({ transactionName: 'batch-tx2' })
.addToBatch({ batchName: 'auth-batch' });
// 3) Estimate or send batches by passing the authorization (delegation will be executed within the UserOp)
const estimate = await kit.estimateBatches({ authorization });
const result = await kit.sendBatches({ authorization });
Notes:
authorization must match the transaction chainId and Kernel v3.3 implementation.authorization will only be applied to chain groups matching the authorization's chainId.authorization is only supported in delegatedEoa mode; using it in modular mode will cause validation errors.authorization is not required.Enhanced batch operations with chain-based grouping:
// Create transactions across multiple chains
const multiChainBatches = async () => {
// Ethereum transaction
kit
.transaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: '1000000000000000000', // 1 ETH
chainId: 1, // Ethereum
})
.name({ transactionName: 'eth-tx' })
.addToBatch({ batchName: 'multi-chain-batch' });
// Polygon transaction
kit
.transaction({
to: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
value: '500000000000000000', // 0.5 ETH
chainId: 137, // Polygon
})
.name({ transactionName: 'poly-tx' })
.addToBatch({ batchName: 'multi-chain-batch' });
// Estimate costs across all chains
const estimates = await kit.estimateBatches();
console.log('Multi-chain estimates:', estimates);
// Send batches (automatically grouped by chain)
const result = await kit.sendBatches();
if (result.isSentSuccessfully) {
console.log('🎉 Multi-chain batch sent successfully!');
Object.entries(result.batches).forEach(([batchName, batchResult]) => {
console.log(`Batch "${batchName}":`, batchResult.userOpHash);
});
}
};
TransactionKit supports two wallet modes to suit different use cases:
Smart account functionality with Etherspot's Modular SDK:
const kit = TransactionKit({
provider: yourWalletProvider, // Required: Your wallet provider
chainId: 137, // Required: Default chain ID
bundlerApiKey: 'your-api-key', // Optional: For better performance
bundlerUrl: 'https://your-bundler-url.com', // Optional: Custom bundler URL
bundlerApiKeyFormat: '?api-key=', // Optional: API key format (default: '?api-key=')
debugMode: false, // Optional: Enable debug logging
walletMode: 'modular', // Optional: Default wallet mode
});
Advanced EIP-7702 functionality with delegated Externally Owned Accounts:
const kit = TransactionKit({
chainId: 137, // Required: Default chain ID
privateKey: '0x...your-private-key...', // Required for EIP-7702 (either privateKey or viemLocalAccount)
bundlerApiKey: 'your-api-key', // Optional: For better performance
bundlerUrl: 'https://your-bundler-url.com', // Optional: Custom bundler URL
bundlerApiKeyFormat: '?api-key=', // Optional: API key format (default: '?api-key=')
debugMode: false, // Optional: Enable debug logging
walletMode: 'delegatedEoa', // Required: Delegated EOA mode
});
Note: In delegated EOA mode, you don't need to provide a provider. You can provide either a privateKey or a viemLocalAccount (a LocalAccount from viem) directly, but not both. The viemLocalAccount option is useful when you already have a LocalAccount object and want to bypass the privateKey conversion.
| Feature | Modular Mode | Delegated EOA Mode |
|---|---|---|
| Account Type | Etherspot Smart Account | EIP-7702 Delegated EOA |
| Provider Required | ✅ Yes (wallet provider) | ❌ No (uses privateKey or viemLocalAccount) |
| Private Key/Owner Account Required | ❌ No | ✅ Yes (either privateKey or viemLocalAccount) |
| Client-Side Safe | ✅ Yes | ⚠️ Depends on private key/account handling |
| Paymaster Support | ✅ Full support | ⚠️ Not yet supported |
| UserOp Overrides | ✅ Supported | ⚠️ Not yet supported |
| EIP-7702 Methods | ❌ Not available | ✅ Yes |
| Modular SDK Integration | ✅ Yes | ❌ No |
| ZeroDev Integration | ❌ No | ✅ Yes integration |
TransactionKit includes a BundlerConfig class for flexible bundler URL management:
import { BundlerConfig } from '@etherspot/transaction-kit';
// Basic bundler config with API key
const bundlerConfig = new BundlerConfig(
137, // chainId
'your-api-key' // API key
);
console.log('Bundler URL:', bundlerConfig.url);
// Custom bundler URL with API key
const customBundlerConfig = new BundlerConfig(
137,
'your-api-key',
'https://your-custom-bundler.com', // custom URL
'?apikey=' // custom API key format
);
// Different API key formats
const pathFormat = new BundlerConfig(
137,
'your-api-key',
'https://bundler.example.com',
'/api-key/' // Results in: https://bundler.example.com/api-key/your-api-key
);
const queryFormat = new BundlerConfig(
137,
'your-api-key',
'https://bundler.example.com',
'&key=' // Results in: https://bundler.example.com&key=your-api-key
);
Access underlying clients for advanced operations:
// Get viem clients (delegatedEoa mode only)
if (kit.getEtherspotProvider().getWalletMode() === 'delegatedEoa') {
const publicClient = await kit.getPublicClient(137);
const walletClient = await kit.getWalletClient(137);
const bundlerClient = await kit.getBundlerClient(137);
// Get account instances (delegatedEoa mode only)
const delegatedEoaAccount = await kit.getDelegatedEoaAccount(137);
const viemLocalAccount = await kit.getOwnerAccount(137);
}
// Get transaction hash from userOp hash (available in both modes)
const txHash = await kit.getTransactionHash(
'0x123...userOpHash',
137, // txChainId
30000, // timeout (optional)
1000 // retry interval (optional)
);
transaction() - Create a new transactionname() - Name a transaction for later referencebatch() - Create a batch for multiple transactionsaddToBatch() - Add a transaction to a batchestimate() - Estimate transaction costsend() - Send a single transactionestimateBatches() - Estimate batch costs with multi-chain supportsendBatches() - Send all batches with chain groupingisDelegateSmartAccountToEoa() - Check if EOA is delegated to a smart accountdelegateSmartAccountToEoa() - Delegate EOA to smart accountundelegateSmartAccountToEoa() - Remove EOA delegationWhen calling delegateSmartAccountToEoa({ delegateImmediately: false }), the method returns an authorization object that can be passed to estimate({ authorization }), send({ authorization }), estimateBatches({ authorization }), and sendBatches({ authorization }) for atomic delegation-and-execution flows.
getPublicClient() - Get viem PublicClient for a chaingetBundlerClient() - Get bundler client for account abstractiongetWalletClient() - Get viem WalletClient for a chaingetDelegatedEoaAccount() - Get delegated EOA account instancegetOwnerAccount() - Get the owner EOA accountgetWalletAddress() - Get your wallet addressgetTransactionHash() - Get transaction hash from userOp hashgetState() - Get current kit statesetDebugMode() - Enable/disable debug loggingreset() - Clear all transactions and batchesgetProvider() - Get the underlying EtherspotProvider instancegetEtherspotProvider() - Get the EtherspotProvider instance directlygetSdk() - Get the Modular SDK instance for a specific chain (modular mode only)remove() - Remove a named transaction or batchupdate() - Update an existing named transaction or batched transactionWhen using walletMode: 'delegatedEoa', you must provide either a privateKey or a viemLocalAccount (LocalAccount from viem). Here are important security considerations:
⚠️ Never expose private keys or owner accounts in client-side code or logs!
// ❌ BAD: Hardcoded private key
const kit = TransactionKit({
privateKey: '0x1234567890abcdef...', // NEVER DO THIS!
walletMode: 'delegatedEoa',
});
// ✅ GOOD: Use environment variables
const kit = TransactionKit({
privateKey: process.env.PRIVATE_KEY, // Server-side only
walletMode: 'delegatedEoa',
});
// ✅ GOOD: Use secure key management (works with both privateKey and viemLocalAccount)
const kit = TransactionKit({
privateKey: await getSecurePrivateKey(), // From secure storage
walletMode: 'delegatedEoa',
});
Best Practices:
viemLocalAccount, ensure the underlying private key is handled securely@zerodev/sdk package@etherspot/modular-sdk packageTransactionKit includes comprehensive network constants and supports multiple blockchain networks:
// Access network configurations
import { getNetworkConfig } from '@etherspot/transaction-kit';
// Get network configuration for a specific chain
const networkConfig = getNetworkConfig(137); // Polygon
console.log('Chain ID:', networkConfig.chainId);
console.log('Bundler/RPC:', networkConfig.bundler); // Etherspot bundler endpoint
console.log('Chain Object:', networkConfig.chain); // viem Chain object
console.log('Entry Point:', networkConfig.contracts.entryPoint);
console.log('Wallet Factory:', networkConfig.contracts.walletFactory);
The library automatically handles network-specific configurations for Etherspot bundler endpoints, smart contract addresses, and viem chain objects.
We love contributions! Whether it's fixing a bug, adding a feature, or improving the documentation, every contribution is welcome. Check out our Contributing Guide to get started.
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by the Etherspot team
Now go forth and build amazing things! The blockchain is your oyster! 🦪
FAQs
Framework-agnostic Etherspot Transaction Kit
The npm package @etherspot/transaction-kit receives a total of 78 weekly downloads. As such, @etherspot/transaction-kit popularity was classified as not popular.
We found that @etherspot/transaction-kit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.