Bridging MetaMask to the AO Network
back to blog

Bridging MetaMask to the AO Network: A Journey of Discovery

β€’5 min read
arweave
ethereum
metamask
ao-network
web3

How we cracked the code to connect Ethereum wallets with Arweave's compute layer

The Challenge

Picture this: You're building a decentralized game on the AO Network - Arweave's groundbreaking compute layer. Your users have MetaMask wallets full of ETH and tokens, but AO requires Arweave wallets. Do you force them to create new wallets? Transfer funds? Remember new seed phrases?

We said no. There had to be a better way.

The Quest Begins

Code editor showing React component structure

Code editor showing React component structure

Our journey started with a simple question: Can we use MetaMask to sign AO messages? The documentation was sparse, the examples non-existent. But armed with determination and a healthy dose of console.log debugging, we dove in.

First Attempt: The Naive Approach

// This seemed logical... but it wasn't
const result = await ao.message({
  process: PROCESS_ID,
  signer: metamaskSigner, // 🚫 Nope!
});

Error after error. "Invalid signer format." "Signature mismatch." "Owner must be 512 bytes." Each error message was a breadcrumb on the trail.

The Breakthrough: Understanding Signature Types

The eureka moment came when we discovered that Arweave supports multiple signature types:

  • Type 1: Native Arweave (RSA - 512 bytes)
  • Type 3: Ethereum (ECDSA - 65 bytes)

The "Owner must be 512 bytes" error? It was expecting an RSA key but getting an Ethereum key!

The Solution

After days of debugging, we cracked it. Here's the magic formula:

Step 1: Extract the Ethereum Public Key

const ethSigner = new InjectedEthereumSigner(provider);
await ethSigner.setPublicKey(); // MetaMask prompts for signature

Step 2: Derive the Arweave Address

// Ethereum public key β†’ SHA-256 β†’ Base64url = Arweave address
const hashBuffer = await crypto.subtle.digest('SHA-256', publicKey);
const arweaveAddress = base64url(hashBuffer).slice(0, 43);

Step 3: Create the Magic Signer

const ethArweaveSigner = async (create, signatureType) => {
  // The secret sauce: specify type 3!
  const dataToSign = await create({
    publicKey: ethSigner.publicKey,
    type: 3, // πŸŽ‰ This was the missing piece!
  });
  
  const signature = await ethSigner.sign(dataToSign);
  
  return { signature, address: arweaveAddress };
};

Step 4: Send Messages to AO

const result = await ao.message({
  process: PROCESS_ID,
  tags: [{ name: 'Action', value: 'Hello-From-MetaMask' }],
  data: 'We did it!',
  signer: ethArweaveSigner,
});

And it worked! πŸŽ‰

The Impact

This breakthrough means:

  • No New Wallets: Users keep their familiar MetaMask
  • Instant Onboarding: Click, sign, and you're on AO
  • Cross-Chain Identity: Same address across Ethereum and Arweave ecosystems
  • Security: Private keys never leave MetaMask

Technical Deep Dive

For the curious minds, here's what's happening under the hood:

  1. Public Key Extraction: MetaMask doesn't expose public keys directly. We get it by asking users to sign a message and recovering the public key from the signature.

  2. Address Normalization: Arweave addresses are 43-character Base64url strings. We convert the Ethereum public key to this format using SHA-256 hashing.

  3. Signature Compatibility: The type: 3 parameter tells AO to expect an Ethereum signature (65 bytes) instead of an Arweave signature (512 bytes).

  4. Message Format: AO messages are actually Arweave DataItems (ANS-104 standard) that get bundled and stored permanently.

The Code That Started It All

Here's the complete working example that brought MetaMask to AO:

import { connect } from '@permaweb/aoconnect';
import { InjectedEthereumSigner } from 'arseeding-arbundles/src/signing';
import { Web3Provider } from '@ethersproject/providers';
 
// Initialize
const provider = new Web3Provider(window.ethereum);
const ethSigner = new InjectedEthereumSigner(provider);
await ethSigner.setPublicKey();
 
// Derive Arweave address
const deriveAddress = async (publicKey: Uint8Array) => {
  const hash = await crypto.subtle.digest('SHA-256', publicKey);
  const base64 = btoa(String.fromCharCode(...new Uint8Array(hash)));
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '').slice(0, 43);
};
 
const arweaveAddress = await deriveAddress(ethSigner.publicKey);
 
// Create AO connection
const ao = connect({ MODE: 'legacy' });
 
// The magic signer
const signer = async (create: any) => {
  const dataToSign = await create({
    publicKey: ethSigner.publicKey,
    type: 3, // Ethereum signature type
  });
  
  return {
    signature: await ethSigner.sign(dataToSign),
    address: arweaveAddress,
  };
};
 
// Send message!
const result = await ao.message({
  process: 'YOUR_PROCESS_ID',
  tags: [{ name: 'Action', value: 'Greetings' }],
  data: 'Hello from the Ethereum side!',
  signer,
});
 
console.log('πŸŽ‰ Message sent to AO:', result);

What's Next?

Code editor showing React component structure

Code editor showing React component structure

This discovery opens up incredible possibilities:

For Developers

  • Unified Auth: One wallet for all Web3 needs
  • Easy Migration: Bring Ethereum users to Arweave/AO
  • New Patterns: Cross-chain composability

For Users

  • Simplified UX: No wallet juggling
  • Familiar Tools: Keep using MetaMask
  • Broader Access: Join the Arweave ecosystem instantly

Building the Future

We're not stopping here. We're building:

  • React Hooks: useAOSigner(), useArweaveAddress()
  • TypeScript SDK: Full type safety
  • Multi-Wallet Support: WalletConnect, Coinbase Wallet, etc.
  • Cross-Chain Messages: Ethereum events triggering AO processes

Join the Revolution

Code editor showing React component structure

Code editor showing React component structure

This bridge between Ethereum and Arweave isn't just technicalβ€”it's philosophical. It represents the future of Web3: interoperable, user-friendly, and boundary-breaking.

Want to implement this in your project? Check out our open-source demo and documentation.

The Community

Special thanks to the Arweave and AO communities for building the infrastructure that made this possible. To the arseeding-arbundles team for Ethereum signing support. And to every developer who's ever stared at an error message and thought, "There must be a way."

There always is. You just have to find it.


Have questions? Found improvements? Reach out on Twitter or open an issue on GitHub.

Happy building, and welcome to the cross-chain future! πŸŒ‰