본문으로 건너뛰기

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

  1. Soft Finality: ~2 seconds (sequencer confirmation)
  2. Safe Finality: ~12 seconds (published to L1)
  3. 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:

L2 Standard Bridge

// L2 Standard Bridge: 0x4200000000000000000000000000000000000010
const L2_STANDARD_BRIDGE = '0x4200000000000000000000000000000000000010';

// Bridge tokens to L1
const bridgeToL1 = async (tokenAddress, amount, recipient) => {
const bridge = new ethers.Contract(L2_STANDARD_BRIDGE, bridgeABI, signer);

const tx = await bridge.withdrawTo(
tokenAddress,
recipient,
amount,
20000,
'0x'
);

return tx.hash;
};

L2 Cross Domain Messenger

// L2 Cross Domain Messenger: 0x4200000000000000000000000000000000000007
const L2_MESSENGER = '0x4200000000000000000000000000000000000007';

// Send message to L1
const sendMessageToL1 = async (target, data) => {
const messenger = new ethers.Contract(L2_MESSENGER, messengerABI, signer);

const tx = await messenger.sendMessage(target, data, 1000000);
return tx.hash;
};

L2 Block Context

// L2 to L1 Message Passer: 0x4200000000000000000000000000000000000016
const L2_MESSAGE_PASSER = '0x4200000000000000000000000000000000000016';

// Get L1 block information
const getL1BlockInfo = async () => {
const provider = new ethers.JsonRpcProvider('https://api.blockeden.xyz/base/<your-api-key>');

// L1 block number that this L2 block is based on
const l1BlockNumber = await provider.call({
to: '0x4200000000000000000000000000000000000015', // L1 Block contract
data: '0x64ca5bb6' // l1BlockNumber()
});

return parseInt(l1BlockNumber, 16);
};

Performance Optimizations

Batch Transactions

// Batch multiple operations
const batchOperations = async (operations) => {
const multicallAddress = '0x...; // Multicall contract
const multicall = new ethers.Contract(multicallAddress, multicallABI, signer);

const calls = operations.map(op => ({
target: op.target,
callData: op.data
}));

const tx = await multicall.aggregate(calls, {
gasLimit: 500000
});

console.log(`Batched ${operations.length} operations: ${tx.hash}`);
return tx;
};

Contract Deployment Optimization

// Optimize contract deployment for L2
const deployOptimized = async (bytecode, args) => {
const factory = new ethers.ContractFactory(abi, bytecode, signer);

// Use CREATE2 for deterministic addresses
const salt = ethers.randomBytes(32);
const contract = await factory.deploy(...args, {
gasLimit: 3000000,
gasPrice: ethers.parseUnits('0.001', 'gwei'),
// CREATE2 deployment
salt: salt
});

await contract.waitForDeployment();

const address = await contract.getAddress();
console.log(`Contract deployed to: ${address}`);
console.log(`Deployment cost: ~$0.01`);

return contract;
};

Gas Estimation for L2

// Accurate gas estimation for Base
const estimateL2Gas = async (contract, method, args) => {
try {
// Estimate gas
const gasEstimate = await contract[method].estimateGas(...args);

// Add buffer for L2 (usually not needed)
const gasWithBuffer = gasEstimate * 110n / 100n;

// Get current gas price
const feeData = await contract.provider.getFeeData();

const cost = gasWithBuffer * feeData.gasPrice;

return {
gasLimit: gasWithBuffer,
gasPrice: feeData.gasPrice,
estimatedCost: ethers.formatEther(cost),
estimatedCostUSD: parseFloat(ethers.formatEther(cost)) * 2000 // Assume $2000/ETH
};
} catch (error) {
console.error('Gas estimation failed:', error);
throw error;
}
};

// Usage
const gasInfo = await estimateL2Gas(contract, 'transfer', [recipient, amount]);
console.log(`Estimated cost: ${gasInfo.estimatedCost} ETH (~$${gasInfo.estimatedCostUSD.toFixed(4)})`);

Development Best Practices

L2-Optimized Smart Contracts

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract BaseOptimized {
// Use packed structs to minimize storage
struct UserData {
uint128 balance; // 16 bytes
uint64 timestamp; // 8 bytes
uint32 count; // 4 bytes
uint32 flags; // 4 bytes
// Total: 32 bytes (1 slot)
}

mapping(address => UserData) public users;

// Batch operations to save gas
function batchUpdate(
address[] calldata addresses,
uint128[] calldata balances
) external {
require(addresses.length == balances.length, "Length mismatch");

for (uint i = 0; i < addresses.length; i++) {
users[addresses[i]].balance = balances[i];
users[addresses[i]].timestamp = uint64(block.timestamp);
}
}

// Use events efficiently
event BatchUpdated(address indexed user, uint128 balance);

// Optimize for L2 gas patterns
function efficientTransfer(address to, uint128 amount) external {
UserData storage sender = users[msg.sender];
UserData storage recipient = users[to];

require(sender.balance >= amount, "Insufficient balance");

// Single storage update pattern
sender.balance -= amount;
recipient.balance += amount;

// Single event
emit BatchUpdated(to, amount);
}
}

Error Handling for L2

// L2-specific error handling
class BaseErrorHandler {
static async handleTransaction(txPromise) {
try {
const tx = await txPromise;
const receipt = await tx.wait();

if (receipt.status === 0) {
throw new Error(`Transaction failed: ${tx.hash}`);
}

return {
success: true,
hash: tx.hash,
gasUsed: receipt.gasUsed,
effectiveGasPrice: receipt.effectiveGasPrice,
cost: ethers.formatEther(receipt.gasUsed * receipt.effectiveGasPrice)
};

} catch (error) {
return this.categorizeError(error);
}
}

static categorizeError(error) {
if (error.code === 'INSUFFICIENT_FUNDS') {
return {
success: false,
type: 'INSUFFICIENT_FUNDS',
message: 'Insufficient ETH for gas fees',
suggestion: 'Bridge more ETH to Base'
};
}

if (error.message.includes('gas required exceeds allowance')) {
return {
success: false,
type: 'GAS_LIMIT',
message: 'Gas limit too low',
suggestion: 'Increase gas limit'
};
}

if (error.message.includes('nonce too low')) {
return {
success: false,
type: 'NONCE_ERROR',
message: 'Transaction nonce error',
suggestion: 'Reset account nonce'
};
}

return {
success: false,
type: 'UNKNOWN',
message: error.message,
suggestion: 'Check transaction details and try again'
};
}
}

Monitoring and Analytics

Transaction Analytics

// Monitor Base transaction patterns
class BaseAnalytics {
constructor(apiKey) {
this.provider = new ethers.JsonRpcProvider(`https://api.blockeden.xyz/base/${apiKey}`);
this.metrics = {
totalTxs: 0,
totalGasUsed: 0n,
totalCost: 0n,
avgGasPrice: 0n
};
}

async analyzeBlock(blockNumber) {
const block = await this.provider.getBlock(blockNumber, true);

const blockMetrics = {
number: block.number,
timestamp: new Date(block.timestamp * 1000),
txCount: block.transactions.length,
gasUsed: block.gasUsed,
gasLimit: block.gasLimit,
utilization: Number(block.gasUsed * 100n / block.gasLimit),
baseFeePerGas: block.baseFeePerGas
};

// Analyze individual transactions
const txAnalysis = block.transactions.map(tx => ({
hash: tx.hash,
from: tx.from,
to: tx.to,
value: tx.value,
gasLimit: tx.gasLimit,
gasPrice: tx.gasPrice,
cost: tx.gasLimit * tx.gasPrice
}));

return {
block: blockMetrics,
transactions: txAnalysis
};
}

async getNetworkStats() {
const latest = await this.provider.getBlockNumber();
const block = await this.provider.getBlock(latest);

return {
latestBlock: latest,
blockTime: 2, // ~2 seconds on Base
gasPrice: await this.provider.getFeeData(),
networkUtilization: Number(block.gasUsed * 100n / block.gasLimit)
};
}
}

Real-time Monitoring

// Monitor Base network in real-time
class BaseMonitor {
constructor(apiKey) {
this.ws = new WebSocket(`wss://api.blockeden.xyz/base/${apiKey}`);
this.stats = {
blocksPerMinute: 0,
avgTxsPerBlock: 0,
avgGasUsed: 0,
networkHealth: 'good'
};

this.setupMonitoring();
}

setupMonitoring() {
this.ws.onopen = () => {
console.log('Connected to Base monitoring');
this.subscribeToBlocks();
};

this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.processBlockData(data);
};
}

subscribeToBlocks() {
const subscribe = {
jsonrpc: '2.0',
method: 'eth_subscribe',
params: ['newHeads'],
id: 1
};

this.ws.send(JSON.stringify(subscribe));
}

processBlockData(data) {
if (data.method === 'eth_subscription') {
const block = data.params.result;
const blockNumber = parseInt(block.number, 16);
const gasUsed = parseInt(block.gasUsed, 16);
const gasLimit = parseInt(block.gasLimit, 16);

console.log(`Block ${blockNumber}: ${gasUsed.toLocaleString()} gas used (${((gasUsed/gasLimit)*100).toFixed(1)}%)`);

this.updateStats(block);
}
}

updateStats(block) {
// Update rolling statistics
this.stats.avgGasUsed = parseInt(block.gasUsed, 16);
this.stats.networkHealth = this.calculateHealth(block);

console.log('Network Stats:', this.stats);
}

calculateHealth(block) {
const gasUsed = parseInt(block.gasUsed, 16);
const gasLimit = parseInt(block.gasLimit, 16);
const utilization = gasUsed / gasLimit;

if (utilization < 0.5) return 'excellent';
if (utilization < 0.7) return 'good';
if (utilization < 0.9) return 'moderate';
return 'congested';
}
}

Security Considerations

L2 Security Model

  1. Optimistic Assumptions: Transactions are assumed valid
  2. Challenge Period: 7-day window for fraud proofs
  3. Sequencer Risk: Single point of failure (being decentralized)
  4. Bridge Security: Cross-layer communication risks

Best Practices

// Secure L2 development practices
class BaseSecurityUtils {
// Validate L1 origin for cross-layer messages
static validateL1Origin(messenger, expectedL1Sender) {
return (
msg.sender === messenger &&
messenger.xDomainMessageSender() === expectedL1Sender
);
}

// Safe withdrawal pattern
static async safeWithdraw(bridge, token, amount, recipient) {
// Check withdrawal limits
const dailyLimit = await bridge.dailyWithdrawalLimit(token);
const todayWithdrawn = await bridge.todayWithdrawn(token);

if (todayWithdrawn + amount > dailyLimit) {
throw new Error('Exceeds daily withdrawal limit');
}

// Execute withdrawal
const tx = await bridge.withdraw(token, amount, recipient, 20000, '0x');
return tx;
}

// Monitor for fraud
static async monitorFraud(l2TxHash) {
// Implementation would monitor for fraud proof challenges
console.log(`Monitoring fraud proofs for ${l2TxHash}`);
}
}

Next Steps

  • Explore the Base JSON-RPC API Reference for complete API documentation
  • Learn about Base WebSocket Guide for real-time data streaming
  • Check out the Base API Quick Start Guide for basic integration
  • Visit BlockEden.xyz Dashboard to monitor your usage

Resources