| name | metamask-openclaw-wallet-integration |
| description | Connect MetaMask wallets, read balances, sign messages, and send transactions on EVM chains with a safety-first TypeScript SDK |
| triggers | ["integrate MetaMask wallet into my dapp","connect to MetaMask and read user balance","sign a message with MetaMask","send a transaction using OpenClaw","switch MetaMask to a different network","set up wallet connection in my web3 app","handle MetaMask account changes","prepare an Ethereum transfer with user approval"] |
MetaMask OpenClaw Wallet Integration
Skill by ara.so — Hermes Skills collection.
Overview
OpenClaw is a safety-first TypeScript toolkit and SDK for integrating MetaMask wallets into dapps. Built on the official MetaMask SDK, it provides a clean API for:
- Connecting wallets (desktop browser, mobile browser, QR code to MetaMask Mobile)
- Reading account balances across EVM chains (Ethereum, Polygon, Base, Arbitrum, Optimism, Linea)
- Signing messages (user approves in MetaMask UI)
- Preparing and sending transactions (user approves amount and gas)
- Switching networks with automatic chain addition
- Reacting to account/network changes
Safety guarantee: OpenClaw never requests or handles seed phrases or private keys. All sensitive operations require user approval in the MetaMask UI.
Installation
As a library (npm/yarn/pnpm)
npm install metamask-openclaw-skill
yarn add metamask-openclaw-skill
pnpm add metamask-openclaw-skill
From source
git clone https://github.com/veryyoldman/metamask-openclaw-skill.git
cd metamask-openclaw-skill
npm install
npm run build
Windows one-command install
git clone https://github.com/veryyoldman/metamask-openclaw-skill.git && cd metamask-openclaw-skill && npm install && npm run build
Quick Start
Basic library usage
import { OpenClaw } from "metamask-openclaw-skill";
const claw = new OpenClaw({
dappMetadata: {
name: "My Dapp",
url: "https://mydapp.example"
},
infuraApiKey: process.env.INFURA_API_KEY
});
await claw.connect();
const { account, chainId } = claw.getState();
console.log(`Connected: ${account} on chain ${chainId}`);
CLI usage
node dist/cli/openclaw.js connect
node dist/cli/openclaw.js balance
node dist/cli/openclaw.js send 0xRecipientAddress 0.001
Browser usage (zero build)
<!DOCTYPE html>
<html>
<head>
<title>OpenClaw Demo</title>
</head>
<body>
<button id="connect">Connect MetaMask</button>
<div id="info"></div>
<script type="module">
import { OpenClaw } from './dist/index.js';
const claw = new OpenClaw({
dappMetadata: { name: "Demo", url: window.location.href }
});
document.getElementById('connect').onclick = async () => {
await claw.connect();
const { account, chainId } = claw.getState();
const balance = await claw.getBalance();
document.getElementById('info').innerText =
`${account}\nChain: ${chainId}\nBalance: ${balance.formatted} ${balance.symbol}`;
};
</script>
</body>
</html>
Core API
Constructor options
interface OpenClawOptions {
dappMetadata: {
name: string;
url: string;
iconUrl?: string;
};
infuraApiKey?: string;
defaultChainId?: string;
}
const claw = new OpenClaw(options);
Connection methods
await claw.connect();
claw.disconnect();
const isConnected = claw.isConnected();
const state = claw.getState();
Reading data
const balance = await claw.getBalance();
const balance = await claw.getBalance("0xOtherAddress");
Signing messages
const signature = await claw.signMessage("Hello, Web3!");
Network switching
await claw.switchChain("0x89");
await claw.switchChain("0x2105");
await claw.switchChain("0xa4b1");
Sending transactions
const txHash = await claw.sendNative({
to: "0xRecipientAddress",
amount: "0.001"
});
console.log(`Transaction sent: ${txHash}`);
Event handling
claw.onChange((state) => {
console.log("Wallet changed:", state);
});
const unsubscribe = claw.onChange(handler);
unsubscribe();
Configuration
Environment variables
INFURA_API_KEY=your_infura_key_here
import { OpenClaw } from "metamask-openclaw-skill";
const claw = new OpenClaw({
dappMetadata: {
name: process.env.DAPP_NAME || "My Dapp",
url: process.env.DAPP_URL || "http://localhost:3000"
},
infuraApiKey: process.env.INFURA_API_KEY
});
Supported networks
| Network | Chain ID | Symbol |
|---|
| Ethereum Mainnet | 0x1 | ETH |
| Polygon | 0x89 | POL |
| Arbitrum One | 0xa4b1 | ETH |
| OP Mainnet | 0xa | ETH |
| Base | 0x2105 | ETH |
| Linea | 0xe708 | ETH |
| Sepolia (testnet) | 0xaa36a7 | ETH |
Add custom networks in src/chains.ts.
Common Patterns
React integration
import { OpenClaw } from "metamask-openclaw-skill";
import { useEffect, useState } from "react";
function WalletButton() {
const [claw] = useState(() => new OpenClaw({
dappMetadata: { name: "My App", url: window.location.href }
}));
const [account, setAccount] = useState<string | null>(null);
const [balance, setBalance] = useState<string>("");
useEffect(() => {
claw.onChange((state) => {
setAccount(state.account);
if (state.account) {
claw.getBalance().then(b => setBalance(`${b.formatted} ${b.symbol}`));
}
});
}, [claw]);
const handleConnect = async () => {
await claw.connect();
const state = claw.getState();
setAccount(state.account);
if (state.account) {
const b = await claw.getBalance();
setBalance(`${b.formatted} ${b.symbol}`);
}
};
return (
<div>
{!account ? (
<button onClick={handleConnect}>Connect MetaMask</button>
) : (
<div>
<p>{account}</p>
<p>{balance}</p>
</div>
)}
</div>
);
}
Transfer with confirmation
async function sendTokens(claw: OpenClaw, recipient: string, amount: string) {
const state = claw.getState();
if (!state.account) {
throw new Error("No wallet connected");
}
const balance = await claw.getBalance();
if (parseFloat(balance.formatted) < parseFloat(amount)) {
throw new Error("Insufficient balance");
}
const txHash = await claw.sendNative({ to: recipient, amount });
console.log(`Transfer initiated: ${txHash}`);
return txHash;
}
Multi-chain balance checker
async function checkBalanceOnChains(claw: OpenClaw, chains: string[]) {
const balances: Record<string, string> = {};
for (const chainId of chains) {
await claw.switchChain(chainId);
const balance = await claw.getBalance();
balances[chainId] = `${balance.formatted} ${balance.symbol}`;
}
return balances;
}
const balances = await checkBalanceOnChains(claw, ["0x1", "0x89", "0x2105"]);
console.log(balances);
Safe message signing
async function signAuthMessage(claw: OpenClaw, nonce: string) {
const state = claw.getState();
if (!state.account) {
throw new Error("No wallet connected");
}
const message = `Sign to authenticate\nAddress: ${state.account}\nNonce: ${nonce}`;
const signature = await claw.signMessage(message);
return { message, signature, address: state.account };
}
Troubleshooting
"MetaMask not detected"
Solution: Ensure MetaMask extension is installed and enabled. On mobile, use the in-app browser or scan the QR code.
if (!window.ethereum) {
alert("Please install MetaMask: https://metamask.io/download/");
}
Connection hangs in Node.js CLI
Cause: Waiting for MetaMask Mobile QR scan.
Solution: The CLI prints a QR code. Open MetaMask Mobile and scan it.
"User rejected the request"
Cause: User clicked "Cancel" or "Reject" in MetaMask.
Solution: Catch the error and prompt the user to try again.
try {
await claw.connect();
} catch (error) {
if (error.code === 4001) {
console.log("User rejected connection");
}
}
Balance shows "0.0" on testnet
Cause: No funds in the test account.
Solution: Get testnet ETH from a faucet:
Chain switch fails
Cause: Network not configured in MetaMask.
Solution: OpenClaw automatically adds the network (user approves). Ensure chain details are correct in src/chains.ts.
Infura rate limiting
Cause: No Infura API key or exceeded free tier.
Solution: Sign up at https://infura.io/ and set INFURA_API_KEY environment variable.
const claw = new OpenClaw({
dappMetadata: { name: "App", url: "https://app.example" },
infuraApiKey: process.env.INFURA_API_KEY
});
Safety & Security
OpenClaw never:
- Requests your seed phrase or private keys
- Signs or sends anything without user approval in MetaMask UI
- Stores sensitive data
Always:
- User approves every signature and transaction in MetaMask
- Keys remain in MetaMask's secure vault
- Only public addresses are exposed to the dapp
Never enter your Secret Recovery Phrase anywhere except the official MetaMask app when restoring a wallet. OpenClaw will never ask for it.
AI Agent Usage
When helping users with OpenClaw:
- Never generate or request seed phrases or private keys
- Always defer fund movements to user approval via
sendNative()
- Verify the user has MetaMask installed before suggesting connection
- Use
getBalance() for read-only balance checks
- Encourage testnet usage (Sepolia) for learning
- Remind users that they approve all signatures and transactions in MetaMask
Example safe agent response:
I can help you prepare a transaction using OpenClaw. Here's the code:
await claw.sendNative({ to: "0xRecipient", amount: "0.001" });
This will show a MetaMask popup where YOU approve the amount and gas.
OpenClaw never handles your keys — you stay in full control.
Additional Resources
Keywords: metamask, wallet integration, web3, ethereum, evm, polygon, base, arbitrum, sign message, send transaction, dapp, typescript, sdk