Guides
Multisig Wallet

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:

FeatureOff-Chain (Veridex SDK)On-Chain (Smart Contract)
Gas costsGasless β€” relayer paysSigners pay gas
AuthenticationPasskeys (WebAuthn)EOA wallets (MetaMask)
Trust modelRelayer + backend DBFully trustless on-chain
Best forConsumer wallets, UX-firstDAOs, treasuries, custody

Quick Start

Clone and install

git clone https://github.com/Veridex-Protocol/examples.git
cd examples/multisig-wallet
bun install

Configure environment

cp .env.example .env.local
VariableDescription
DATABASE_URLSQLite path (file:./data/multisig.db)
NEXT_PUBLIC_RELAYER_URLVeridex relayer for gasless txs
NEXT_PUBLIC_BASE_SEPOLIA_RPC_URLBase Sepolia RPC
NEXT_PUBLIC_INTEGRATOR_SPONSOR_KEYSponsor key for gasless vault creation

Initialize database and run

bunx prisma generate
bunx prisma db push
bun run dev

Open http://localhost:3000 (opens in a new tab).


Approach 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, RelayerClient

Key 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

  1. Create β€” Any signer proposes a transaction (auto-approves their own vote)
  2. Vote β€” Other signers approve or reject; status auto-updates at threshold
  3. Execute β€” Once approved, any signer triggers execution via relayer
  4. 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)

ModelPurpose
WalletMultisig metadata (name, threshold, vault address)
SignerPasskey key hash, name, status
InviteShareable invite links
ProposalTransaction proposals (type, target, amount, status)
ProposalVoteIndividual signer votes
NotificationPush 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:

FunctionDescription
createWallet(name, signers, threshold, ttl)Deploy a new MultisigWallet
getWalletsByCreator(address)Wallets created by address
getWalletsBySigner(address)Wallets where address is signer

MultisigWallet:

FunctionDescription
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 / proposeRemoveSignerSigner governance
approve(id) / reject(id)Vote on proposal
execute(id)Execute approved proposal

Deployment

npx hardhat run contracts/scripts/deploy.ts --network monadTestnet

End-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 schema

Tech 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