NudgeVault is a privacy-preserving smart contract for gifting ERC20 tokens on Base. It allows users to send tokens with encrypted messages to recipients identified by privacy-protecting hashes rather than public addresses.
- Contract Address:
0x69e82Efc1C38379c331b48A0C24597086e806ee8 - Network: Base (Chain ID: 8453)
- Compiler: Solidity ^0.8.20
- License: MIT
- Verified Source: View on Basescan
Recipient Protection
- Recipients identified by
keccak256hash, not public addresses - Hash includes FID + wallet address + unique salt
- Impossible to correlate on-chain activity with specific users
- No plaintext recipient data stored on-chain
Message Encryption
- Messages stored as hashes on-chain
- Full message encrypted off-chain before storage
- Only recipient can decrypt using their private salt
Example Privacy Flow:
Recipient Hash = keccak256(
keccak256(FID + Wallet Address) + Salt
)
Queue Gift (queueGift)
- Send ERC20 tokens with encrypted message
- Tokens locked in contract until claimed
- Supports any ERC20 token
- Message hash stored for verification
Queue Batch Gift (queueGiftBatch)
- Send to multiple recipients in one transaction
- 30-50% gas savings vs individual gifts
- Same privacy guarantees
Claim Gift (claimGift)
- Recipient proves identity with FID + address + salt
- Contract verifies hash matches
- Tokens transferred directly to recipient
- Double-claim protection via dual tracking
Revoke Gift (revokeGift)
- Gifter can revoke unclaimed gifts
- Returns tokens to original sender
- Cannot revoke after claim
Double-Spend Protection
- Dual tracking:
claimedflag +claimedGiftsmapping - Nonce-based replay protection
- CEI (Checks-Effects-Interactions) pattern throughout
Rate Limiting
- Minimum 1 block between gifts per sender
- Maximum 100 pending gifts per recipient
- Prevents spam and DoS attacks
Salt Entropy Requirements
- Minimum salt entropy: 2^128
- Prevents brute force attacks on recipient hashes
- Ensures cryptographic security
Emergency Controls
- One-way pause mechanism (can pause, never unpause)
- Emergency pauser role (recommend multisig)
- Blacklist functionality for bad actors
- No admin functions beyond emergency pause
OpenZeppelin Dependencies
SafeERC20for secure token transfersReentrancyGuardfor reentrancy protectionPausablefor emergency stops- Battle-tested, audited libraries
- Tight struct packing:
uint96for amounts,uint32for timestamps - Efficient storage layout
- Batch operations for multi-recipient gifts
- Minimal storage reads/writes
struct Gift {
address gifter; // Who sent the gift
address token; // ERC20 token address
bytes32 messageHash; // Hash of encrypted message
uint96 amount; // Token amount (packed)
uint32 timestamp; // Creation time (packed)
bool claimed; // Claim status
}
mapping(bytes32 => mapping(uint256 => Gift)) public gifts;
mapping(bytes32 => uint256) public giftCount;| Function | Purpose | Gas Cost |
|---|---|---|
queueGift |
Send single gift | ~150k gas |
queueGiftBatch |
Send batch gifts | ~100k + 50k per recipient |
claimGift |
Claim tokens | ~80k gas |
revokeGift |
Cancel unclaimed gift | ~60k gas |
checkPendingGifts |
View pending gifts | View only (free) |
- OpenZeppelin contracts (industry standard)
- CEI pattern (Checks-Effects-Interactions)
- ReentrancyGuard on all state-changing functions
- SafeERC20 for token transfers
- Application Dependency: Salt management is handled by the Nudge application. Users depend on the app's database for gift claiming.
- Token Approvals: Gifters must approve contract before sending
- Gas Costs: Claiming requires gas fees (paid by recipient)
- Emergency Pause: Once paused, contract cannot be unpaused (by design)
For Gifters:
- Approve exact amount (don't over-approve)
- Verify recipient username before sending
- Use batch operations for multiple recipients
For Recipients:
- Claim gifts promptly (gifter can revoke)
- Verify gift details before claiming
- Ensure you have ETH for gas fees
The Nudge Mini App manages salt generation and storage to provide a seamless user experience:
Salt Management (Server-Side)
- Salts are auto-generated when users first interact with the app
- Stored encrypted in Supabase database with AES-256-GCM
- Associated with user's Farcaster FID
- Never exposed to end users directly
Database Security (Supabase)
- Row Level Security (RLS) policies prevent public access to salt table
- Salts only accessible via authenticated API routes with verified FID
- Service role key required for salt operations (stored in secure env vars)
- Database audit logs track all salt access
- Encrypted at rest and in transit
User Experience
- Users don't manage salts manually
- Claiming works automatically when logged in
- Salt is retrieved server-side during claim flow
- No additional steps or backups required
Security Layers
- Encryption: Salts encrypted with
MESSAGE_ENCRYPTION_KEYbefore storage - RLS Policies: Database-level access control prevents unauthorized reads
- API Authentication: All salt access requires valid Farcaster JWT
- Service Key Protection: Only server-side routes can decrypt salts
Trade-offs
- ✅ Pro: Seamless UX - users don't manage cryptographic keys
- ✅ Pro: Lower barrier to entry for non-technical users
- ✅ Pro: Multiple security layers (encryption + RLS + auth)
⚠️ Con: Users trust Nudge to manage salts securely⚠️ Con: Database unavailability prevents claiming (though gifts remain safe on-chain)
This architecture prioritizes user experience while maintaining strong security through defense-in-depth: encryption, access control, and authentication.
The contract source code is verified and publicly viewable on Basescan. You can:
- Read the contract source code
- Verify bytecode matches source
- View all transactions and events
- Check security features
# Clone the repository
git clone https://github.com/jumpboxtech/nudge.git
cd nudge
# Install dependencies
npm install
# Run tests (coming soon)
npm test1. Queue a Gift
import { parseUnits } from 'viem';
// Calculate recipient hash (server-side with salt)
const recipientHash = keccak256(
encodePacked(
['bytes32', 'bytes32'],
[
keccak256(encodePacked(['uint256', 'address'], [fid, wallet])),
salt
]
)
);
// Approve token
await token.write.approve([VAULT_ADDRESS, amount]);
// Queue gift
await vault.write.queueGift([
recipientHash,
tokenAddress,
parseUnits(amount, decimals),
messageHash
]);2. Claim a Gift
await vault.write.claimGift([
giftId,
fid,
recipientAddress,
salt
]);The full ABI is available in /app/lib/contract.ts or can be viewed on Basescan.
Key events:
GiftQueued- Emitted when gift is createdGiftClaimed- Emitted when gift is claimedGiftRevoked- Emitted when gift is cancelled
- Multi-token batch gifts (different tokens in one tx)
- Gift expiration dates
- Gift conditions (claim after X date)
- NFT support
- Governance token integration
We welcome contributions! Please:
- Fork the repository
- Create a feature branch
- Follow security best practices
- Add tests for new features
- Submit a pull request
Security Disclosure: If you discover a security vulnerability, please email nudge@jumpbox.tech (do NOT create a public issue).
MIT License - see LICENSE.md
- Contract: View on Basescan
This software is provided "as is" without warranty of any kind. Users interact with the smart contract at their own risk. Always verify contract addresses and test with small amounts first.