diff --git a/website/blog/merkle_ai_batching.tsx b/website/blog/merkle_ai_batching.tsx
index b57970bea..db94f187e 100644
--- a/website/blog/merkle_ai_batching.tsx
+++ b/website/blog/merkle_ai_batching.tsx
@@ -1,6 +1,7 @@
import React from "react";
import { css } from "../styled-system/css";
import MermaidDiagram from "../components/MermaidDiagram";
+import { Link } from "../components/Link";
// Simplified LLM Prepaid Workflow with Merkle Batching
const UPDATED_LLM_WORKFLOW_DEFINITION = `sequenceDiagram
@@ -53,7 +54,7 @@ export default function MerkleAIBatching() {
My initial answer is now live:{" "}
-
Feel free to try my AI assistant
- {" "}
+ {" "}
- you connect your wallet, deposit ETH, and chat with an LLM. No subscriptions, no accounts, no data weird
harvesting. And you just pay for exactly what you use.
@@ -113,7 +114,7 @@ export default function MerkleAIBatching() {
Building on Previous Blockchain-AI Experience
- I previously built an AI image generator with blockchain payments, proving that
+ I previously built an AI image generator with blockchain payments, proving that
crypto-native AI services can work. But LLMs present fundamentally different challenges: users expect multiple
requests per session, instant responses, and variable costs per interaction. The traditional "one
transaction per request" model becomes prohibitively expensive and cumbersome.
@@ -133,8 +134,8 @@ export default function MerkleAIBatching() {
After evaluating different approaches, Merkle tree batching emerged as the optimal solution - providing
cryptographic proof of each interaction while enabling efficient batch processing. This approach balances the
competing demands of user experience, cost efficiency, and technical simplicity. I explored this technique in
- detail in my previous post on Merkle tree fundamentals. While that post covered the
- mathematical foundations, here we'll focus on the practical implementation for real-time AI services.
+ detail in my previous post on Merkle tree fundamentals. While that post covered
+ the mathematical foundations, here we'll focus on the practical implementation for real-time AI services.
diff --git a/website/blog/merkle_ai_batching_fundamentals.tsx b/website/blog/merkle_ai_batching_fundamentals.tsx
index fbc53ba64..910a4a20d 100644
--- a/website/blog/merkle_ai_batching_fundamentals.tsx
+++ b/website/blog/merkle_ai_batching_fundamentals.tsx
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
import { css } from "../styled-system/css";
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
import MermaidDiagram from "../components/MermaidDiagram";
+import { Link } from "../components/Link";
// Mermaid diagram definitions
const MERKLE_TREE_MATH_DEFINITION = `graph TD
@@ -1094,7 +1095,7 @@ export default function MerkleAIBatching() {
I've already built an AI image generator that works on the blockchain - you can try it on my{" "}
- image generation page. Users pay with their wallet, and my{" "}
+ image generation page. Users pay with their wallet, and my{" "}
GenImNFT contract
{" "}
diff --git a/website/blog/x402_facilitator_imagegen.mdx b/website/blog/x402_facilitator_imagegen.mdx
index b6c6532bd..878417771 100644
--- a/website/blog/x402_facilitator_imagegen.mdx
+++ b/website/blog/x402_facilitator_imagegen.mdx
@@ -38,7 +38,7 @@ sequenceDiagram
## Introduction
-Over the last year, I've built two AI services with blockchain payments: an [image generator with NFT minting](/blog/9) and an [LLM assistant with Merkle-tree batching](/blog/16). Both work well – users can pay anonymously with crypto, no accounts needed, costs under control.
+Over the last year, I've built two AI services with blockchain payments: an [image generator with NFT minting](/blog/9/) and an [LLM assistant with Merkle-tree batching](/blog/16/). Both work well – users can pay anonymously with crypto, no accounts needed, costs under control.
But each service has its own payment logic. My image generator uses a custom smart contract flow, my LLM assistant uses prepaid deposits. If someone wanted to build a client that uses both, they'd need to understand two completely different payment systems. And if an AI agent wanted to pay for my services autonomously? It would need custom integration for each endpoint.
@@ -178,7 +178,7 @@ Now that we understand the infrastructure, let's see how the actual image genera
## The ImageGen Endpoint
-Part of my website is the imagegen feature, which calls a serverless function to generate AI images via Black Forest Labs and mints an NFT via the GenImNFTv4 contract. The NFT serves as a certificate of authenticity and proof of ownership for the generated image – I've written more about the [original implementation](/blog/9) and the [gallery features](/blog/12) in previous posts. This endpoint is now upgraded to use the x402 standard for payments and accessible to anyone interested under [imagegen-agent.fretchen.eu](https://imagegen-agent.fretchen.eu). How does the endpoint work now?
+Part of my website is the imagegen feature, which calls a serverless function to generate AI images via Black Forest Labs and mints an NFT via the GenImNFTv4 contract. The NFT serves as a certificate of authenticity and proof of ownership for the generated image – I've written more about the [original implementation](/blog/9/) and the [gallery features](/blog/12/) in previous posts. This endpoint is now upgraded to use the x402 standard for payments and accessible to anyone interested under [imagegen-agent.fretchen.eu](https://imagegen-agent.fretchen.eu). How does the endpoint work now?
Using the official x402 TypeScript SDK, the buyer-side code is remarkably simple:
diff --git a/website/components/EntryList.tsx b/website/components/EntryList.tsx
index 0a37650b0..be11365f3 100644
--- a/website/components/EntryList.tsx
+++ b/website/components/EntryList.tsx
@@ -19,13 +19,15 @@ import { SITE } from "../utils/siteData";
*/
const EntryList: React.FC = ({
blogs,
- basePath,
+ basePath: rawBasePath,
titleClassName,
showDate = false,
reverseOrder = false,
limit,
showViewAllLink = false,
}) => {
+ // Normalize basePath: strip trailing slash to prevent double-slash URLs
+ const basePath = rawBasePath.endsWith("/") ? rawBasePath.slice(0, -1) : rawBasePath;
let displayBlogs = reverseOrder ? [...blogs].reverse() : blogs;
const hasMore = limit && blogs.length > limit;
if (limit) {
@@ -39,7 +41,7 @@ const EntryList: React.FC = ({
// This ensures correct link indices when the blog list is filtered by category
const linkIndex =
blog.originalIndex !== undefined ? blog.originalIndex : reverseOrder ? blogs.length - 1 - index : index;
- const entryUrl = `${basePath}/${linkIndex}`;
+ const entryUrl = `${basePath}/${linkIndex}/`;
// Format publishing date as ISO8601 for dt-published if available
const isoDatetime = blog.publishing_date ? new Date(blog.publishing_date).toISOString().split("T")[0] : null;
@@ -114,7 +116,7 @@ const EntryList: React.FC = ({
{/* View All Link */}
{hasMore && showViewAllLink && (
- View all entries →
+ View all entries →
)}
diff --git a/website/components/Link.tsx b/website/components/Link.tsx
index eed6870aa..ec8942e00 100644
--- a/website/components/Link.tsx
+++ b/website/components/Link.tsx
@@ -25,6 +25,11 @@ export function Link({
locale = defaultLocale;
}
+ // Ensure trailing slash for internal page links (not files, hashes, or queries)
+ if (!href.endsWith("/") && !href.includes(".") && !href.includes("#") && !href.includes("?")) {
+ href += "/";
+ }
+
// Only add locale prefix for non-default locale
if (locale !== defaultLocale) {
href = "/" + locale + href;
diff --git a/website/components/MetadataLine.tsx b/website/components/MetadataLine.tsx
index 80e15322b..0a7f0deb1 100644
--- a/website/components/MetadataLine.tsx
+++ b/website/components/MetadataLine.tsx
@@ -35,10 +35,11 @@ export default function MetadataLine({
const { supportCount, isLoading, isSuccess, errorMessage, isConnected, handleSupport, isReadPending, readError } =
useSupportAction(showSupport ? currentUrl : "");
- // Format publishing date
+ // Format publishing date — parse as local time to avoid timezone shifts
const formatDate = (dateString: string) => {
try {
- const date = new Date(dateString);
+ const [year, month, day] = dateString.split("-").map(Number);
+ const date = new Date(year, month - 1, day);
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
diff --git a/website/pages/x402/+Page.tsx b/website/pages/x402/+Page.tsx
index 0565b1104..45b186889 100644
--- a/website/pages/x402/+Page.tsx
+++ b/website/pages/x402/+Page.tsx
@@ -4,6 +4,7 @@ import MermaidDiagram from "../../components/MermaidDiagram";
import { FacilitatorApproval } from "../../components/FacilitatorApproval";
import { titleBar } from "../../layouts/styles";
import * as styles from "../../layouts/styles";
+import { Link } from "../../components/Link";
// ─── Mermaid diagram definitions ─────────────────────────────────────────────
@@ -389,7 +390,7 @@ return new Response(JSON.stringify(result), { status: 200 });`}
That's it — your service now accepts crypto payments. See the{" "}
- agent onboarding guide for a complete walkthrough.
+ agent onboarding guide for a complete walkthrough.
Each payment is individually signed via EIP-3009. The
authorization is bound to a specific amount, recipient, and expiration. The protocol never has blanket access
- to your customer's funds. See the AI Image Generator for a live example.
+ to your customer's funds. See the AI Image Generator for a live example.