Year-End Reflections: Shipping Time Capsule and ScholrLink to Production
Dec 30, 2025 · 11 min · Reflections , Web3 , Solana , Product Launch , Year in Review
Year-End Reflections: Shipping Time Capsule and ScholrLink to Production
Introduction
December 2025 brought the year full circle. From contributing to Rust Clippy in January to shipping two production Web3 applications in December, it’s been an incredible journey. This post reflects on the final sprint: launching Time Capsule Protocol and ScholrLink to mainnet, the lessons learned, and what’s next.
Time Capsule Protocol: Production Launch
The Final Polish
Before mainnet, we needed bulletproof reliability:
Security Audit
Partnered with a security firm for smart contract review:
// Before: No reentrancy protection
pub fn reveal_key(ctx: Context<RevealKey>, solution: Vec<u8>) -> Result<()> {
// Verify solution
verify_puzzle_solution(&solution, ctx.accounts.capsule.puzzle_difficulty)?;
// Update state
ctx.accounts.capsule.is_unlocked = true;
ctx.accounts.capsule.decryption_key = Some(solution);
Ok(())
}
// After: Proper state checks and reentrancy guards
pub fn reveal_key(ctx: Context<RevealKey>, solution: Vec<u8>) -> Result<()> {
let capsule = &mut ctx.accounts.capsule;
let clock = Clock::get()?;
// Check time (before any state changes)
require!(
clock.unix_timestamp >= capsule.unlock_timestamp,
ErrorCode::TooEarly
);
// Check not already unlocked (reentrancy guard)
require!(!capsule.is_unlocked, ErrorCode::AlreadyUnlocked);
// Verify solution
let is_valid = verify_puzzle_solution(
&solution,
capsule.puzzle_difficulty,
capsule.created_at,
)?;
require!(is_valid, ErrorCode::InvalidSolution);
// Update state AFTER all checks
capsule.is_unlocked = true;
capsule.decryption_key = Some(solution);
capsule.unlocked_at = Some(clock.unix_timestamp);
// Emit event
emit!(CapsuleUnlocked {
capsule_id: capsule.key(),
unlocked_by: ctx.accounts.solver.key(),
unlock_time: clock.unix_timestamp,
});
Ok(())
}Security Improvements:
- ✅ Checks-Effects-Interactions pattern
- ✅ Comprehensive input validation
- ✅ Reentrancy guards
- ✅ Proper error handling
- ✅ Event emission for monitoring
Optimized Unlock Flow
Made the user experience seamless:
// Before: Manual puzzle solving UI
<button onClick={() => solvePuzzle()}>
Solve Puzzle (This will take 5 minutes)
</button>
// After: Smart background solving with progress
function UnlockFlow({ capsule }: { capsule: TimeCapsule }) {
const [status, setStatus] = useState<UnlockStatus>('ready');
const [progress, setProgress] = useState(0);
const unlock = async () => {
setStatus('solving');
// Solve in web worker (non-blocking)
const worker = new Worker('/puzzle-worker.js');
worker.onmessage = (e) => {
if (e.data.type === 'progress') {
setProgress(e.data.value);
} else if (e.data.type === 'complete') {
submitSolution(e.data.solution);
setStatus('submitting');
}
};
worker.postMessage({
difficulty: capsule.puzzleDifficulty,
createdAt: capsule.createdAt,
});
};
const submitSolution = async (solution: Uint8Array) => {
try {
const program = getProgram();
const tx = await program.methods
.revealKey(Array.from(solution))
.accounts({
solver: wallet.publicKey,
capsule: capsule.pubkey,
})
.rpc();
console.log('Solution submitted:', tx);
setStatus('unlocked');
// Decrypt and display message
const decrypted = decrypt(capsule.encryptedData, solution);
setMessage(decrypted);
} catch (error) {
console.error('Failed to submit solution:', error);
setStatus('error');
}
};
return (
<div className="unlock-flow">
{status === 'ready' && (
<div className="ready-state">
<UnlockIcon className="text-6xl text-primary" />
<h2>Ready to Unlock</h2>
<p>Your capsule is ready to be opened!</p>
<button onClick={unlock} className="btn-primary">
Unlock Capsule
</button>
</div>
)}
{status === 'solving' && (
<div className="solving-state">
<LoadingSpinner />
<h2>Solving Cryptographic Puzzle</h2>
<ProgressBar value={progress} max={100} />
<p>{progress.toFixed(1)}% complete</p>
<p className="text-sm text-gray-500">
This process takes a few minutes and cannot be interrupted
</p>
</div>
)}
{status === 'submitting' && (
<div className="submitting-state">
<LoadingSpinner />
<h2>Submitting to Blockchain</h2>
<p>Confirming transaction...</p>
</div>
)}
{status === 'unlocked' && (
<div className="unlocked-state">
<CheckCircleIcon className="text-6xl text-success" />
<h2>Unlocked!</h2>
<div className="message-container">
<p className="message">{message}</p>
</div>
<button onClick={shareUnlock} className="btn-secondary">
Share Unlock
</button>
</div>
)}
{status === 'error' && (
<div className="error-state">
<ErrorIcon className="text-6xl text-error" />
<h2>Unlock Failed</h2>
<p>There was an error unlocking your capsule.</p>
<button onClick={unlock} className="btn-primary">
Try Again
</button>
</div>
)}
</div>
);
}Future Message Presets
Added quick-start templates:
const PRESETS: Record<string, CapsulePreset> = {
'future-me-1-year': {
title: 'Message to Future Me (1 Year)',
duration: 365 * 24 * 60 * 60,
difficulty: 18,
prompt: 'What advice would you give yourself in one year?',
suggestions: [
'Reflect on your current goals',
'Share your hopes and fears',
'Include predictions about your life',
'Write about what matters most to you',
],
},
'birthday-surprise': {
title: 'Birthday Surprise',
duration: null, // User picks date
difficulty: 16,
prompt: "Write a birthday message to be revealed on their special day",
suggestions: [
'Share a favorite memory together',
'Express what they mean to you',
'Include a photo or special quote',
],
},
'new-years-resolution': {
title: "New Year's Time Capsule",
duration: 365 * 24 * 60 * 60,
difficulty: 18,
prompt: "What do you want to accomplish this year?",
suggestions: [
'Set clear, achievable goals',
'Describe your vision for the year',
'Track your progress throughout',
'Reflect on last year\'s growth',
],
},
'project-launch': {
title: 'Project Launch Countdown',
duration: 90 * 24 * 60 * 60, // 3 months
difficulty: 17,
prompt: 'Message to reveal at your project launch',
suggestions: [
'Document your journey',
'Share your vision and goals',
'Include metrics you want to hit',
'Motivate your future self',
],
},
};
function PresetSelector({ onSelect }: PresetSelectorProps) {
return (
<div className="grid grid-cols-2 gap-4">
{Object.entries(PRESETS).map(([key, preset]) => (
<Card
key={key}
className="cursor-pointer hover:shadow-lg transition"
onClick={() => onSelect(preset)}
>
<h3 className="font-bold">{preset.title}</h3>
<p className="text-sm text-gray-600">{preset.prompt}</p>
{preset.duration && (
<p className="text-xs text-gray-500 mt-2">
Unlocks in {Math.round(preset.duration / (24 * 60 * 60))} days
</p>
)}
</Card>
))}
</div>
);
}Launch Metrics (First Week)
Total Capsules Created: 234
Total Value Locked: 12.3 SOL (~$2,460)
Average Unlock Duration: 37 days
Most Popular Preset: "Message to Future Me (1 Year)"
User Retention: 67% (came back after creating first capsule)
User Stories
The capsules people created were incredible:
“I created a time capsule for my son’s 18th birthday. It contains advice, family photos, and stories about his childhood. He’ll unlock it in 12 years.” - Sarah M.
“Used it to lock away my startup idea for 3 months while I validate the market. Forces me to actually do the research instead of jumping straight to building.” - Alex T.
“Made a capsule with my New Year’s resolutions. Coming back to unlock it next January will be powerful.” - Jamie L.
ScholrLink: Feature Rollout
Blink-Based One-Tap Funding
The killer feature shipped perfectly:
// Final Blink implementation
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { project, amount } = req.query;
// Enhanced metadata
if (req.method === 'GET') {
const projectData = await getProject(project as string);
const stats = await getProjectStats(project as string);
return res.json({
type: 'action',
icon: projectData.image,
title: `Fund ${projectData.title}`,
description: `${projectData.description}\n\n` +
`🎯 Goal: ${projectData.goalAmount} SOL\n` +
`📊 Raised: ${stats.raisedAmount} SOL (${stats.percentage}%)\n` +
`👥 Supporters: ${stats.supporterCount}`,
links: {
actions: [
{
label: `Fund 0.1 SOL`,
href: `/api/blink/fund?project=${project}&amount=0.1`,
},
{
label: `Fund 0.5 SOL`,
href: `/api/blink/fund?project=${project}&amount=0.5`,
},
{
label: `Fund 1 SOL`,
href: `/api/blink/fund?project=${project}&amount=1`,
},
{
label: 'Custom Amount',
href: `/api/blink/fund?project=${project}&amount={amount}`,
parameters: [{
name: 'amount',
label: 'Amount in SOL',
required: true,
}],
},
],
},
});
}
// Transaction creation with error handling
if (req.method === 'POST') {
try {
const { account } = req.body;
// Validate input
if (!account) {
return res.status(400).json({
error: 'Wallet account required',
});
}
const amountSol = parseFloat(amount as string);
if (isNaN(amountSol) || amountSol <= 0) {
return res.status(400).json({
error: 'Invalid amount',
});
}
// Create transaction
const tx = await createFundingTransaction({
from: new PublicKey(account),
project: project as string,
amount: amountSol,
});
// Serialize
const serialized = tx.serialize({
requireAllSignatures: false,
verifySignatures: false,
});
return res.json({
transaction: serialized.toString('base64'),
message: `Funding ${amountSol} SOL`,
});
} catch (error) {
console.error('[Blink Error]', error);
return res.status(500).json({
error: handleBlinkError(error).userMessage,
});
}
}
return res.status(405).json({ error: 'Method not allowed' });
}Soulbound Patron Badges
Badges now show in profile:
function PatronBadges({ userId }: { userId: string }) {
const { data: badges, isLoading } = useQuery(
['patron-badges', userId],
() => getUserBadges(userId)
);
if (isLoading) return <LoadingSkeleton />;
return (
<div className="badges-grid">
<h3 className="text-xl font-bold mb-4">Patron Badges</h3>
<div className="grid grid-cols-3 gap-4">
{badges?.map((badge) => (
<BadgeCard
key={badge.projectId}
badge={badge}
className="hover:scale-105 transition"
/>
))}
</div>
{badges?.length === 0 && (
<div className="empty-state">
<p>No patron badges yet</p>
<Link href="/explore">
<button className="btn-primary">Explore Projects</button>
</Link>
</div>
)}
</div>
);
}
function BadgeCard({ badge }: { badge: PatronBadge }) {
return (
<div className="badge-card">
<BadgeIcon tier={badge.tier} className="w-16 h-16" />
<h4 className="font-bold mt-2">{badge.projectTitle}</h4>
<p className="text-sm text-gray-600">
{badge.amount} SOL contributed
</p>
<span className="badge-tier">{badge.tier} Patron</span>
</div>
);
}Launch Metrics (First Month)
Total Projects: 89
Total Funding: 156 SOL (~$31,200)
Average Funding per Project: 1.75 SOL
Blink Conversion Rate: 12.3%
Repeat Funders: 28%
Badge Distribution:
- Bronze: 67%
- Silver: 24%
- Gold: 7%
- Platinum: 2%
Success Stories
Projects that found success:
“Raised 15 SOL for my open-source Rust library. The soulbound badges created a real community of supporters who stay engaged.” - Dev making RustCrypto library
“ScholrLink helped me fund my computer science degree. 43 patrons believed in my vision. Now I’m paying it forward.” - CS student
“The one-tap funding via Twitter was game-changing. Shared my project once and got 8 supporters within an hour.” - Indie game developer
Unified Dashboard
Built a central hub for both projects:
function Dashboard() {
const { capsules } = useTimeCapsules();
const { projects, badges } = useScholrLink();
return (
<div className="dashboard">
<header>
<h1>Your Web3 Dashboard</h1>
<WalletButton />
</header>
<div className="grid grid-cols-2 gap-8">
{/* Time Capsule Section */}
<section>
<h2>⏰ Time Capsules</h2>
<div className="stats">
<Stat label="Active" value={capsules.active.length} />
<Stat label="Unlocked" value={capsules.unlocked.length} />
<Stat label="Upcoming" value={capsules.upcoming.length} />
</div>
<div className="capsules-list">
{capsules.active.map(c => (
<CapsuleCard key={c.id} capsule={c} />
))}
</div>
<Link href="/capsules/create">
<button className="btn-primary">+ New Capsule</button>
</Link>
</section>
{/* ScholrLink Section */}
<section>
<h2>🎓 ScholrLink</h2>
<div className="stats">
<Stat label="Projects" value={projects.length} />
<Stat label="Raised" value={`${totalRaised} SOL`} />
<Stat label="Badges" value={badges.length} />
</div>
<div className="projects-list">
{projects.map(p => (
<ProjectCard key={p.id} project={p} />
))}
</div>
<Link href="/projects/create">
<button className="btn-primary">+ New Project</button>
</Link>
</section>
</div>
</div>
);
}Technical Debt Paydown
Code Quality Improvements
// Before: Scattered error handling
try {
const result = await something();
return result;
} catch (e) {
console.error(e);
}
// After: Centralized error handling
import { AppError, ErrorCode } from '@/lib/errors';
try {
const result = await something();
return result;
} catch (error) {
throw AppError.from(error, {
code: ErrorCode.OPERATION_FAILED,
context: { operation: 'something' },
});
}Performance Monitoring
Added comprehensive monitoring:
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
beforeSend(event) {
// Filter sensitive data
if (event.request) {
delete event.request.cookies;
}
return event;
},
});
// Track key metrics
export function trackMetric(name: string, value: number, tags?: Record<string, string>) {
Sentry.metrics.distribution(name, value, { tags });
}
// Usage
trackMetric('capsule.unlock.duration', unlockDuration, {
difficulty: capsule.difficulty,
});
trackMetric('blink.conversion', 1, {
source: 'twitter',
});2025: Year in Review
Projects Shipped
- Rust Clippy Contribution (January)
- Jam JavaScript Toolchain (February)
- Video Automation Pipeline (April)
- DedCore v1.0 (June)
- Time Capsule Protocol (September → December)
- ScholrLink (October → December)
- RustyTasks (November)
- Validator Monitoring System (November)
Technical Growth
Languages Mastered:
- Rust (from beginner to production)
- Solana/Anchor (smart contracts)
- TypeScript/Next.js (full-stack Web3)
Skills Acquired:
- Blockchain development
- Cryptographic protocols
- Performance optimization
- System design at scale
- Open source contribution
Community Impact
- 7 open source projects
- 1 merged PR to rust-lang
- 500+ users across projects
- $35k+ in value locked
- Active Discord community
Blog Posts Written
- 11 technical deep-dives (12 including this one!)
- Topics from Rust to Web3
- Shared learnings from real projects
- Helped others on their journey
Lessons from 2025
1. Ship Early, Iterate Fast
Don’t wait for perfection. Time Capsule’s v1 was rough, but user feedback made v2 incredible.
2. Performance Matters More Than You Think
Users notice 500ms delays. Invest in optimization early.
3. Open Source Builds Trust
Open-sourcing DedCore and RustyTasks built credibility and community.
4. Documentation is a Feature
Good docs convert users. Poor docs lose them.
5. Build in Public
Sharing the journey created opportunities I never expected.
What’s Next: 2026 Goals
Q1 2026
- Mobile Apps: Time Capsule and ScholrLink on iOS/Android
- Cross-Chain: Expand beyond Solana
- Enterprise: Time Capsule for businesses
Q2 2026
- DAO Tooling: Governance features for ScholrLink
- Advanced Encryption: Zero-knowledge proofs
- Scale: 10k+ daily active users
Q3 2026
- API Platform: Let others build on our protocols
- Education: Course on Solana development
- Partnerships: Integrate with major platforms
Q4 2026
- Fundraise: Build a sustainable business
- Team: Hire first employees
- Ecosystem: Foster developer community
Final Thoughts
2025 was transformative. From learning Rust basics to shipping production Web3 apps, every month brought new challenges and growth.
The key was consistency: showing up every day, building in public, learning from failures, and celebrating small wins.
To everyone who used my projects, gave feedback, or followed along—thank you. You made this journey meaningful.
Here’s to 2026. Let’s build something amazing.
Follow the journey: mxnish.me | Time Capsule | ScholrLink