A non-custodial Solana web wallet that uses Multi-Party Computation (MPC) with threshold signatures (TSS) for secure key management. Users can send SOL and swap tokens without any single server ever holding a complete private key.
Built for Superdev Solana Fellowship.
- MPC Wallet Creation - On signup, each user is assigned a wallet derived from distributed key shares across 3 MPC nodes. No single node holds the full private key.
- Send SOL - Transfer SOL to any Solana address. Transactions are signed collaboratively by MPC nodes using the MuSig2 protocol.
- Swap Tokens - Swap between SOL, USDC, USDT, and other SPL tokens via the Jupiter DEX aggregator with slippage protection and dynamic priority fees.
- Balance Tracking - A background indexer continuously polls Solana to keep user balances and transaction history in sync, with USD prices fetched from CoinGecko.
┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐
│ Frontend │──────▶│ Backend │──────▶│ MPC Node 1 (3001) │
│ (Next.js) │ │ (Actix-web) │──────▶│ MPC Node 2 (3002) │
│ Port 3000 │ │ Port 8080 │──────▶│ MPC Node 3 (3003) │
└──────────────┘ └──────┬───────┘ └──────────────────────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
┌──────────┐ ┌─────┐ ┌──────────┐
│PostgreSQL│ │Solana│ │ Jupiter │
│ (2 DBs) │ │ RPC │ │ API │
└──────────┘ └─────┘ └──────────┘
▲
│
┌──────────┐
│ Indexer │
│ (cron) │
└──────────┘
4 services run independently:
| Service | Language | Purpose |
|---|---|---|
| Backend | Rust (Actix-web) | REST API - auth, send, swap, balance queries |
| MPC Node | Rust (Actix-web) | Holds key shares, produces partial signatures |
| Indexer | Rust (Tokio) | Polls Solana RPC, syncs balances & transactions to DB |
| Frontend | TypeScript (Next.js 15) | User-facing dashboard |
This project uses the multi-party-eddsa library implementing the MuSig2 protocol for Ed25519 (Solana's signature scheme).
Key Generation:
- Admin triggers key generation across all 3 MPC nodes
- Each node generates its own Solana keypair and stores it
- Backend aggregates the public keys into a single Solana address using MuSig2 key aggregation
- The aggregated public key becomes the user's wallet address
Transaction Signing (2-round protocol):
- Round 1 - Backend sends a signing request to each MPC node. Each node generates partial nonces and returns them.
- Round 2 - Backend distributes all nonces to each node along with the unsigned transaction. Each node produces a partial signature.
- Aggregation - Backend aggregates partial signatures into a valid Ed25519 signature and submits the signed transaction to Solana.
Private keys never leave their respective MPC nodes. The backend only ever sees partial signatures.
Backend & Services:
- Rust, Actix-web 4, Tokio
- SQLx + PostgreSQL
- multi-party-eddsa / curv-kzen (MPC/TSS)
- solana-sdk 3.0, solana-client 3.0
- jsonwebtoken (JWT auth), bcrypt (password hashing)
Frontend:
- Next.js 15, React 19, TypeScript
- Tailwind CSS 4
- Zustand (state management)
External APIs:
- Jupiter v6 (DEX aggregation for swaps)
- CoinGecko (token prices)
- Solana RPC (devnet / mainnet)
tiplink-clone/
├── backend/ # Main API server (port 8080)
│ └── src/
│ ├── main.rs # Server setup & routes
│ ├── routes/
│ │ ├── user.rs # Signup, signin, user info
│ │ ├── solana.rs # Send, swap, quote
│ │ ├── balance.rs # SOL & token balances
│ │ ├── aggregated_key.rs
│ │ ├── asset.rs # Token registry CRUD
│ │ └── mpc_node.rs # MPC node management
│ ├── tss.rs # Signature aggregation & broadcast
│ └── jwt.rs # JWT middleware
├── mpc/ # MPC signing node (port 3001+)
│ └── src/
│ ├── main.rs # MPC server setup
│ └── routes/
│ ├── keypair.rs # Key generation & lookup
│ └── sign.rs # Step-one / step-two signing
├── indexer/ # Balance & transaction indexer
│ └── src/main.rs # Polling loop
├── store/ # Shared DB models (main database)
├── mpc-store/ # MPC DB models (keypair storage)
├── frontend/ # Next.js application
│ ├── app/
│ │ ├── (auth)/ # Signin / signup pages
│ │ ├── dashboard/ # Main wallet UI
│ │ └── page.tsx # Landing page
│ ├── components/
│ │ ├── SendComponent.tsx
│ │ └── SwapComponent.tsx
│ └── store/ # Zustand stores
└── Cargo.toml # Rust workspace
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/signup |
Register a new user (auto-assigns MPC wallet) |
| POST | /api/v1/signin |
Login, returns JWT |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/user |
Current user info |
| POST | /api/v1/send |
Send SOL to an address |
| POST | /api/v1/quote |
Get a swap quote from Jupiter |
| POST | /api/v1/swap |
Execute a swap using a stored quote |
| GET | /api/v1/balance/sol |
SOL balance |
| GET | /api/v1/balance/token/{mint} |
SPL token balance |
| GET | /api/v1/balance/all |
All balances with asset info |
| GET | /api/v1/assets |
List supported tokens |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/admin/mpc-node |
Register an MPC node |
| POST | /api/v1/admin/generate-aggregated-keys |
Generate aggregated keys from MPC nodes |
| POST | /api/v1/admin/asset |
Add a supported token |
- Rust (1.75+)
- Node.js (18+)
- PostgreSQL (12+)
- Two PostgreSQL databases: one for the main app, one for MPC keypairs
git clone https://github.com/thecurioussailor/tiplink-clone.git
cd tiplink-cloneCreate .env files from the examples:
cp backend/.env.example backend/.env
cp mpc/.env.example mpc/.env
cp indexer/.env.example indexer/.env
cp store/.env.example store/.env
cp mpc-store/.env.example mpc-store/.envbackend/.env
DATABASE_URL=postgresql://user:password@localhost/bonfire
MPC_API_KEY=your-mpc-api-key
JWT_SECRET=your-jwt-secret
SOLANA_RPC_URL=https://api.devnet.solana.commpc/.env
DATABASE_URL=postgresql://user:password@localhost/bonfire_mpc
MPC_PORT=3001
MPC_API_KEY=your-mpc-api-keyindexer/.env
DATABASE_URL=postgresql://user:password@localhost/bonfire
RPC_URL=https://api.devnet.solana.com
INTERVAL_MS=60000
JWT_SECRET=your-jwt-secretMigrations run automatically on startup via SQLx.
# Terminal 1 - Backend
cd backend && cargo run
# Terminal 2 - MPC Node (run multiple with different ports for production)
cd mpc && cargo run
# Terminal 3 - Indexer
cd indexer && cargo run
# Terminal 4 - Frontend
cd frontend && npm install && npm run dev- Create an admin user (set
is_admin = truein the database) - Register MPC nodes via
POST /api/v1/admin/mpc-node - Generate aggregated keys via
POST /api/v1/admin/generate-aggregated-keys - Add supported assets (SOL, USDC, etc.) via
POST /api/v1/admin/asset
Now users can sign up and each will be assigned a wallet automatically.
Two PostgreSQL databases:
Main DB - users, aggregated_keys, assets, balances, transactions, quotes, mpc_nodes
MPC DB - key_pairs (stores individual node keypairs)
Key relationships:
- Each user is assigned one aggregated_key (their wallet)
- Each aggregated_key maps to keypair IDs across all MPC nodes (
node_keypairsJSONB) - Balances are tracked per user per asset, updated by the indexer
- Transactions store on-chain history linked to users
- Private keys are split across 3 MPC nodes - no single point of compromise
- All transaction signing happens server-side through the MPC protocol
- Passwords are hashed with bcrypt
- API authentication uses JWT with 24-hour expiry
- MPC nodes authenticate via API key headers
- Quote ownership is verified before swap execution
MIT