How to Connect MetaMask for Identity in Internet Computer Applications?

I’m developing a dapp on the Internet Computer and want to implement MetaMask login functionality similar to OpenChat’s simple one-button approach. I need help with:

Current Goal

I need to connect MetaMask and obtain an identity that can be used with the Agent:

const agent = new HttpAgent({
    identity: fetchIdentity(MetaMaskToken),
    host,
});

Where my backend can catch that in caller()

What I’ve Tried

  • Looked at ic-swi example but found it overly complex, it requires user to preform two steps and I want them to do it in just one click
  • Tried to navigate OpenChat’s codebase but it’s quite large and difficult to extract just the MetaMask integration part

What I Need

  • A simpler implementation with a single button for MetaMask connection
  • The correct way to get the identity from MetaMask that works with HttpAgent
  • Ensure that caller() returns the MetaMask ID when the user is authenticated
  • Can I just use
const newAccounts = await window.ethereum.request({
         method: 'eth_requestAccounts'
       });

?
Has anyone implemented a clean solution for this or can point me to relevant code examples? Ideally looking for something more straightforward than the existing examples.

1 Like

It seems that the one most widely used in the community is the SIWE developed by Astrox.

I once developed an application that allowed users to log in using MetaMask. The flow was simple: the frontend would prompt MetaMask to sign a message (which could be customized), and after signing just once, users could log into the IC directly.

This mechanism uses a Delegation Identity flow similar to Internet Identity. It’s a bit complex to explain, but not too difficult to implement or use.

I didn’t package it as a public infrastructure library at the time, but I can show you how to implement the functionality you’re looking for—you’ll just need to adapt the code I provide. If you’re interested, I’d be happy to walk you through it.

I think it’s time for me to reorganize this repository and create a simple, lightweight crate along with an npm package for the frontend.

1 Like

I tried ic_siws it does not work there are some bugs in it
check this out : Announcing ic-siwe: Use Ethereum wallets to login to IC - #37 by AliSci

Check this out

I have an auth.js/ts that handles the whole process

with this you can add any wallet you want, the process is exactly the same

Sign in with the wallet → UniqueMessage → Create seed → Create keys out of seed → Create identity/agent ready to sign calls

It’s all one-click solution with seed recovery method integrated

1 Like

So, just to make sure I understand

class MetaMaskService {
  async isMetaMaskInstalled() {
    const isInstalled = typeof window.ethereum !== 'undefined';
    return isInstalled;
  }

  async getEthereumAddress() {
    console.log('Getting Ethereum address...');
    if (!(await this.isMetaMaskInstalled())) {
      alert('MetaMask is not installed.');
      throw new Error('MetaMask is not installed');
    }

    let accounts = await window.ethereum.request({ method: 'eth_accounts' });

    // If accounts are empty, prompt user to unlock MetaMask
    if (accounts.length === 0) {
      accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
    }

    if (accounts.length === 0) {
      alert('Metamask is locked.');
      throw new Error('Metamask is locked.');
    }

    console.log('Ethereum address:', accounts[0]);
    return accounts[0]; // Returns the first account
  }

  async signMessage(message) {
    console.log('Signing message...');
    if (!(await this.isMetaMaskInstalled())) {
      alert('MetaMask is not installed.');
      throw new Error('MetaMask is not installed');
    }

    const ethereumAddress = await this.getEthereumAddress();

    const signature = await window.ethereum.request({
      method: 'personal_sign',
      params: [message, ethereumAddress],
    });
    //console.log('Signature:', signature);
    return signature; // Returns the signature
  }
}

export default new MetaMaskService();

async loginWithMetaMask() {
      try {
        // Show loading state in the store
        this.authenticated = false;
        this.registered = false;
        this.player = null;

        // Unique message for signature to create deterministic seed
        const uniqueMessage = 'Sign this message to log in with your Ethereum wallet';
        
        console.log('Requesting MetaMask signature...');
        const signature = await MetaMaskService.signMessage(uniqueMessage);
        console.log('MetaMask Signature received');
        
        if (!signature) {
          throw new Error('Failed to sign with MetaMask.');
        }
        
        // Generate seed phrase from signature
        console.log('Generating seed phrase from signature...');
        const seedPhrase = await generateSeedPhrase(signature);
        
        // Wait for this to fully complete before continuing
        console.log('Initializing login flow with seed phrase...');
        await this.handleLoginFlow(seedPhrase, { source: 'metamask', retry: true });
        
        return { success: true };
      } catch (error) {
        console.error('MetaMask login error:', error);
        throw new Error(`MetaMask login failed: ${error.message}`);
      }
    }
  • I just inject that in my code and that is it ?
  • I think there should be providers to wrapp my app with it?
    <ContextProviders ><Myapp/> <ContextProviders/>
  • What about fetchIdentity(MetaMaskToken)
const agent = new HttpAgent({
    identity: fetchIdentity(MetaMaskToken),
    host,
});

Yes so the Wallet/Metamask service is just a helper to call the extension on the browser, and get the signature, and get the 0x address

The auth will handle the rest which is to create your ICP wallet/identity

Including seed phrase, private/public key, principal id, agent to sign calls and it even has utilities to get AccountIDs out of your principal.

if confused, copy paste the code to an LLM it’ll know what to do

1 Like

I just tried it. I got

import { AuthClient } from "@dfinity/auth-client";
const createAuthClient = await AuthClient.create();
const client = await createAuthClient();
let identity = await client?.getIdentity()
  console.log({identity})

it was working fine but with metamsk now just nothing

{
    "identity": {}
}