Base L2 Features Guide
Base is built on the OP Stack, providing all the benefits of Layer 2 scaling while maintaining full Ethereum compatibility. This guide covers Layer 2 specific features, optimizations, and best practices for building on Base.
Layer 2 Architecture
OP Stack Foundation
Base leverages Optimism's proven OP Stack technology:
- Optimistic Rollup: Transactions are assumed valid by default
- Fraud Proof System: Invalid transactions can be challenged
- Ethereum Security: Inherits Ethereum's security guarantees
- EVM Compatibility: Run existing Ethereum contracts without modification
Key Components
Ultra-Low Transaction Costs
Gas Fee Structure
Base's Layer 2 architecture dramatically reduces transaction costs:
// Example: Compare gas costs
const estimateBaseCosts = async () => {
const provider = new ethers.JsonRpcProvider('https://api.blockeden.xyz/base/<your-api-key>');
// Simple transfer
const transferGas = 21000;
const gasPrice = await provider.getFeeData();
const baseCost = transferGas * gasPrice.gasPrice;
console.log(`Transfer cost on Base: ${ethers.formatEther(baseCost)} ETH`);
// Typically < \$0.01
// ERC-20 transfer
const erc20Gas = 65000;
const erc20Cost = erc20Gas * gasPrice.gasPrice;
console.log(`ERC-20 transfer cost: ${ethers.formatEther(erc20Cost)} ETH`);
// Typically < \$0.02
// Uniswap V3 swap
const swapGas = 150000;
const swapCost = swapGas * gasPrice.gasPrice;
console.log(`DEX swap cost: ${ethers.formatEther(swapCost)} ETH`);
// Typically < \$0.05
};
L2 Gas Optimization
// Optimize transactions for L2
const optimizeForBase = {
// Use appropriate gas settings
gasLimit: 1000000, // Conservative estimate
gasPrice: ethers.parseUnits('0.001', 'gwei'), // Very low gas price
// Or use EIP-1559
maxFeePerGas: ethers.parseUnits('0.01', 'gwei'),
maxPriorityFeePerGas: ethers.parseUnits('0.001', 'gwei')
};
// Deploy contracts efficiently
const deployContract = async (factory, args) => {
const contract = await factory.deploy(...args, {
gasLimit: 3000000,
gasPrice: ethers.parseUnits('0.001', 'gwei')
});
console.log(`Deployment cost: ~\$0.005`); // Extremely low
return contract;
};
Fast Finality
Finality Types on Base
- Soft Finality: ~2 seconds (sequencer confirmation)
- Safe Finality: ~12 seconds (published to L1)
- Hard Finality: ~7 days (challenge period complete)
// Monitor transaction finality
class FinalityTracker {
constructor(provider) {
this.provider = provider;
}
async trackFinality(txHash) {
console.log(`Tracking finality for ${txHash}`);
// 1. Wait for soft finality (sequencer)
const receipt = await this.provider.waitForTransaction(txHash, 1);
console.log(`✓ Soft finality: Block ${receipt.blockNumber}`);
// 2. Wait for safe finality (L1 publication)
const safeReceipt = await this.provider.waitForTransaction(txHash, 6);
console.log(`✓ Safe finality: ${safeReceipt.confirmations} confirmations`);
// 3. Hard finality (challenge period) - 7 days
console.log(`✓ Hard finality: Available after 7-day challenge period`);
return {
softFinality: receipt.blockNumber,
safeFinality: safeReceipt.blockNumber,
hardFinalityTime: Date.now() + (7 * 24 * 60 * 60 * 1000)
};
}
}
// Usage
const tracker = new FinalityTracker(provider);
const finality = await tracker.trackFinality('0x...');
Cross-Layer Communication
L1 to L2 Deposits
Deposit ETH or tokens from Ethereum to Base:
// Deposit ETH from L1 to L2
const depositETH = async (amount) => {
// L1 Bridge Contract
const l1BridgeAddress = '0x3154Cf16ccdb4C6d922629664174b904d80F2C35';
const l1Bridge = new ethers.Contract(l1BridgeAddress, bridgeABI, l1Signer);
const tx = await l1Bridge.depositETH(20000, '0x', {
value: ethers.parseEther(amount),
gasLimit: 100000
});
console.log(`Deposit transaction: ${tx.hash}`);
// Monitor L2 for deposit completion
await monitorL2Deposit(tx.hash);
};
// Monitor L2 for deposit completion
const monitorL2Deposit = async (l1TxHash) => {
const l2Provider = new ethers.JsonRpcProvider('https://api.blockeden.xyz/base/<your-api-key>');
// Check for deposit events on L2
const filter = {
address: '0x4200000000000000000000000000000000000010', // L2 Bridge
topics: [
'0x...', // Deposit event signature
ethers.zeroPadValue(l1TxHash, 32)
]
};
const logs = await l2Provider.getLogs(filter);
console.log(`Deposit completed on L2: ${logs.length > 0}`);
};
L2 to L1 Withdrawals
Withdraw funds from Base back to Ethereum:
// Initiate withdrawal from L2 to L1
const initiateWithdrawal = async (amount, recipient) => {
const l2BridgeAddress = '0x4200000000000000000000000000000000000010';
const l2Bridge = new ethers.Contract(l2BridgeAddress, bridgeABI, l2Signer);
const tx = await l2Bridge.withdraw(
'0x0000000000000000000000000000000000000000', // ETH
ethers.parseEther(amount),
20000,
'0x',
{
gasLimit: 100000
}
);
console.log(`Withdrawal initiated: ${tx.hash}`);
// Wait for 7-day challenge period
const completionTime = Date.now() + (7 * 24 * 60 * 60 * 1000);
console.log(`Withdrawal available for completion after: ${new Date(completionTime)}`);
return {
l2TxHash: tx.hash,
completionTime: completionTime
};
};
// Complete withdrawal on L1 (after challenge period)
const completeWithdrawal = async (withdrawalData) => {
// This requires the withdrawal proof from L2
const l1BridgeAddress = '0x3154Cf16ccdb4C6d922629664174b904d80F2C35';
const l1Bridge = new ethers.Contract(l1BridgeAddress, bridgeABI, l1Signer);
// Generate withdrawal proof (simplified)
const proof = await generateWithdrawalProof(withdrawalData.l2TxHash);
const tx = await l1Bridge.finalizeWithdrawal(proof, {
gasLimit: 200000
});
console.log(`Withdrawal completed on L1: ${tx.hash}`);
};
Message Passing
L1 to L2 Messages
Send arbitrary data from Ethereum to Base:
// Send message from L1 to L2
const sendL1ToL2Message = async (target, message) => {
const l1MessengerAddress = '0x866E82a600A1414e583f7F13623F1aC5d58b0Afa';
const l1Messenger = new ethers.Contract(l1MessengerAddress, messengerABI, l1Signer);
const tx = await l1Messenger.sendMessage(
target, // L2 contract address
message, // Encoded message data
1000000, // L2 gas limit
{
gasLimit: 200000
}
);
console.log(`L1 to L2 message sent: ${tx.hash}`);
return tx.hash;
};
// Receive message on L2
contract L2Receiver {
address public constant L2_MESSENGER = 0x4200000000000000000000000000000000000007;
modifier onlyFromL1(address l1Sender) {
require(
msg.sender == L2_MESSENGER &&
IL2CrossDomainMessenger(L2_MESSENGER).xDomainMessageSender() == l1Sender,
"Only from L1"
);
_;
}
function receiveMessage(bytes calldata data) external onlyFromL1(authorizedL1Contract) {
// Process the message from L1
processL1Message(data);
}
}
L2 to L1 Messages
Send messages from Base to Ethereum:
// Send message from L2 to L1
const sendL2ToL1Message = async (target, message) => {
const l2MessengerAddress = '0x4200000000000000000000000000000000000007';
const l2Messenger = new ethers.Contract(l2MessengerAddress, messengerABI, l2Signer);
const tx = await l2Messenger.sendMessage(
target, // L1 contract address
message, // Encoded message data
1000000, // L1 gas limit
{
gasLimit: 100000
}
);
console.log(`L2 to L1 message sent: ${tx.hash}`);
// Message will be available on L1 after challenge period
return {
l2TxHash: tx.hash,
availableOnL1After: Date.now() + (7 * 24 * 60 * 60 * 1000)
};
};
// Relay message on L1 (after challenge period)
const relayL2ToL1Message = async (messageData) => {
const l1MessengerAddress = '0x866E82a600A1414e583f7F13623F1aC5d58b0Afa';
const l1Messenger = new ethers.Contract(l1MessengerAddress, messengerABI, l1Signer);
// Generate message proof
const proof = await generateMessageProof(messageData.l2TxHash);
const tx = await l1Messenger.relayMessage(...proof, {
gasLimit: 300000
});
console.log(`Message relayed on L1: ${tx.hash}`);
};
Precompiled Contracts
Base includes several precompiled contracts for L2 functionality: