Skip to main content

How to Use Flashblocks in Your App

Updated on
Aug 20, 2025

9 min read

Overview

Slow transaction confirmation times can be a major setback for user experience in decentralized applications. Base Flashblocks offer a powerful solution, providing transaction preconfirmations in as little as 200 milliseconds—up to 10 times faster than Base's standard 2-second block time. This technology breaks traditional blocks into mini-blocks that stream every 200 milliseconds, providing near-instant transaction feedback while maintaining cryptographic security.

In this guide, we’ll build a visual comparison dApp that demonstrates this speed difference in action using QuickNode's Flashblocks-enabled endpoints. Here's how the app and console outputs look:

Flashblocks Demo

Explore More in Our Sample App Library

This sample app is also featured in the QuickNode Sample App Library. Check it out for more ready-to-use Web3 projects, tools, and inspiration to kickstart your next build!

What You Will Learn


  • How Flashblocks work at a protocol level
  • Which RPC methods are supported to access Flashblocks data
  • How to integrate Flashblocks in your app using Viem and Wagmi
  • Considerations for implementing Flashblocks in production

What You Will Need


  • A QuickNode account with Base Sepolia endpoint enabled
  • Basic knowledge of React and TypeScript
  • Node.js installed on your machine
  • A browser wallet (MetaMask, Rabby, or similar)
  • Small amount of Base Sepolia ETH for testing (get testnet ETH from QuickNode's faucet)
  • Project ID from Reown (formerly WalletConnect) for wallet connectivity

Understanding Flashblocks

Flashblocks fundamentally change how transactions are processed on Base by providing a preconfirmation layer that operates much faster than traditional block creation. Instead of waiting 2 seconds for a standard L2 block, Flashblocks stream updates every 200 milliseconds, offering a user experience that feels nearly instant.

For a technical deep-dive, we highly recommend watching our detailed video explanation. For the purpose of this guide, we'll cover the core concepts you need to know.

For a technical deep-dive into Flashblocks architecture and implementation details, check out this video.
Subscribe to our YouTube channel for more videos!

How Flashblocks Work

Streaming Preconfirmations

Every 200 milliseconds, a new flashblock is created containing pending transactions. These aren't just status updates – each flashblock represents a cryptographically secure snapshot of the blockchain state at that moment.

Progressive Block Building

Ten flashblocks progressively accumulate over 2 seconds to form one complete block. This means while the final block still takes 2 seconds to produce, users get 10 confirmation updates during that time instead of waiting for the entire duration.

Flashblocks - Traditional Blocks Source: QuickNode Youtube Video - Flashblocks Explained

Intelligent Gas Allocation

Gas is allocated progressively across these ten flashblocks. The first flashblock has a smaller gas limit (1.4M), which increases with each subsequent one until the full block gas limit is reached in the final flashblock. This allows smaller, time-sensitive transactions to confirm quickly.

Flashblocks - Gas Allocation Source: QuickNode Youtube Video - Flashblocks Explained

Cryptographic Security

Each flashblock includes its own state root, ensuring cryptographic verification at every 200ms interval. This maintains the same security guarantees as traditional blocks while delivering faster feedback.

Final Settlement

After 10 flashblocks accumulate, the complete block is submitted to Ethereum L1 for final settlement, ensuring the same finality guarantee as traditional Base blocks.

This design allows applications to see state changes earlier and update UI quickly without waiting for a full block cycle.

Supported RPC Methods for Flashblocks

Flashblocks integrate seamlessly with existing standard RPC methods, but there are a few differences to be aware of. While some methods must be used with the pending tag, other Flashblocks-enabled methods can be called without it.

MethodDescriptionFlashblocks Usage
eth_getBlockByNumberRetrieves block by numberWith pending, returns the most recent Flashblock
eth_getTransactionReceiptGets transaction receiptReturns receipts for preconfirmed transactions in Flashblocks
eth_getBalanceQueries account balanceWith pending, reflects balance changes from the latest Flashblock
eth_getTransactionCountGets account nonceWith pending, returns the latest nonce from Flashblocks
eth_getTransactionByHashRetrieves transaction by hashReturns the transaction with the specified hash

For a more detailed breakdown of the RPC methods, refer to our Flashblocks documentation.

Important Note on eth_getTransactionReceipt and eth_getTransactionByHash

A key behavior to be aware of when using the eth_getTransactionReceipt and eth_getTransactionByHash methods is that on a Flashblocks-enabled endpoint, these RPC calls will always return the preconfirmed data.

Unlike other Flashblocks-supported methods, these methods do not accept a block tag parameter (like pending or latest). This means you cannot use it to fetch a traditionally confirmed receipt from a Flashblocks endpoint.

QuickNode supports Flashblocks on both the Base Mainnet and the Base Sepolia testnet. In this guide, we will use Base Sepolia as the example, but the process is the same for the Base Mainnet.

Viem and Wagmi Integration for Flashblocks

Your dApp or script needs to be configured correctly with the pending block tag for relevant RPC methods to interact with Flashblocks. Viem offers a few ways to achieve this, and as Wagmi uses Viem under the hood, integrating Flashblocks for your dApp is as simple as configuring the chain.

  • Explicitly for Each Method: You can manually set the blockTag to 'pending' on every individual method call (e.g., client.getBalance({ ..., blockTag: 'pending' })). This offers granular control but can be repetitive.

  • Set on the Client: You can set a default blockTag when creating your Viem client using the experimental_blockTag property (e.g., createPublicClient({ ..., experimental_blockTag: 'pending' })). This applies the setting to all the method calls made with that client instance. See details here.

  • Use a Pre-configured Chain (Recommended): The most convenient approach is to use a chain configuration that already has a pre-confirmation mechanism built-in, like baseSepoliaPreconf. This automatically sets the default block tag to pending without extra configuration.

For this project, we use Viem's baseSepoliaPreconf chain. This configuration automatically ensures that relevant API calls fetch data from the latest flashblock rather than the last finalized block.

Automatic Block Tag Behavior

When using a configuration like baseSepoliaPreconf, the pending block tag is automatically applied as the default for several key Viem Actions, including:


  • call
  • estimateGas
  • getBalance
  • getBlock
  • simulateContract
  • waitForTransactionReceipt
  • watchBlocks

For the latest information, always refer to the official Viem documentation.

In this app, we will run two client instances, one for the traditional blocks and one for the flashblocks. Then, we'll use the getBlock method to see when the transaction is included in a block, and the getBalance method to fetch the updated balance.

Setting Up the Flashblocks App

Let's set up the development environment and get the comparison dApp running locally.

Prerequisites Configuration

Step 1: Create a QuickNode Endpoint


  1. Sign up for a QuickNode account
  2. Create a new endpoint for Base Sepolia testnet
  3. Copy your HTTPS endpoint URL from the dashboard

Step 2: Set Up Reown (WalletConnect)


  1. Visit Reown Cloud and create a new project
  2. Name your project (e.g., "Flashblocks Demo")
  3. Copy your Project ID from the project dashboard

Project Installation

Step 1: Clone and Navigate

git clone https://github.com/quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/sample-dapps/flashblocks-base

Step 2: Install Dependencies

pnpm install
# or
npm install
# or
yarn install

Step 3: Configure Environment Variables

Copy the example environment file:

cp .env.example .env.local

Edit .env.local with your credentials:

NEXT_PUBLIC_QUICKNODE_ENDPOINT=your_quicknode_endpoint_here
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id_here

Step 4: Start Development Server

pnpm dev
# or
npm run dev
# or
yarn dev

Navigate to http://localhost:3000 to see the application running.

Building the Flashblocks App

The Flashblocks app is ready. Now let's explore the core implementation details that make the side-by-side comparison work.

Step 1: Configure Viem Clients

The core of the comparison logic lies in using two distinct Viem publicClient instances. You’ll find this configuration in the /lib/clients.ts file. The key to this setup is the difference between the Viem chain configurations we use for each client: baseSepoliaPreconf and baseSepolia.

  • baseSepoliaPreconf (For Flashblocks): This is a special configuration provided by Viem designed specifically for Flashblocks. This chain configuration has a pre-confirmation mechanism and thus, Viem automatically sets the default block tag for RPC requests to pending.

  • baseSepolia (For Traditional Blocks): This is the standard configuration for the Base Sepolia testnet. It uses the default block tag of latest.

// lib/clients.ts
import { createPublicClient, http } from 'viem'
import { baseSepolia, baseSepoliaPreconf } from 'viem/chains'

const QUICKNODE_ENDPOINT = process.env.NEXT_PUBLIC_QUICKNODE_ENDPOINT!

// Flashblocks client automatically uses "pending" for supported actions
export const flashblocksClient = createPublicClient({
chain: baseSepoliaPreconf,
transport: http(QUICKNODE_ENDPOINT),
})

// Traditional client uses "latest" by default
export const traditionalClient = createPublicClient({
chain: baseSepolia,
transport: http(QUICKNODE_ENDPOINT),
})

Step 2: Fetch Balances

We’ll fetch the initial balances from both clients and display them in the app. This is done in the use-balance-tracker.ts hook.

// hooks/use-balance-tracker.ts
// ...
const client =
clientType === 'flashblocks' ? flashblocksClient : traditionalClient
const newBalance = await client.getBalance({ address })
setBalance(newBalance)
// ...

Step 3: Implement Transaction Tracking

The transaction confirmation strategy is managed in the use-transaction-tracking.ts hook. For this demonstration, instead of using Viem's waitForTransactionReceipt, we implement a custom polling mechanism to highlight the difference in block availability.

Here’s the flow:


  1. When a user sends a transaction, its hash is captured.
  2. The application starts two parallel polling loops, one for each client (Flashblocks and Traditional).
  3. Each loop calls getBlock every 100 milliseconds (a hypothetical interval)
  4. The loop checks if the transaction hash exists in the block's transactions array.
  5. Once the transaction is found, the time taken is recorded and displayed as the confirmation time.
  6. Lastly, the latest balance is fetched from each client and displayed.

This polling implementation is designed to demonstrate the concept effectively by logging each trial to the console. For production applications, you should use a more robust method like waitForTransactionReceipt to ensure transaction finality.

Flashblocks Console Logs

Here's the partial code for the polling loop:

// hooks/use-transaction-tracking.ts
// ...
try {
// Get the latest block with transactions
const block = await client.getBlock({
includeTransactions: true,
})

console.log(
`[${clientName}] Checking block ${block.number} (${block.hash}). Attempt: ${attempts}`
)

// Check if our transaction hash is in the block's transactions
const txInBlock = block.transactions.find((tx: any) => tx.hash === hash)

if (txInBlock) {
confirmed = true
confirmationTime = Date.now() - startTime
// Get the full receipt for additional data
receipt = await client.getTransactionReceipt({ hash })

console.log(`[${clientName}] Tx ${hash} confirmed in block ${block.number}`)
break
}

// Wait before next attempt
await new Promise(resolve => setTimeout(resolve, 100))
} catch (blockError) {
console.warn(`[${clientName}] Block fetch attempt failed:`, blockError)
// Continue trying
await new Promise(resolve => setTimeout(resolve, 100))
}
// ...

Testing the Application

Now let's see the Flashblocks in action. Follow these steps to test the application:

  1. Connect Your Wallet: Make sure your development server is running. Click the Connect Wallet button in the app and approve the connection in your browser wallet. Ensure you are connected to the Base Sepolia network.

  2. Get Test ETH: If you need Base Sepolia ETH, visit QuickNode's Base Sepolia Faucet and enter your wallet address. You'll receive test ETH for transactions.

  3. Send a Test Transaction: Click Send 0.0001 ETH Test Transaction and confirm the transaction in your wallet. You can watch both panels for confirmation updates.

You should observe the Flashblocks panel (left) updating faster than the traditional panel (right). The updates should be near-instantaneous. You can also check the console logs for the polling mechanism.

A Note on Confirmation Speed

Occasionally, you might notice the Flashblocks panel does not display a confirmation time for a sent transaction. This can happen because Flashblocks are so fast that the transaction may be included in a block before the application's simple polling loop begins to check for it.

This is a nuance of the demonstration's getBlock polling method. In a production application, using a more robust function like waitForTransactionReceipt would reliably capture the transaction confirmation.

Flashblocks in Your App - Demo

Implementation Considerations

The techniques used in this guide are designed to clearly demonstrate the speed of Flashblocks. When building a production application, keep the following considerations in mind:

  1. Use waitForTransactionReceipt for Production Code: The manual polling with getBlock in our demo is for visualization. In a real-world dApp, you must use Viem's waitForTransactionReceipt function. It is more efficient, robust, and optimized for production use, handling retries and polling internally. Also, use the confirmations flag if you need to wait for a specific number of confirmations.

  2. Distinguish Between Preconfirmation and Finality: Flashblocks provide a high-confidence preconfirmation in milliseconds. This is great for user experience. However, it is not the same as final on-chain settlement. For critical transactions, you can wait for the transaction to be fully finalized in a subsequent block to protect against rare cases like block reorganizations.

  3. Optimize Your Data Fetching: Our demo pools every 100ms to see the effect of Flashblocks. You can choose polling intervals in relevant actions based on your use case. Also, for applications requiring the absolute lowest latency updates, you can consider using a WebSocket (WSS) connection. Note that Flashblocks are not supported yet by WSS connections on QuickNode.

Conclusion

You learned how Flashblocks work, how to configure Viem to use them via pre-defined chains, and how to build a comparison dApp to see the benefits firsthand. By integrating Flashblocks with QuickNode, you can significantly enhance your dApp's user experience, providing the speed and responsiveness that users expect.

Further Resources


We would love to hear more about what you are building. Drop us a line in Discord, or give us a follow on X to stay up to date on all the latest information!

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this guide