This guide explains how SmartWallet authentication works in the Lira Protocol and how to configure it as the primary login method.
Lira Protocol supports multiple authentication methods:
- RainbowKit - Traditional wallet connection (MetaMask, WalletConnect, etc.)
- SmartWallet - Advanced account abstraction with social login (PRIMARY)
- DAO Token Resolution - Username-based authentication with DAO token mapping
SmartWallet provides:
- Account Abstraction: Gasless transactions and improved UX
- Social Login: Login with Google, Twitter, Email
- Session Keys: Persistent sessions without repeated wallet approvals
- Multi-Chain Support: Works across BASE, Ethereum, and other chains
Add these to your .env file:
# Wallet Connect Project ID (required for RainbowKit)
NEXT_PUBLIC_WALLET_CONNECT_ID=your_wallet_connect_project_id
# SmartWallet Configuration (Coinbase Smart Wallet)
NEXT_PUBLIC_SMART_WALLET_ENABLED=true
NEXT_PUBLIC_SMART_WALLET_PROVIDER=coinbase
# Chain Configuration
NEXT_PUBLIC_CHAIN_ID=8453
NEXT_PUBLIC_CHAIN_NAME=base
BASE_RPC_URL=https://mainnet.base.org
# DAO Token Resolution
NEXT_PUBLIC_DAO_TOKEN_CONTRACT=0x...
NEXT_PUBLIC_USERNAME_REGISTRY_CONTRACT=0x...
# Session Configuration
SESSION_SECRET=your_secret_key_for_sessions
SESSION_MAX_AGE=604800The application is configured to support SmartWallet through Wagmi v2 and RainbowKit v2:
import { WagmiProvider } from 'wagmi';
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit';
import { base, baseSepolia } from 'viem/chains';
const config = getDefaultConfig({
appName: 'Lira Protocol',
projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_ID!,
chains: [base, baseSepolia],
transports: {
[base.id]: http(),
[baseSepolia.id]: http(),
},
});RainbowKit v2 automatically includes Coinbase Smart Wallet in the default wallet list.
// User clicks "Connect Wallet"
import { useAccount, useConnect } from 'wagmi';
function ConnectButton() {
const { address, isConnected } = useAccount();
const { connect, connectors } = useConnect();
// SmartWallet is automatically available in connectors
return (
<RainbowKitConnectButton />
);
}After connection, create a server-side session:
// pages/api/auth/session.ts
export default async function handler(req, res) {
const { address, signature } = req.body;
// Verify signature
const isValid = await verifySignature(address, signature);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Create session
const session = await createSession({
address,
chainId: req.body.chainId,
expiresAt: Date.now() + SESSION_MAX_AGE * 1000,
});
res.setHeader('Set-Cookie', `session=${session.token}; HttpOnly; Secure; Path=/`);
res.json({ success: true, session });
}Store authentication tokens securely:
// lib/auth.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AuthState {
token: string | null;
address: string | null;
setAuth: (token: string, address: string) => void;
clearAuth: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
address: null,
setAuth: (token, address) => set({ token, address }),
clearAuth: () => set({ token: null, address: null }),
}),
{
name: 'lira-auth',
}
)
);// Disconnect wallet and clear session
function LogoutButton() {
const { disconnect } = useDisconnect();
const clearAuth = useAuthStore((state) => state.clearAuth);
const handleLogout = async () => {
// Clear server session
await fetch('/api/auth/logout', { method: 'POST' });
// Disconnect wallet
disconnect();
// Clear local auth state
clearAuth();
};
return <button onClick={handleLogout}>Logout</button>;
}Users can be identified by usernames that map to DAO tokens, enabling social-style interactions while maintaining on-chain identity.
Username → Registry Contract → DAO Token → Wallet Address
// contracts/UsernameRegistry.sol
pragma solidity ^0.8.20;
contract UsernameRegistry {
mapping(string => address) public usernameToToken;
mapping(address => string) public tokenToUsername;
mapping(address => address) public tokenToOwner;
event UsernameRegistered(string username, address indexed token, address indexed owner);
function registerUsername(
string memory username,
address daoToken,
address owner
) external {
require(usernameToToken[username] == address(0), "Username taken");
require(bytes(tokenToUsername[daoToken]).length == 0, "Token already has username");
usernameToToken[username] = daoToken;
tokenToUsername[daoToken] = username;
tokenToOwner[daoToken] = owner;
emit UsernameRegistered(username, daoToken, owner);
}
function resolveUsername(string memory username)
external
view
returns (address token, address owner)
{
token = usernameToToken[username];
owner = tokenToOwner[token];
}
}// lib/username.ts
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';
const REGISTRY_ADDRESS = process.env.NEXT_PUBLIC_USERNAME_REGISTRY_CONTRACT!;
const client = createPublicClient({
chain: base,
transport: http(),
});
export async function resolveUsername(username: string) {
const result = await client.readContract({
address: REGISTRY_ADDRESS,
abi: REGISTRY_ABI,
functionName: 'resolveUsername',
args: [username],
});
return {
token: result[0],
owner: result[1],
};
}
export async function getUsernameByToken(token: string) {
const result = await client.readContract({
address: REGISTRY_ADDRESS,
abi: REGISTRY_ABI,
functionName: 'tokenToUsername',
args: [token],
});
return result;
}// components/UsernameLogin.tsx
import { useState } from 'react';
import { resolveUsername } from '@/lib/username';
import { useConnect } from 'wagmi';
export function UsernameLogin() {
const [username, setUsername] = useState('');
const { connect } = useConnect();
const handleLogin = async () => {
// Resolve username to DAO token and owner
const { token, owner } = await resolveUsername(username);
// Connect with the resolved owner address
// In practice, user still needs to sign with their wallet
connect();
// Store username context
sessionStorage.setItem('lira_username', username);
sessionStorage.setItem('lira_dao_token', token);
};
return (
<div>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter username"
/>
<button onClick={handleLogin}>Login</button>
</div>
);
}Combine wallet connection with username resolution:
// lib/auth-context.ts
import { createContext, useContext, useEffect, useState } from 'react';
import { useAccount } from 'wagmi';
import { getUsernameByToken, resolveUsername } from './username';
interface AuthContext {
address: string | undefined;
username: string | null;
daoToken: string | null;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContext>({
address: undefined,
username: null,
daoToken: null,
isAuthenticated: false,
});
export function AuthProvider({ children }: { children: React.ReactNode }) {
const { address } = useAccount();
const [username, setUsername] = useState<string | null>(null);
const [daoToken, setDaoToken] = useState<string | null>(null);
useEffect(() => {
if (address) {
// Try to load from session first
const storedUsername = sessionStorage.getItem('lira_username');
const storedToken = sessionStorage.getItem('lira_dao_token');
if (storedUsername && storedToken) {
setUsername(storedUsername);
setDaoToken(storedToken);
}
} else {
setUsername(null);
setDaoToken(null);
sessionStorage.removeItem('lira_username');
sessionStorage.removeItem('lira_dao_token');
}
}, [address]);
return (
<AuthContext.Provider
value={{
address,
username,
daoToken,
isAuthenticated: !!address,
}}
>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);- Use
HttpOnlycookies for session tokens - Set
Secureflag in production - Implement CSRF protection
- Use short session expiry times
Always verify wallet signatures on the server:
import { recoverMessageAddress } from 'viem';
async function verifySignature(address: string, signature: string, message: string) {
const recoveredAddress = await recoverMessageAddress({
message,
signature,
});
return recoveredAddress.toLowerCase() === address.toLowerCase();
}Implement rate limiting for authentication endpoints:
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
// Allow 5 authentication attempts per 15 minutes per identifier (e.g. IP or wallet address)
export const authLimiter = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '15 m'),
analytics: true,
});
export async function limitAuthRequest(identifier: string) {
const { success } = await authLimiter.limit(identifier);
if (!success) {
throw new Error('Too many authentication attempts');
}
}- Validate username format (alphanumeric, length limits)
- Check for offensive/reserved usernames
- Rate limit username registration
- Require signature for username changes
- Start development server:
npm run dev- Connect with test wallet:
- Use MetaMask with BASE Sepolia testnet
- Or use Coinbase Smart Wallet
- Test username resolution:
// Test in browser console
const result = await fetch('/api/username/resolve?username=alice');
console.log(await result.json());// __tests__/auth.test.ts
import { resolveUsername } from '@/lib/username';
describe('Username Resolution', () => {
it('resolves username to token', async () => {
const result = await resolveUsername('alice');
expect(result.token).toBeDefined();
expect(result.owner).toBeDefined();
});
it('handles non-existent username', async () => {
await expect(resolveUsername('nonexistent')).rejects.toThrow();
});
});- Check RainbowKit version (should be v2+)
- Verify Wallet Connect Project ID is set
- Check browser console for errors
- Ensure you're on a supported chain
- Increase
SESSION_MAX_AGEin environment variables - Implement token refresh mechanism
- Check cookie settings in production
- Verify contract address is correct
- Check network (should be BASE mainnet/testnet)
- Ensure RPC endpoint is working
- Check contract is deployed and accessible
- Always use HTTPS in production
- Implement proper error handling
- Log authentication events
- Use environment-specific configurations
- Regular security audits
- Keep dependencies updated
- Test across different wallets
- Monitor authentication metrics
Last Updated: 2026-01-20
Version: 1.0.0
Maintainer: SMSDAO Team