Multisig Wallet
Build a production-grade multisig wallet with passkey authentication, gasless execution, and on-chain smart contracts.
Overview
This example demonstrates two approaches to multi-signer transaction approval:
| Feature | Off-Chain (Veridex SDK) | On-Chain (Smart Contract) |
|---|---|---|
| Gas costs | Gasless β relayer pays | Signers pay gas |
| Authentication | Passkeys (WebAuthn) | EOA wallets (MetaMask) |
| Trust model | Relayer + backend DB | Fully trustless on-chain |
| Best for | Consumer wallets, UX-first | DAOs, treasuries, custody |
Quick Start
Clone and install
git clone https://github.com/Veridex-Protocol/examples.git
cd examples/multisig-wallet
bun installConfigure environment
cp .env.example .env.local| Variable | Description |
|---|---|
DATABASE_URL | SQLite path (file:./data/multisig.db) |
NEXT_PUBLIC_RELAYER_URL | Veridex relayer for gasless txs |
NEXT_PUBLIC_BASE_SEPOLIA_RPC_URL | Base Sepolia RPC |
NEXT_PUBLIC_INTEGRATOR_SPONSOR_KEY | Sponsor key for gasless vault creation |
Initialize database and run
bunx prisma generate
bunx prisma db push
bun run devApproach 1: Off-Chain Multisig (Veridex SDK)
Uses pre-deployed vault contracts on supported chains. Multisig logic (proposals, voting, thresholds) runs off-chain in Next.js with SQLite. Approved proposals execute through the Veridex relayer at zero gas cost.
Architecture
Frontend (React)
βββ AuthScreen βββ PasskeyManager (register/login)
βββ Dashboard ββββ MultisigContext (React Context)
βββ VaultPanel βββ REST API (Next.js)
β /api/wallets, /api/proposals
β /api/invites, /api/notifications
βββ Prisma + SQLite
βββ @veridex/sdk
PasskeyManager, GasSponsor, RelayerClientKey SDK patterns
import { VeridexSDK, EVMClient } from '@veridex/sdk';
// Passkey authentication
await sdk.passkey.register('user@example.com', 'My Wallet');
// Gasless vault deployment
await sdk.createSponsoredVault();
// Gasless transaction execution
await sdk.transferViaRelayer({
token: 'native',
recipient: '0x...',
amount: parseEther('0.5'),
targetChain: 10004,
});
// Balance queries
const portfolio = await sdk.balance.getPortfolioBalance();Proposal lifecycle
- Create β Any signer proposes a transaction (auto-approves their own vote)
- Vote β Other signers approve or reject; status auto-updates at threshold
- Execute β Once approved, any signer triggers execution via relayer
- Notify β All signers receive notifications at each stage
// Create proposal
await api.createProposal(walletId, {
title: 'Pay contractor',
proposalType: 'transfer',
token: 'native',
recipient: '0x...',
amount: parseEther('0.5'),
createdBy: credential.keyHash,
});
// Vote
await api.voteOnProposal(proposalId, {
signerKeyHash: credential.keyHash,
vote: 'approve',
});
// Execute (gasless)
const result = await sdk.transferViaRelayer(proposal.transferParams);
await api.markProposalExecuted(proposalId, credential.keyHash, result.transactionHash);Database schema (Prisma)
| Model | Purpose |
|---|---|
| Wallet | Multisig metadata (name, threshold, vault address) |
| Signer | Passkey key hash, name, status |
| Invite | Shareable invite links |
| Proposal | Transaction proposals (type, target, amount, status) |
| ProposalVote | Individual signer votes |
| Notification | Push notifications for events |
Approach 2: On-Chain Multisig (Smart Contract)
All multisig logic lives on-chain in Solidity. A factory contract deploys new wallet instances.
Contract API
MultisigFactory:
| Function | Description |
|---|---|
createWallet(name, signers, threshold, ttl) | Deploy a new MultisigWallet |
getWalletsByCreator(address) | Wallets created by address |
getWalletsBySigner(address) | Wallets where address is signer |
MultisigWallet:
| Function | Description |
|---|---|
proposeTransfer(title, desc, to, value) | Propose native transfer |
proposeTokenTransfer(title, desc, token, to, amount) | Propose ERC-20 transfer |
proposeExecute(title, desc, target, value, data) | Propose contract call |
proposeAddSigner / proposeRemoveSigner | Signer governance |
approve(id) / reject(id) | Vote on proposal |
execute(id) | Execute approved proposal |
Deployment
npx hardhat run contracts/scripts/deploy.ts --network monadTestnetEnd-to-end on-chain flow
import { ethers } from 'ethers';
// 1. Deploy 2-of-3 multisig
const tx = await factory.createWallet(
'Team Treasury',
[signer1.address, signer2.address, signer3.address],
2, 604800 // 7-day TTL
);
// 2. Fund the wallet
await signer1.sendTransaction({
to: walletAddress, value: ethers.parseEther('1.0')
});
// 3. Propose transfer (auto-approves as signer1)
await wallet.proposeTransfer(
'Pay contractor', 'Monthly payment',
recipientAddress, ethers.parseEther('0.5')
);
// 4. Second signer approves β 2-of-3 threshold met
await wallet.connect(signer2).approve(1);
// 5. Execute
await wallet.execute(1); // 0.5 ETH sent βProject Structure
multisig-wallet/
βββ app/
β βββ api/ # Next.js API routes
β β βββ wallets/ # Wallet CRUD + signers
β β βββ proposals/ # Vote + execute
β β βββ invites/ # Invite accept
β β βββ notifications/ # Notification endpoints
β βββ layout.tsx # Root layout
β βββ page.tsx # Main page
βββ components/
β βββ AuthScreen.tsx # Passkey registration/login
β βββ Dashboard.tsx # Main dashboard
β βββ CreateWalletModal.tsx # New wallet form
β βββ ProposalList.tsx # Transaction proposals
β βββ VaultPanel.tsx # Vault management
βββ contracts/
β βββ MultisigWallet.sol # Core multisig contract
β βββ MultisigFactory.sol # Factory for deploying wallets
β βββ scripts/deploy.ts # Deployment script
βββ lib/
β βββ MultisigContext.tsx # React context + SDK
β βββ api.ts # Frontend API client
β βββ config.ts # Chain configuration
β βββ db.ts # Prisma access layer
βββ prisma/schema.prisma # Database schemaTech Stack
- Next.js 16 β React framework with API routes
- Tailwind CSS v4 β Styling
- Prisma ORM β SQLite database
- @veridex/sdk β Passkey wallet SDK
- Solidity ^0.8.24 β On-chain multisig contracts
Next Steps
- Examples Hub β Browse all available examples
- Session Keys Guide β Understand session key concepts
- Gasless Transactions β Learn about relayer-sponsored execution