A modern MetaMask Snap that brings Bitcoin, Lightning and Arkade functionality to your browser. Built with Arkade SDK and MetaMask Snaps, this project demonstrates a simplified provider pattern where the Snap only handles Bitcoin signing operations, while all wallet logic runs in the frontend.
This project uses a simplified provider pattern where the Snap only handles Bitcoin signing operations, while all wallet logic runs in the frontend:
packages/snap- Minimal MetaMask Snap that provides Bitcoin key management and PSBT signingpackages/site- React dapp that runs Arkade SDK directly using MetaMaskSnapIdentity provider
┌─────────────────────────────────────────┐
│ Frontend (Dapp) │
│ ┌────────────────────────────────┐ │
│ │ Arkade SDK Wallet │ │
│ │ - Balance queries │ │
│ │ - Transaction history │ │
│ │ - Lightning operations │ │
│ │ - VTXO management │ │
│ └──────────┬─────────────────────┘ │
│ │ │
│ │ (signing requests only) │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ MetaMask Snap │ │
│ │ - arkade_getPublicKey() │ │
│ │ - arkade_getAddress() │ │
│ │ - arkade_signPsbt() │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────┘
- Node.js v20 or later
- pnpm v8 or later
- MetaMask Flask - Developer version of MetaMask
Clone the repository and install dependencies:
git clone <your-repo-url>
cd arkade-snap
pnpm installRun both the Snap and the frontend in watch mode:
pnpm startThis will:
- Start the Snap server at
http://localhost:8080 - Start the frontend dapp at
http://localhost:8000
- Open http://localhost:8000 in your browser
- Make sure you have MetaMask Flask installed
- Click "Connect Snap" to install the Arkade Wallet Snap
- Create a new wallet or import an existing one
- Start using your Arkade Wallet!
pnpm start- Start both snap and site in development modepnpm build- Build both packagespnpm clean- Clean all build artifactspnpm test- Run tests in all packages
pnpm start- Watch mode for development (auto-rebuilds on changes)pnpm stop- Stop the snap server (kills process on port 8080)pnpm build- Build the snap bundlepnpm rebuild- Clean, build, and serve the snappnpm serve- Serve the snap locallypnpm clean/pnpm delete- Clean build artifactspnpm lint- Lint the codepnpm test- Run tests
pnpm dev- Start Vite development serverpnpm build- Build for productionpnpm preview- Preview production buildpnpm lint- Lint the code
The Arkade Wallet Snap is minimal by design and exposes 3 focused RPC methods for Bitcoin key management and signing:
Get the snap's public keys (compressed and x-only formats).
const response = await ethereum.request({
method: 'wallet_invokeSnap',
params: {
snapId: 'local:http://localhost:8080',
request: { method: 'arkade_getPublicKey' }
}
});
// Returns:
// {
// compressedPublicKey: "02...", // Compressed public key (33 bytes hex)
// xOnlyPublicKey: "..." // x-only public key (32 bytes hex)
// }Get the Arkade address for the current network and server configuration.
const response = await ethereum.request({
method: 'wallet_invokeSnap',
params: {
snapId: 'local:http://localhost:8080',
request: {
method: 'arkade_getAddress',
params: {
network: 'bitcoin', // 'bitcoin' | 'testnet' | 'signet' | 'mutinynet' | 'regtest'
signerPubkey: '...', // Server's x-only public key (64 hex chars)
unilateralExitDelay: '512' // CSV timelock value from server
}
}
}
});
// Returns:
// {
// address: "ark1..." // Arkade address (bech32m encoded)
// }Sign a Partially Signed Bitcoin Transaction (PSBT).
const response = await ethereum.request({
method: 'wallet_invokeSnap',
params: {
snapId: 'local:http://localhost:8080',
request: {
method: 'arkade_signPsbt',
params: {
psbt: 'cHNidP8B...', // Base64-encoded PSBT
inputIndexes: [0, 1] // Indexes of inputs to sign
}
}
}
});
// Returns:
// {
// psbt: 'cHNidP8B...' // Base64-encoded signed PSBT
// }All wallet logic (balance, transactions, Lightning) runs in the frontend using the Arkade SDK with a MetaMaskSnapIdentity provider. The snap only handles sensitive key operations. This makes it:
- ✅ Simpler - Easier to audit and maintain
- ✅ More secure - Minimal attack surface, keys never leave the snap
- ✅ Faster - No RPC overhead for data queries
- ✅ More flexible - Update wallet logic without snap rebuild
arkade-metamask-snap/
├── packages/
│ ├── snap/ # MetaMask Snap
│ │ ├── src/
│ │ │ ├── index.ts # Main snap entry point with RPC handlers
│ │ │ └── wallet.ts # Arkade SDK wallet integration
│ │ ├── images/
│ │ │ └── icon.svg # Snap icon
│ │ ├── package.json
│ │ ├── snap.manifest.json # Snap configuration
│ │ └── tsconfig.json
│ └── site/ # Frontend dapp
│ ├── src/
│ │ ├── components/
│ │ │ ├── Header.tsx
│ │ │ ├── WalletConnect.tsx
│ │ │ ├── Dashboard.tsx
│ │ │ ├── SendModal.tsx
│ │ │ ├── LightningModal.tsx
│ │ │ └── MetaMaskProvider.tsx
│ │ ├── App.tsx
│ │ ├── main.tsx
│ │ └── index.css
│ ├── public/
│ │ ├── logo.png
│ │ └── icon.svg
│ └── package.json
├── Logo/ # Brand assets
├── package.json
├── pnpm-workspace.yaml
└── README.md
The app uses a modern purple color scheme inspired by the Arkade brand:
- Primary Purple:
#8B5CF6 - Dark Purple:
#7C3AED - Light Purple:
#A78BFA - Background:
#F5F3FF - Typography: Geist TT First Neue Trial
- MetaMask Snaps SDK - For building the browser extension snap
- Arkade SDK v0.3.1-alpha.3 - Bitcoin Layer 2 wallet functionality
- React 18 - Modern UI framework
- TypeScript - Type-safe development
- Vite - Fast build tool and dev server
- pnpm - Fast, disk space efficient package manager
The dapp can be configured to connect to either a local snap (for development) or the published npm package (for production) using environment variables.
For local development, create a .env.development file in packages/site/:
cd packages/site
cp .env.example .env.developmentThe file should contain:
VITE_SNAP_ID=local:http://localhost:8080Then start the development servers:
pnpm start # From root - starts both snap and siteFor production deployment, create a .env.production file in packages/site/:
cd packages/site
cp .env.example .env.productionEdit the file to use the npm package:
VITE_SNAP_ID=npm:@arkade-os/snapThen build for production:
cd packages/site
pnpm buildThe built files in dist/ can be deployed to any static hosting service (Vercel, Netlify, GitHub Pages, etc.).
You can also set the snap ID and version directly via environment variables:
# Build with npm snap (uses default version >=0.1.0)
VITE_SNAP_ID=npm:@arkade-os/snap pnpm build
# Or pin to a specific version
VITE_SNAP_ID=npm:@arkade-os/snap VITE_SNAP_VERSION=0.1.2 pnpm build
# Or use a version range
VITE_SNAP_ID=npm:@arkade-os/snap VITE_SNAP_VERSION=^0.1.0 pnpm build-
Publish the snap to npm (see Release Process below)
-
Configure environment for production
-
Build the frontend:
cd packages/site VITE_SNAP_ID=npm:@arkade-os/snap pnpm build -
Deploy the
dist/folder to your hosting service
To publish a new version of the snap to npm:
# Navigate to the snap package
cd packages/snap
# Bump version (patch, minor, or major)
npm version patch # or minor/majorAfter bumping the version, you must rebuild the snap to update the snap.manifest.json with the new version and shasum:
pnpm run buildThis command will automatically:
- Update the version in
snap.manifest.jsonto matchpackage.json - Regenerate the shasum for the new bundle
git add .
git commit -m "chore: bump version to x.x.x"
git push# Make sure you're logged in to npm
npm whoami
# Publish the package
npm publish- Shasum mismatch error: Always run
pnpm run buildafter changing the version number - Version mismatch: The build command automatically syncs versions between
package.jsonandsnap.manifest.json
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
