Using Transaction Bundles
Transaction bundles are DuckyDux’s core security feature that allows you to group multiple transactions together and execute them atomically through private channels to block builders.
DuckyDux Advantage: All bundles bypass the public mempool and go directly to block builders, providing complete MEV protection for your transactions.
Why Use Bundles?
Atomicity
Bundles ensure “all or nothing” execution, critical for multi-step DeFi operations where partial execution could result in loss of funds.
MEV Protection
Security Critical: Never send approval transactions separately! This exposes your trading intent to MEV bots who can front-run your swap.
DuckyDux bundles provide three layers of MEV protection:
- Private mempool: Transactions never appear in public mempool
- Direct builder submission: Bypasses public relayers
- Atomic execution: No opportunity for sandwich attacks
Gas Sponsorship
DuckyDux’s unique sponsorship model allows you to pay gas fees on behalf of users, enabling truly gasless experiences.
Bundle Types
Regular Bundle
Regular Bundle: Standard atomic execution with user-paid gas
{
"txs": [
"0x02f87...", // Raw signed transaction 1
"0x02f87..." // Raw signed transaction 2
]
}Complete Bundle Workflow
Get Quote with Bundle Preparation
When getting a swap quote, always include check_approvals=true to get all necessary transactions:
JavaScript
const response = await fetch('https://api.duckydux.com/v1/swap/quote?' + new URLSearchParams({
sources: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
sinks: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
supplies: '1000000000', // 1000 USDC
demands: '-1',
slippage: '0.5',
sender: userAddress,
check_approvals: 'true' // Critical for bundle preparation
}));
const quote = await response.json();
Sign All Transactions
The API response provides all the necessary transactions with sequential nonces and gas parameters pre-populated. Sign each transaction as provided.
const signedTxs = []
// Sign approval transaction (if needed)
if (quote.approvals.length > 0) {
const signedApproval = await web3.eth.accounts.signTransaction(quote.approvals[0].tx, privateKey)
signedTxs.push(signedApproval.rawTransaction)
}
// Sign swap transaction(s)
for (const tx of quote.txs) {
const signedSwap = await web3.eth.accounts.signTransaction(tx, privateKey)
signedTxs.push(signedSwap.rawTransaction)
}Submit the Bundle
Regular Bundle
// Submit regular bundle
const bundleResponse = await fetch('https://api.duckydux.com/v1/bundles/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
txs: signedTxs
})
});
const bundleResult = await bundleResponse.json();
console.log('Bundle ID:', bundleResult.bundleId);
Monitor Bundle Status
Track your bundle’s progress through the execution pipeline:
const bundleId = bundleResult.bundleId
// Poll for bundle status
const checkStatus = async () => {
const statusResponse = await fetch(`https://api.duckydux.com/v1/bundles/${bundleId}`)
const status = await statusResponse.json()
console.log('Status:', status.status)
console.log('Block:', status.blockNumber)
return status
}
// Poll until bundle is included or fails
const pollStatus = async () => {
let status
do {
status = await checkStatus()
if (status.status === 'pending') {
await new Promise(resolve => setTimeout(resolve, 2000))
}
} while (status.status === 'pending')
return status
}Bundle Status Lifecycle
| Status | Description | Action Required |
|---|---|---|
pending | Bundle received and is awaiting processing. | Wait and poll for status updates. |
submitted | Bundle has been sent to block builders for inclusion. | Continue polling. |
included | Bundle was successfully included in a block. | Your transaction is complete. |
failed | An error occurred before the bundle could be included. | Check the error message for details and resubmit if necessary. |
expired | The bundle was not included in its target block and has expired. | Resubmit the bundle, potentially with a higher gas fee. |
Note on Reverts: Because bundles are executed atomically, a transaction revert will cause the
entire bundle to fail before inclusion. You will not see a reverted status; instead, the bundle
will be marked as failed.
Best Practices
Pro Tip: Always bundle approval + swap transactions for ERC20 swaps to prevent MEV attacks and ensure atomic execution.
Security Guidelines
- Never send approvals separately - Always bundle with the actual swap
- Use consecutive nonces - Ensure transaction ordering
- Set appropriate gas limits - Account for all transactions in bundle
- Monitor bundle status - Don’t assume success without confirmation
Gas Optimization
Regular Bundle
- Use moderate gas prices to ensure inclusion - Bundle multiple operations to amortize costs - Consider gas price at bundle submission time
Advanced Use Cases
Arbitrage Bundles
Execute cross-DEX arbitrage with atomic protection:
// Bundle: Buy on DEX A, Sell on DEX B
const bundleTxs = [
signedBuyTx, // Buy token on Uniswap
signedSellTx, // Sell token on Curve
]
await submitBundle({ txs: bundleTxs })DeFi Strategies
Complex multi-step operations:
// Bundle: Approve + Deposit + Stake
const strategyBundle = [
signedApproveTx, // Approve token spending
signedDepositTx, // Deposit to protocol
signedStakeTx, // Stake LP tokens
]Error Handling
Common bundle failures and solutions: - Nonce gaps: Ensure consecutive nonces - Gas estimation: Account for all transactions - Slippage: Use appropriate slippage for volatile markets
try {
const result = await submitBundle(bundleData)
if (result.status === 'failed') {
// Handle specific failure reasons
if (result.error.includes('nonce')) {
// Resubmit with correct nonces
} else if (result.error.includes('gas')) {
// Increase gas limits
}
}
} catch (error) {
console.error('Bundle submission failed:', error)
}