Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "vite",
"build": "tsc -b && vite build && node scripts/inline-css.js && tsx scripts/og.ts && tsx scripts/sitemap.ts && node scripts/gen-rss.mjs",
"og:generate": "tsx scripts/og.ts",
"rss:generate": "tsx scripts/rss.ts",
"preview": "vite preview",
"test": "vitest run",
"test:a11y": "vitest run src/__tests__/a11y.test.tsx",
Expand Down
101 changes: 101 additions & 0 deletions scripts/rss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { writeFileSync, readdirSync, readFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = join(__dirname, '..');
const distDir = join(rootDir, 'dist');
const publicDir = join(rootDir, 'public');
const blogDir = join(rootDir, 'src', 'content', 'blog');
const siteUrl = 'https://usewraith.xyz';

interface PostMetadata {
title: string;
date: string;
author: string;
excerpt: string;
slug: string;
}

function parseFrontmatter(content: string): Partial<PostMetadata> {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return {};
const yaml = match[1];
const metadata: Record<string, string> = {};

yaml.split('\n').forEach((line) => {
const colonIdx = line.indexOf(':');
if (colonIdx > -1) {
const key = line.slice(0, colonIdx).trim();
let value = line.slice(colonIdx + 1).trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
metadata[key] = value;
}
});

return {
title: metadata.title,
date: metadata.date,
author: metadata.author,
excerpt: metadata.excerpt,
};
}

try {
const posts: PostMetadata[] = [];

if (existsSync(blogDir)) {
const files = readdirSync(blogDir).filter((f) => f.endsWith('.mdx'));
for (const file of files) {
const slug = file.replace(/\.mdx$/, '');
const content = readFileSync(join(blogDir, file), 'utf8');
const meta = parseFrontmatter(content);
if (meta.title && meta.date) {
posts.push({
title: meta.title,
date: meta.date,
author: meta.author || 'Wraith Team',
excerpt: meta.excerpt || '',
slug,
});
}
}
}

posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());

const rssFeed = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Wraith Protocol Blog</title>
<link>${siteUrl}/blog</link>
<description>Announcements, technical deep dives, and product updates from Wraith Protocol.</description>
<language>en-us</language>
<atom:link href="${siteUrl}/feed.xml" rel="self" type="application/rss+xml"/>
${posts
.map(
(post) => ` <item>
<title><![CDATA[${post.title}]]></title>
<link>${siteUrl}/blog/${post.slug}</link>
<guid>${siteUrl}/blog/${post.slug}</guid>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
<description><![CDATA[${post.excerpt}]]></description>
</item>`,
)
.join('\n')}
</channel>
</rss>`;

if (existsSync(distDir)) {
writeFileSync(join(distDir, 'feed.xml'), rssFeed, 'utf8');
writeFileSync(join(distDir, 'rss.xml'), rssFeed, 'utf8');
}
writeFileSync(join(publicDir, 'feed.xml'), rssFeed, 'utf8');
writeFileSync(join(publicDir, 'rss.xml'), rssFeed, 'utf8');

console.log(`RSS feed generated successfully with ${posts.length} posts.`);
} catch (error) {
console.error('Failed to generate RSS feed:', error);
}
12 changes: 12 additions & 0 deletions scripts/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ try {
}

const routes = getRoutes(distDir);

const blogDir = join(rootDir, 'src', 'content', 'blog');
if (existsSync(blogDir)) {
const blogFiles = readdirSync(blogDir).filter((f) => f.endsWith('.mdx'));
if (!routes.includes('/blog')) routes.push('/blog');
for (const f of blogFiles) {
const slug = f.replace(/\.mdx$/, '');
const blogRoute = `/blog/${slug}`;
if (!routes.includes(blogRoute)) routes.push(blogRoute);
}
}

const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${routes
Expand Down
34 changes: 34 additions & 0 deletions src/__tests__/blog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import App from '../App';
import { getAllPosts, getPostBySlug } from '../utils/blog';

describe('Blog utility & page', () => {
it('loads blog posts correctly', () => {
const posts = getAllPosts();
expect(posts.length).toBeGreaterThan(0);
expect(posts[0].slug).toBe('wave-7-kickoff');

Check failure on line 10 in src/__tests__/blog.test.tsx

View workflow job for this annotation

GitHub Actions / Lighthouse Audit

Object is possibly 'undefined'.
});

it('retrieves post by slug', () => {
const post = getPostBySlug('wave-7-kickoff');
expect(post).toBeDefined();
expect(post?.title).toContain('Wave 7 Kick-off');
});

it('renders blog index page', async () => {
window.history.replaceState({}, '', '/blog');
render(<App />);

expect(await screen.findByRole('heading', { name: /wraith protocol blog/i, level: 1 })).toBeInTheDocument();
expect(screen.getByText(/wave 7 kick-off/i)).toBeInTheDocument();
});

it('renders blog single post page', async () => {
window.history.replaceState({}, '', '/blog/wave-7-kickoff');
render(<App />);

expect(await screen.findByRole('heading', { name: /wave 7 kick-off/i, level: 1 })).toBeInTheDocument();
expect(screen.getByText(/welcome to/i)).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export default function Footer() {
{ label: t('footer.resources.erc6538'), href: 'https://eips.ethereum.org/EIPS/eip-6538' },
{ label: t('footer.resources.security'), href: 'https://docs.usewraith.xyz/security' },
{ label: t('footer.resources.press'), href: '/press' },
{ label: 'Blog', href: '/blog' },
{ label: 'Stellar Integration', href: '/stellar' },
{ label: 'Careers', href: '/careers' },
{ label: 'About', href: '/about' },
Expand Down
13 changes: 13 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ export default function Header() {
>
{t('header.nav.roadmap')}
</Link>
<Link
to="/blog"
className="font-body text-[13px] text-outline transition-colors duration-150 hover:text-on-surface-variant"
>
{t('header.nav.blog')}
</Link>
</nav>

<div className="hidden items-center gap-3 md:flex">
Expand Down Expand Up @@ -289,6 +295,13 @@ export default function Header() {
>
{t('header.nav.roadmap')}
</Link>
<Link
to="/blog"
onClick={closeMenu}
className="font-body text-[13px] text-outline transition-colors duration-150 hover:text-on-surface-variant"
>
{t('header.nav.blog')}
</Link>
<a
href="https://github.com/wraith-protocol"
target="_blank"
Expand Down
67 changes: 67 additions & 0 deletions src/content/blog/wave-7-kickoff.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: "Wave 7 Kick-off + What We Shipped in Wave 6"
date: "2026-07-27"
author: "Wraith Protocol Team"
excerpt: "Announcing the start of Wave 7 alongside a recap of Wave 6 milestones: EVM stealth transactions, SDK v1.4 release, Stellar ecosystem integrations, and TEE privacy enhancements."
tags: ["announcements", "wave-7", "wave-6", "stealth-payments", "sdk"]
---

Welcome to **Wave 7** of Wraith Protocol development!

Over the past wave, our focus was on expanding privacy guarantees across both EVM and non-EVM chains, improving developer experience with our TypeScript SDK, and strengthening our privacy-preserving infrastructure.

Here is a look back at what we shipped in **Wave 6**, followed by what to expect in **Wave 7**.

---

## 🚀 What We Shipped in Wave 6

### 1. EVM Stealth Address Expansion
We successfully delivered full support for ERC-5564 stealth payment generations across multiple EVM networks. Users can now execute stealth transfers without revealing linked recipient addresses on-chain.

- **Zero Linkability:** Built-in ephemeral pubkey derivation ensures sender-recipient privacy.
- **Gas Optimizations:** Reduced contract interaction overhead by ~25% using streamlined cryptographic primitives.

### 2. SDK v1.4 Release
The `@wraith-protocol/sdk` package received major performance and developer experience updates:
- **Unified Multichain API:** One intuitive interface for EVM chains and Stellar.
- **Automatic Stealth Key Derivation:** Instant generation of stealth addresses with a single function call.
- **Enhanced Type Safety:** Comprehensive TypeScript definitions for all chain adapters.

```typescript
import { buildSendStealth } from '@wraith-protocol/sdk/chains/evm';

const { transaction } = buildSendStealth({
recipientMetaAddress: 'st:eth:0x...',
amount: '0.1',
chain: 'horizen',
});
```

### 3. TEE Privacy Enclave (Spectre) Upgrades
Our hardware enclave architecture—Spectre—received critical security and latency enhancements:
- Attestation verification workflows for remote nodes.
- Faster scanning rates for stealth outputs across supported networks.

---

## 🎯 What's Coming in Wave 7

With Wave 7 officially underway, our primary objectives focus on scaling usability and developer adoption:

1. **Stellar Ecosystem Expansion:** Deepening integration with Soroban smart contracts and expanding grant-funded privacy primitives.
2. **Automated Scanning Agents:** TEE-managed AI agents capable of background balance scanning and stealth token claim automation.
3. **Developer Tools & Console:** Launching key management dashboards and analytics for privacy-focused dApps.
4. **Additional Chain Support:** Expanding our testnets for Solana and Base stealth address bridging.

---

## 🛠️ Get Involved

Wraith Protocol is open source and built for the privacy-first web.

- **Documentation:** Check out the [Wraith Docs](https://docs.usewraith.xyz).
- **GitHub:** Explore our open-source repositories on [GitHub](https://github.com/wraith-protocol).
- **Demo:** Try stealth transactions live on the [Wraith Demo](https://demo.usewraith.xyz).

Stay tuned for regular updates as Wave 7 unfolds!
60 changes: 60 additions & 0 deletions src/utils/blog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { ComponentType } from 'react';

export interface BlogPostFrontmatter {
title: string;
date: string;
author: string;
excerpt: string;
tags: string[];
}

export interface BlogPost {
slug: string;
title: string;
date: string;
author: string;
excerpt: string;
tags: string[];
Component: ComponentType;
}

interface MDXModule {
default: ComponentType;
frontmatter: BlogPostFrontmatter;
}

const modules = import.meta.glob<MDXModule>('/src/content/blog/*.mdx', { eager: true });

export function getAllPosts(): BlogPost[] {
return Object.entries(modules)
.map(([filepath, mod]) => {
const filename = filepath.split('/').pop() || '';
const slug = filename.replace(/\.mdx$/, '');
const frontmatter = mod.frontmatter || {};

return {
slug,
title: frontmatter.title || slug,
date: frontmatter.date || '',
author: frontmatter.author || 'Wraith Team',
excerpt: frontmatter.excerpt || '',
tags: Array.isArray(frontmatter.tags) ? frontmatter.tags : [],
Component: mod.default,
};
})
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
}

export function getPostBySlug(slug: string): BlogPost | undefined {
const posts = getAllPosts();
return posts.find((p) => p.slug === slug);
}

export function getAllTags(): string[] {
const posts = getAllPosts();
const tagSet = new Set<string>();
posts.forEach((post) => {
post.tags.forEach((tag) => tagSet.add(tag));
});
return Array.from(tagSet).sort();
}
13 changes: 13 additions & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
/// <reference types="vite/client" />

declare module '*.mdx' {
import type { ComponentType } from 'react';
export const frontmatter: {
title: string;
date: string;
author: string;
excerpt: string;
tags: string[];
};
const Component: ComponentType;
export default Component;
}
14 changes: 13 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import mdx from '@mdx-js/rollup';
import remarkFrontmatter from 'remark-frontmatter';
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';

export default defineConfig({
plugins: [react(), tailwindcss()],
plugins: [
{
enforce: 'pre',
...mdx({
remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
}),
},
react(),
tailwindcss(),
],
test: {
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
Expand Down
Loading