Skip to content
Merged
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
18 changes: 9 additions & 9 deletions public/feed.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
<link>https://usewraith.xyz/blog</link>
<description>Notes on stealth payments, private infrastructure, and the Wraith ecosystem.</description>
<language>en-us</language>
<lastBuildDate>Sat, 08 Aug 2026 18:36:39 GMT</lastBuildDate>
<lastBuildDate>Tue, 25 Aug 2026 05:26:09 GMT</lastBuildDate>
<atom:link href="https://usewraith.xyz/feed.xml" rel="self" type="application/rss+xml" />

<item>
<title>How Stealth Addresses Keep Payments Private</title>
<link>https://usewraith.xyz/blog/stealth-addresses-explained</link>
<guid>https://usewraith.xyz/blog/stealth-addresses-explained</guid>
<pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate>
<description>A look at the cryptography behind stealth addresses and why every Wraith payment lands on a fresh one-time address that cannot be linked to the recipient.</description>
<author>Lena Vogt</author>
</item>
<item>
<title>Wave 7 Kick-off + What We Shipped in Wave 6</title>
<link>https://usewraith.xyz/blog/wave-7-kickoff</link>
<guid>https://usewraith.xyz/blog/wave-7-kickoff</guid>
<pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate>
<description>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.</description>
<author>Wraith Protocol Team</author>
</item>
<item>
<title>Stealth addresses explained</title>
<link>https://usewraith.xyz/blog/stealth-addresses-explained</link>
<guid>https://usewraith.xyz/blog/stealth-addresses-explained</guid>
<pubDate>Wed, 22 Jul 2026 12:00:00 GMT</pubDate>
<description>A straightforward introduction to stealth addresses and why they matter.</description>
<author>Wraith Team</author>
</item>
<item>
Expand Down
32 changes: 19 additions & 13 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,85 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://usewraith.xyz</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>daily</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://usewraith.xyz/faq</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/privacy</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/use-cases</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/roadmap</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/case-studies</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/stellar</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/careers</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/press</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/case-studies/payroll-processor</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://usewraith.xyz/blog/author/lena-vogt</loc>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/blog</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/compare</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://usewraith.xyz/newsletter</loc>
<lastmod>2026-08-08</lastmod>
<lastmod>2026-08-25</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
Expand Down
28 changes: 26 additions & 2 deletions scripts/gen-rss.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ function toRfc822(date) {
return new Date(date).toUTCString();
}

let authorsCache = null;
let optOutCache = null;

function resolveAuthorName(authorId) {
if (!authorId) return 'Wraith Team';
try {
if (!authorsCache) {
const aPath = join(rootDir, 'src', 'data', 'authors.json');
authorsCache = existsSync(aPath) ? JSON.parse(readFileSync(aPath, 'utf8')) : {};
}
if (!optOutCache) {
const oPath = join(rootDir, 'src', 'data', 'authors-optout.json');
optOutCache = existsSync(oPath) ? JSON.parse(readFileSync(oPath, 'utf8')) : [];
}
} catch {
return authorId;
}
if (optOutCache.includes(authorId)) return 'Wraith Team';
const author = authorsCache[authorId];
if (author && author.optIn) return author.name;
if (author && !author.optIn) return 'Wraith Team';
return authorId;
}

function parseFrontmatter(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return { metadata: {}, content };
Expand Down Expand Up @@ -66,7 +90,7 @@ export function getPosts() {
excerpt: metadata.excerpt ?? '',
content: content ?? '',
publishedAt: metadata.publishedAt || metadata.date,
author: metadata.author ?? 'Wraith Protocol',
author: resolveAuthorName(metadata.author),
url: metadata.url ?? `${siteUrl}/blog/${slug}`,
});
}
Expand All @@ -87,7 +111,7 @@ export function getPosts() {
excerpt: post.excerpt ?? '',
content: post.content ?? '',
publishedAt: post.publishedAt || post.date,
author: post.author ?? 'Wraith Protocol',
author: resolveAuthorName(post.author),
url: post.url ?? `${siteUrl}/blog/${post.slug}`,
});
}
Expand Down
27 changes: 24 additions & 3 deletions scripts/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ function getCaseStudyRoutes(): string[] {
return routes;
}

