Building truthchain: My journey building a chrome extension for content verification on the stacks blockchain.

Henry AgukweHenry Agukwe
5 min read

Yeahh, I know my title is too long and that’s because this is a personal project published to help developers integrate chrome extensions to the stack blockchain.

in this write-up I am building an extension which merges traditional web content and stacks blockchain technology.

The problem Truthchain solves?

By creating immutable records of contents on the the blockchain, we make it almost impossible to forge, backdate premium contents, also we provide authenticity for suspected AI generated contents.

Tech stack

in this tutorial we’ll be using:

Frontend: React+Typescript
Blockchain: Stacks blockchain for content anchoring
Storage: IPFS for decentralised content storage
wallet integration: Xverse and Leather wallet

Project structure


```
web_extension/
├── public/
│   ├── manifest.json          
│   └── Your-Icon.jpeg        
├── src/
│   ├── App.tsx               
│   ├── background.ts        
│   ├── content-script.ts    
│   └── lib/
│       ├── ipfs.ts        
│       └── stacks.ts       
```

Errmm…

Due the the time it takes to create a well written tutorial, I’ve decided to split the tutorial into 2 layers(parts).
because stacks is a bitcoin L2 blockchain(hope you got the joke).

follow me on X(twitter) @boy_gene_us for more stacks related contents.

Now lets Begin…

Step-1: setting up the manifest

The manifest.json is the hearbeat of any browser extension, because it defines permissions, capabilities and more. it is like a passport for your browser extension, ittells the browser everything it needs to know about your extension.

```json
{
  "manifest_version": 3,
  "name": "Your-extension-name",
  "version": "1.0.0",
  "description": "your extension description",

  "permissions": [
    "storage",
    "activeTab", 
    "scripting",
    "notifications",
    "tabs"
  ],

  "background": {
    "service_worker": "background.js",
    "type": "module"
  }
}
```

I always advise for the use of manifest V3 as you can see above because it offers better security and performance.

Why Permissions in your manifest.json

### Why These Permissions Matter

Each permission we request has a specific purpose in our blockchain workflow:

**`storage`** - We need this to:
- Save wallet connection data between sessions
- Cache blockchain transaction IDs
- Store content hashes for quick verification
- Remember user preferences

**`activeTab`** - This lets us:
- Extract content from the webpage user is viewing
- Get the page title, URL, and text content
- Inject our floating action buttons

**`scripting`** - Essential for:
- Detecting installed Stacks wallets on web pages
- Injecting our content extraction logic
- Adding our UI elements to existing websites

**`notifications`** - For user feedback:
- "Content successfully bridged!" success messages
- Error notifications when blockchain operations fail
- Progress updates during long operations

note: the mainifest.json file needs to be configured to your needs(based off whatever solution you’re building) so there may be need for other permissions such as “Host permissions” etc.

Step-2: Creating our user Interface

Our App.tsx serves as the popup interface, using react and tailwind create a simple UI

const App = () => {
  const [wallet, setWallet] = useState<WalletState>({
    isConnected: false,
    address: null,
    publicKey: null
  });

  const [content, setContent] = useState<ContentState>({
    isProcessing: false,
    lastAction: null,
    contentCID: null,
    txId: null
  });

  // Wallet connection logic
  const connectXverseWallet = async () => {
    setIsLoading(true);
    try {
      chrome.runtime.sendMessage(
        { action: 'connectXverse' },
        (response) => {
          if (response?.success) {
            setWallet({
              isConnected: true,
              address: response.walletData.address,
              publicKey: response.walletData.publicKey
            });
          }
        }
      );
    } catch (error) {
      console.error('Wallet connection failed:', error);
    }
  };

This code simply:

  • Shows wallet connection first, then blockchain operations

  • Loading states and success indicators

  • Graceful fallbacks and user-friendly error messages

Step-3: Background service worker

This is where we handle our blockchain functionality:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  switch (request.action) {
    case 'connectXverse':
      handleXverseConnection()
        .then(result => sendResponse({ success: true, walletData: result }))
        .catch(error => sendResponse({ success: false, error: error.message }));
      return true;

    case 'truthchain':
      handleContentBridge()
        .then(result => sendResponse({ success: true, data: result }))
        .catch(error => sendResponse({ success: false, error: error.message }));
      return true;
  }
});

Key features implemented:

- Wallet detection: Automatically detects Xverse and Leather wallets
- Content extraction: Pulls text, titles, and metadata from web pages
- Dual-mode operation: Real blockchain transactions with demo mode fallback.

Understanding Service workers vs Background pages


In Manifest V3, Google replaced persistent background pages with service workers. This was a game-changer:

**Old Background Pages (Manifest V2):**
- Ran continuously, consuming memory even when idle
- Could access DOM APIs (but this was often a security risk)
- Easier to program but less efficient

**New Service Workers (Manifest V3):**
- Start up only when needed, then sleep to save resources
- No DOM access (more secure)
- Event-driven architecture (more complex but more efficient)

Step-4: Wallet Connection

So far this has been the most challenging part of building this extension because connecting to blockchain wallets in a browser extension is tricky because wallets inject their APIs into web pages, but our extension runs in an isolated context. after days of research I figure this strategy below:


async function handleXverseConnection(): Promise<WalletData> {
  try {
    // Step 1: Get the current active tab
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

    if (!tab.id) {
      throw new Error('No active tab found');
    }

    // Step 2: Try to communicate with existing content script
    try {
      const response = await chrome.tabs.sendMessage(tab.id, { action: 'detectWallets' });
      console.log('Wallet detection response:', response);

      if (response && (response.xverse || response.leather || response.stacks)) {
        // Step 3: Attempt connection through content script
        const connectionResponse = await chrome.tabs.sendMessage(tab.id, { action: 'connectWallet' });

        if (connectionResponse && connectionResponse.success) {
          const walletData = connectionResponse.wallet;
          // Step 4: Persist wallet data for future sessions
          await chrome.storage.local.set({ walletData });
          return walletData;
        }
      }
    } catch (error) {
      console.log('Content script method failed, trying direct injection:', error);
    }

I know you’re scrolling down hoping to see more, unfortunately this is where I stop for now, will update theis with the following areas in my next tutorial:

  • How to inject wallet detection script directly

  • injecting the wallet detection function into web pages

  • content bridging: extraction and storage of contents to and in the blockchain

  • CSP configuration and more.

  • saving content on the blockchain using IFPS for decentralised storage

Revision

in this tutorial I have shown you how to setup witha basic project structure:

  • permissions and why they are needed in our Manifest.json

  • How to create a basic popup using src/App.tsx

  • Background.js or background service worker

  • and wallet connection to another web extension

0
Subscribe to my newsletter

Read articles from Henry Agukwe directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Henry Agukwe
Henry Agukwe

A chill guy who disguises as a software engineer on somedays and other days a blockchain developer. Primarily focused on creating solution for everyday tools using tech