function getAuthorRoutes(): string[] {
const routes: string[] = [];
const authorsPath = join(rootDir, 'src', 'data', 'authors.json');
const optOutPath = join(rootDir, 'src', 'data', 'authors-optout.json');
if (!existsSync(authorsPath)) return routes;
try {
const authors = JSON.parse(readFileSync(authorsPath, 'utf8'));
const optOut = existsSync(optOutPath) ? JSON.parse(readFileSync(optOutPath, 'utf8')) : [];
for (const [id, author] of Object.entries(authors)) {
if (optOut.includes(id)) continue;
if ((author as { optIn?: boolean }).optIn) {
routes.push(`/blog/author/${id}`);
}
}
} catch {
// ignore
}
return routes;
}

function getRoutes(dir: string, base = ''): string[] {
const routes: string[] = [];
if (!existsSync(dir)) {
Expand All @@ -61,10 +81,11 @@ function getRoutes(dir: string, base = ''): string[] {

try {
const csRoutes = getCaseStudyRoutes();
const authorRoutes = getAuthorRoutes();
const distRoutes = existsSync(distDir) ? getRoutes(distDir) : [];
const allRoutes = Array.from(new Set([...knownRoutes, ...csRoutes, ...distRoutes])).filter(
(r) => r && r !== '/404' && !r.includes('/staging') && !r.includes('/preview'),
);
const allRoutes = Array.from(
new Set([...knownRoutes, ...csRoutes, ...authorRoutes, ...distRoutes]),
).filter((r) => r && r !== '/404' && !r.includes('/staging') && !r.includes('/preview'));

const today = new Date().toISOString().split('T')[0];

Expand Down
8 changes: 8 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ export default function App() {
</Layout>
}
/>
<Route
path="/blog/author/:authorId"
element={
<Layout>
<Blog />
</Layout>
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
Expand Down
39 changes: 38 additions & 1 deletion src/__tests__/blog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ describe('Blog utility & page', () => {
const posts = getAllPosts();
expect(posts.length).toBeGreaterThan(0);
expect(posts[0]).toBeDefined();
expect(posts[0]?.slug).toBe('wave-7-kickoff');
expect(posts.some((p) => p.slug === 'wave-7-kickoff')).toBe(true);
const dates = posts.map((p) => new Date(p.date).getTime());
const sortedDesc = dates.every((d, i) => i === 0 || (dates[i - 1] ?? 0) >= d);
expect(sortedDesc).toBe(true);
});

it('retrieves post by slug', () => {
Expand Down Expand Up @@ -36,4 +39,38 @@ describe('Blog utility & page', () => {
).toBeInTheDocument();
expect(screen.getByText(/welcome to/i)).toBeInTheDocument();
});

it('collapses opted-out author to "Wraith Team" in byline', async () => {
window.history.replaceState({}, '', '/blog');
render(<App />);

expect(await screen.findByText(/wraith team/i)).toBeInTheDocument();
expect(screen.queryByRole('link', { name: /wraith team/i })).not.toBeInTheDocument();
});

it('links opted-in author byline to the author page', async () => {
window.history.replaceState({}, '', '/blog');
render(<App />);

const byline = await screen.findByRole('link', { name: /lena vogt/i });
expect(byline).toHaveAttribute('href', '/blog/author/lena-vogt');
});

it('renders a public author page for an opted-in author', async () => {
window.history.replaceState({}, '', '/blog/author/lena-vogt');
render(<App />);

expect(
await screen.findByRole('heading', { name: /lena vogt/i, level: 1 }),
).toBeInTheDocument();
expect(screen.getByText(/how stealth addresses keep payments private/i)).toBeInTheDocument();
});

it('falls back gracefully for an unknown author id', async () => {
window.history.replaceState({}, '', '/blog/author/does-not-exist');
render(<App />);

expect(await screen.findByText(/author not found/i)).toBeInTheDocument();
expect(screen.getByText(/back to blog/i)).toBeInTheDocument();
});
});
2 changes: 1 addition & 1 deletion src/__tests__/rss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('gen-rss script', () => {
const mdxPost = posts.find((p) => p.slug === 'wave-7-kickoff');
expect(mdxPost).toBeDefined();
expect(mdxPost?.title).toContain('Wave 7 Kick-off');
expect(mdxPost?.author).toBe('Wraith Protocol Team');
expect(mdxPost?.author).toBe('Wraith Team');
expect(mdxPost?.publishedAt).toBe('2026-07-27');
});

Expand Down
19 changes: 19 additions & 0 deletions src/content/blog/stealth-addresses-explained.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: 'How Stealth Addresses Keep Payments Private'
date: '2026-08-12'
author: 'lena-vogt'
excerpt: 'A look at the cryptography behind stealth addresses and why every Wraith payment lands on a fresh one-time address that cannot be linked to the recipient.'
tags: ['cryptography', 'stealth-payments', 'privacy']
---

Stealth addresses are the foundation of Wraith Protocol's privacy guarantees.

When a sender makes a payment, the SDK derives a fresh, single-use address from the recipient's meta-address. Funds arrive at an address that has never appeared on-chain before, so outside observers cannot link the payment to the recipient's public identity.

This post walks through:

- How meta-addresses are constructed from spend and view public keys
- Why the recipient — and only the recipient — can scan and recover the funds
- How the same scheme maps cleanly onto both EVM and Stellar

The result is private payments that require no interaction from the recipient and no changes to the underlying chain's consensus.
2 changes: 1 addition & 1 deletion src/content/blog/wave-7-kickoff.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: 'Wave 7 Kick-off + What We Shipped in Wave 6'
date: '2026-07-27'
author: 'Wraith Protocol Team'
author: 'wraith-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']
---
Expand Down
36 changes: 36 additions & 0 deletions src/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,39 @@ Drives the Showcase component on the homepage.
### Schema

Contains `items` with `title`, `description`, and optional `link` and `tags`.

## `authors.json`

Powers blog author bylines and the `/blog/author/:id` author pages.

MDX posts reference an author by `id` in their frontmatter (`author: 'lena-vogt'`). The id maps to an entry in this file.

### Schema

```typescript
interface AuthorsData {
[id: string]: {
/** Display name shown in the byline and on the author page */
name: string;
/** Short biography shown on the author page */
bio: string;
/** Optional avatar URL; if omitted, initials are rendered */
avatar?: string;
/** Optional profile links rendered on the author page */
links?: {
website?: string;
github?: string;
twitter?: string;
email?: string;
};
/** When false the author collapses to "Wraith Team" and has no public page */
optIn: boolean;
};
}
```

Opted-out authors (those in `authors-optout.json` or with `optIn: false`) collapse to the "Wraith Team" label in post bylines and never generate a page. Unknown author ids fall back gracefully to the raw string with no link.

## `authors-optout.json`

A flat array of author ids that should never get a public author page and collapse to "Wraith Team" in bylines.
1 change: 1 addition & 0 deletions src/data/authors-optout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["wraith-team"]
22 changes: 22 additions & 0 deletions src/data/authors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"wraith-team": {
"name": "Wraith Protocol Team",
"bio": "The core team building private, multichain stealth payments infrastructure at Wraith Protocol.",
"avatar": "",
"links": {
"website": "https://usewraith.xyz",
"github": "https://github.com/wraith-protocol"
},
"optIn": false
},
"lena-vogt": {
"name": "Lena Vogt",
"bio": "Cryptography researcher focused on stealth address schemes and zero-knowledge payments at Wraith Protocol.",
"avatar": "",
"links": {
"github": "https://github.com/wraith-protocol",
"twitter": "https://twitter.com/wraithprotocol"
},
"optIn": true
}
}
Loading
Loading