diff --git a/.gitignore b/.gitignore index 8a79af47869..197fc213fe7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ pnpm-lock.yaml.bak next-env.d.ts # rss feeds -feed.xml +/feed.xml # Sitemaps sitemap*.xml diff --git a/app/[locale]/developers/page.tsx b/app/[locale]/developers/page.tsx index 18bcc707f84..9fb47300afc 100644 --- a/app/[locale]/developers/page.tsx +++ b/app/[locale]/developers/page.tsx @@ -29,11 +29,14 @@ import { VStack } from "@/components/ui/flex" import Link from "@/components/ui/Link" import InlineLink from "@/components/ui/Link" import { Section } from "@/components/ui/section" +import { TagsInlineText } from "@/components/ui/tag" import { TerminalTypewriter } from "@/components/ui/terminal-typewriter" +import { getBlogFallbackHero } from "@/lib/utils/blog" import { cn } from "@/lib/utils/cn" import { getAppPageContributorInfo } from "@/lib/utils/contributors" -import { formatDateRange } from "@/lib/utils/date" +import { formatDate, formatDateRange } from "@/lib/utils/date" +import { getBlogPostsData } from "@/lib/utils/md" import { getMetadata } from "@/lib/utils/metadata" import { screens } from "@/lib/utils/screen" @@ -52,6 +55,7 @@ import tutorialTagsBanner from "@/public/images/developers/tutorial-tags-banner. import dogeImage from "@/public/images/doge-computer.png" import fallbackThumbnail from "@/public/images/eth-glyph-thumbnail.png" import heroImage from "@/public/images/heroes/developers-hub-hero.png" + const H3 = (props: ChildOnlyProp) =>

const Text = (props: ChildOnlyProp) =>

@@ -140,6 +144,8 @@ const DevelopersPage = async (props: { params: Promise }) => { const hackathons = (await getHackathons()).slice(0, 5) + const recentPosts = (await getBlogPostsData(locale)).slice(0, 3) + const { contributors } = await getAppPageContributorInfo( "developers", locale as Lang @@ -459,6 +465,85 @@ const DevelopersPage = async (props: { params: Promise }) => { + {recentPosts.length > 0 && ( +

+

{t("page-developers-blog-title")}

+

{t("page-developers-blog-desc")}

+ + + {recentPosts.map((post) => ( + + + + + {post.image ? ( + + ) : ( + + )} + + + + + {post.title} + + + + {post.description} + + + + + {formatDate(post.published, locale)} + + + + + ))} + + +
+ + {t("page-developers-blog-view-all")} + +
+
+ )} +
= { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", +} + +const escapeXml = (value: string): string => + value.replace(/[&<>"']/g, (c) => XML_ESCAPE[c]) + +export async function GET( + _: NextRequest, + { params }: { params: Promise<{ locale: string }> } +) { + const { locale } = await params + + const t = await getTranslations({ + locale, + namespace: "page-latest", + }) + + const posts = await getBlogPostsData(locale) + + // Strip trailing slash for the .xml file URL (getFullUrl appends one for + // directory-style URLs, but feed.xml is a file). + const feedUrl = getFullUrl(locale, "/latest/feed.xml").replace(/\/$/, "") + const channelLink = getFullUrl(locale, "/latest/") + const channelTitle = t("page-latest-title") + const channelDescription = t("page-latest-subtitle") + + const items = posts + .map((post) => { + const link = getFullUrl(locale, post.href) + const pubDate = new Date(post.published).toUTCString() + const creator = post.author + ? `${escapeXml(post.author)}` + : "" + const categories = (post.tags ?? []) + .map((tag) => `${escapeXml(tag)}`) + .join("") + return [ + "", + `${escapeXml(post.title)}`, + `${link}`, + `${link}`, + `${escapeXml(post.description)}`, + creator, + `${pubDate}`, + categories, + "", + ].join("") + }) + .join("") + + const lastBuildDate = new Date().toUTCString() + const xml = [ + '', + '', + "", + `${escapeXml(channelTitle)}`, + `${channelLink}`, + ``, + `${escapeXml(channelDescription)}`, + `${locale}`, + `${lastBuildDate}`, + items, + "", + "", + ].join("") + + return new Response(xml, { + headers: { + "Content-Type": "application/rss+xml; charset=utf-8", + }, + }) +} diff --git a/app/[locale]/latest/page-jsonld.tsx b/app/[locale]/latest/page-jsonld.tsx new file mode 100644 index 00000000000..52252b4fbc7 --- /dev/null +++ b/app/[locale]/latest/page-jsonld.tsx @@ -0,0 +1,82 @@ +import type { BlogPost, FileContributor } from "@/lib/types" + +import PageJsonLD from "@/components/PageJsonLD" + +import { normalizeUrlForJsonLd } from "@/lib/utils/url" + +import { BASE_GRAPH_NODES } from "@/lib/jsonld/constants" +import { REFERENCE } from "@/lib/jsonld/references" + +export default async function BlogPageJsonLD({ + locale, + blogPosts, + contributors, +}: { + locale: string + blogPosts: BlogPost[] + contributors: FileContributor[] +}) { + const url = normalizeUrlForJsonLd(locale, "/latest") + + const contributorList = contributors.map((contributor) => ({ + "@type": "Person", + name: contributor.login, + url: contributor.html_url, + })) + + const blogPostItems = blogPosts.map((post, index) => ({ + "@type": "ListItem", + position: index + 1, + url: normalizeUrlForJsonLd(locale, post.href), + name: post.title, + })) + + const jsonLd = { + "@context": "https://schema.org", + "@graph": [ + ...BASE_GRAPH_NODES, + { + "@type": "CollectionPage", + "@id": url, + name: "Builder updates", + description: + "Builder resources, tools, and developments from the Ethereum ecosystem.", + url, + inLanguage: locale, + isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE, + publisher: REFERENCE.ETHEREUM_FOUNDATION, + contributor: contributorList, + breadcrumb: { + "@type": "BreadcrumbList", + itemListElement: [ + { + "@type": "ListItem", + position: 1, + name: "Home", + item: normalizeUrlForJsonLd(locale, "/"), + }, + { + "@type": "ListItem", + position: 2, + name: "Developers", + item: normalizeUrlForJsonLd(locale, "/developers"), + }, + { + "@type": "ListItem", + position: 3, + name: "Builder updates", + item: url, + }, + ], + }, + mainEntity: { + "@type": "ItemList", + numberOfItems: blogPosts.length, + itemListElement: blogPostItems, + }, + }, + ], + } + + return +} diff --git a/app/[locale]/latest/page.tsx b/app/[locale]/latest/page.tsx new file mode 100644 index 00000000000..7bd7af20f81 --- /dev/null +++ b/app/[locale]/latest/page.tsx @@ -0,0 +1,163 @@ +import { pick } from "lodash" +import { + getMessages, + getTranslations, + setRequestLocale, +} from "next-intl/server" + +import type { Lang, PageParams } from "@/lib/types" + +import Emoji from "@/components/Emoji" +import FeedbackCard from "@/components/FeedbackCard" +import ContentHero from "@/components/Hero/ContentHero" +import I18nProvider from "@/components/I18nProvider" +import MainArticle from "@/components/MainArticle" +import { BaseLink } from "@/components/ui/Link" +import { Tag } from "@/components/ui/tag" + +import { getAppPageContributorInfo } from "@/lib/utils/contributors" +import { getBlogPostsData } from "@/lib/utils/md" +import { getMetadata } from "@/lib/utils/metadata" +import { getLocaleTimestamp } from "@/lib/utils/time" +import { getRequiredNamespacesForPage } from "@/lib/utils/translations" +import { getFullUrl } from "@/lib/utils/url" + +import BlogPageJsonLD from "./page-jsonld" + +import heroImg from "@/public/images/developers/blog/latest-landing-hero.png" + +const publishedDate = (locale: Lang, published: string) => { + const localeTimestamp = getLocaleTimestamp(locale, published) + + return localeTimestamp !== "Invalid Date" ? ( + + + {localeTimestamp} + + ) : null +} + +const Page = async (props: { params: Promise }) => { + const params = await props.params + const { locale } = params + + setRequestLocale(locale) + + const t = await getTranslations("page-latest") + + // Get i18n messages + const allMessages = await getMessages({ locale }) + const requiredNamespaces = getRequiredNamespacesForPage("/latest") + const messages = pick(allMessages, requiredNamespaces) + + const blogPosts = await getBlogPostsData(locale) + + const { contributors } = await getAppPageContributorInfo( + "latest", + locale as Lang + ) + + return ( + <> + + + + +
+ {blogPosts.length === 0 ? ( +

+ {t("page-latest-no-posts")} +

+ ) : ( + blogPosts.map((post) => ( + +

+ {post.title} +

+

+ + {post.author} + {post.team ? <> • {post.team} : null} + {post.published ? ( + <> •{publishedDate(locale as Lang, post.published)} + ) : null} + {post.timeToRead ? ( + <> + {" "} + • + + {t("page-latest-minute-read", { + minutes: post.timeToRead, + })} + + ) : null} +

+

{post.description}

+
+ {post.tags?.map((tag) => ( + + {tag} + + ))} +
+
+ )) + )} +
+ + +
+
+ + ) +} + +export async function generateMetadata(props: { + params: Promise<{ locale: string }> +}) { + const params = await props.params + const { locale } = params + + const t = await getTranslations("page-latest") + + const metadata = await getMetadata({ + locale, + slug: ["latest"], + title: t("page-latest-title"), + description: t("page-latest-meta-description"), + }) + + return { + ...metadata, + alternates: { + ...(metadata.alternates ?? {}), + types: { + "application/rss+xml": getFullUrl(locale, "/latest/feed.xml").replace( + /\/$/, + "" + ), + }, + }, + } +} + +export default Page diff --git a/next.config.js b/next.config.js index 5ab40c4448c..b66c8e27c4c 100644 --- a/next.config.js +++ b/next.config.js @@ -259,6 +259,34 @@ module.exports = (phase) => { ".manifests", ], }, + /** + * Re-include MD that hub/index routes enumerate at runtime. These pages + * read from `public/content/` and may be opted into ISR via other data + * getters; without per-route includes the global `public/content` exclude + * above leaves those reads empty inside the Netlify function. + * + * Keys match the normalized App Router route via picomatch with + * `contains: true`. Bracketed segments are picomatch character classes, + * so the `[locale]` brackets must be escaped to match literally. + */ + outputFileTracingIncludes: { + "\\[locale\\]/latest": [ + "./public/content/latest/**/*.md", + "./public/content/translations/*/latest/**/*.md", + ], + "\\[locale\\]/developers/tutorials": [ + "./public/content/developers/tutorials/**/*.md", + "./public/content/translations/*/developers/tutorials/**/*.md", + ], + "\\[locale\\]/videos": [ + "./public/content/videos/**/*.md", + "./public/content/translations/*/videos/**/*.md", + ], + "\\[locale\\]/developers": [ + "./public/content/latest/**/*.md", + "./public/content/translations/*/latest/**/*.md", + ], + }, } } diff --git a/proxy.ts b/proxy.ts index be00c346ac6..66e6a0ed1fb 100644 --- a/proxy.ts +++ b/proxy.ts @@ -103,5 +103,12 @@ export default function proxy(request: NextRequest) { // Simplified matcher pattern export const config = { - matcher: ["/((?!api|_next|_vercel|.well-known|.*\\.[^/]*$).*)"], + matcher: [ + "/((?!api|_next|_vercel|.well-known|.*\\.[^/]*$).*)", + // Opt feed routes back in: The first pattern excludes any path with a file + // extension, which would otherwise bypass next-intl locale handling for + // /latest/feed.xml and short-circuit the canonical redirect for + // /en/latest/feed.xml. + "/:locale?/latest/feed.xml", + ], } diff --git a/public/content/latest/building-on-ethereum-in-2026/index.md b/public/content/latest/building-on-ethereum-in-2026/index.md new file mode 100644 index 00000000000..dd7ecaaf3a2 --- /dev/null +++ b/public/content/latest/building-on-ethereum-in-2026/index.md @@ -0,0 +1,104 @@ +--- +title: "Building on Ethereum in 2026: what has changed" +description: "Three protocol upgrades since 2023 changed two things builders care about: how much L1 costs to use and what regular wallets can do. A practical guide to building on Ethereum in 2026." +author: "Philip Krause" +team: "EF Builder Growth" +tags: + - "gas fees" + - "account abstraction" + - "protocol upgrades" +published: 2026-05-07 +image: /images/developers/blog/latest-post-header-3.png +breadcrumb: Building on Ethereum in 2026 +lang: en +--- + +If your mental model of Ethereum was formed in 2021 to 2023, it is out of date. Three protocol upgrades since then, [Dencun](/roadmap/dencun/) in March 2024, [Pectra](/roadmap/pectra/) in May 2025, and [Fusaka](/roadmap/fusaka/) in December 2025, changed two things builders care about, how much L1 costs to use and what regular wallets can do. + +## Mainnet is cheap again {#mainnet-is-cheap-again} + +The 2021 to 2023 fee regime is no longer a safe default assumption. + +As of May 5, 2026, Etherscan's gas tracker shows standard gas around 0.15 gwei, with daily averages near 0.5 gwei through April. A basic ETH transfer costs under a cent at that level, with typical recent days landing in the low single-digit cents. The trend has been falling across each of the recent upgrades, and the next one, [Glamsterdam](/roadmap/glamsterdam/), is set to push fees lower still. That makes "Ethereum mainnet is too expensive for most apps" a stale starting point. + +If you want a simple rule of thumb, use gas math instead of old folklore. At 0.5 gwei, the recent April average, and ETH at roughly $2,350, illustrative costs look like this. + +| Operation | Gas Used | Illustrative Cost | +| :-------------- | :---------- | :---------------- | +| ETH transfer | 21,000 | **$0.025** | +| ERC-20 transfer | \~65,000 | **$0.076** | +| ERC-20 approve | \~46,000 | **$0.054** | +| Swap | \~180,000 | **$0.21** | +| ERC-20 deploy | \~1,200,000 | **$1.41** | + +Those are examples, not guarantees. Costs move with ETH price, gas price, and contract complexity. Gwei readings can swing widely inside a normal month while the dollar cost barely moves, because rollups now carry about 95 percent of Ethereum's transactions and L1 typically runs well below its block target. Mainnet fees are now low enough that many apps can sensibly run on mainnet. + +### Why costs fell {#why-costs-fell} + +Three upgrades did most of the work. + +Dencun (March 2024) introduced EIP-4844 and gave rollups their own data lane through blobs, with a separate fee market. Rollups stopped competing with ordinary execution traffic on the same blockspace. + +Pectra activated on May 7, 2025. EIP-7691 raised blob throughput from 3 target / 6 max blobs per block to 6 target / 9 max, which expanded the cheap data lane rollups use and pushed L2 fees lower. + +Fusaka activated on December 3, 2025. Its headline capacity change was PeerDAS, which lets validators sample blob data instead of downloading every blob in full, and that sampling is what makes higher blob counts safe at the network layer. In parallel, the community raised the L1 gas limit from 30M to 60M during 2025, and Fusaka's EIP-7935 standardized 60M as the new default. EIP-7825 caps any single transaction at \~16.78M gas, which most apps will never notice but very large deployments and monolithic multicalls now have to fit inside. EIP-7951 also added native secp256r1 (P-256) verification on mainnet, which makes passkey and WebAuthn signatures far cheaper to verify in account flows. + +The net effect is that mainnet is no longer priced like a permanently congested chain. + +## How EIP-7702 changes the account model {#how-eip-7702-changes-the-account-model} + +Pectra also shipped EIP-7702, which gives regular wallets access to smart-account behavior like batching, gas sponsorship, session keys, recovery flows, and passkey-friendly UX, without making the user migrate to a new account. + +It works by adding a new transaction type (type `0x04`, `SetCode`) that lets an EOA set a pointer to already deployed contract code. The user keeps the same address, the original EOA key keeps ultimate control over the account, and the delegation can later be changed or reset to the null address. + +For app builders, the practical change is to ask the wallet for the outcome, not for low-level EIP-7702 setup. If a user needs to approve and swap in one flow, request a batch through ERC-5792 `wallet_sendCalls`. The wallet can decide whether to use EIP-7702, ERC-4337, or another account system. + +The delegated code is a security boundary. If a wallet points an EOA at buggy or malicious code, that code can make calls as the user, including token approvals, transfers, and app interactions. Builders should treat delegation targets like wallet infrastructure, relying on wallet-vetted implementations and not asking users to delegate to app-controlled code casually. + +## What this changes about how to build {#what-this-changes-about-how-to-build} + +The default builder question used to be "which L2 is cheap enough?" That question still has answers, but it is not the only one. With L1 fees in the cents-per-tx range during normal load, and EIP-7702 letting any wallet expose smart-account UX without migrating addresses, the more useful default is whether the app should live on mainnet, or whether a specific L2 gives a real distribution, liquidity, or UX advantage that L1 cannot. + +The account assumption changes too. Do not design new apps as if every user account is a plain ECDSA EOA that must hold ETH before doing anything useful. Prefer wallet-level batching interfaces such as ERC-5792 `wallet_sendCalls`, assume gas sponsorship and session keys will become normal wallet features, and treat passkeys and recovery flows as part of the account UX surface rather than separate onboarding hacks. + +## What is next {#whats-next} + +Ethereum's next named upgrade is Glamsterdam, with Block-level Access Lists (BALs) and enshrined proposer-builder separation (ePBS) as headline items. Together those make it safe to raise the block gas limit from 60 million today toward roughly 200 million, leaving more L1 capacity for builders to work with. Activation is expected in the second half of 2026. After Glamsterdam, [Hegotá](https://forkcast.org/upgrade/hegota/) is planned to follow, with Fork-choice enforced Inclusion Lists (FOCIL) selected as its headlining feature. + +For builders, the items worth tracking are more L1 capacity (BALs), more reliable transaction inclusion (FOCIL), and the path toward native account abstraction. ePBS, the other Glamsterdam headline, is mostly an infrastructure change that removes a trust dependency under L1 transaction inclusion. The direct app-level surface change is small. + +BALs are about keeping L1 cheap as usage grows. In plain English, a block would come with a map of the accounts and storage it touches. Clients can use that map to prefetch data and execute independent transactions in parallel, which makes it safer to raise the L1 gas limit without making blocks too slow to verify. The practical effect for builders is that more activity can come back to mainnet without automatically recreating the 2021 to 2023 gas regime. + +FOCIL is about getting valid transactions into blocks even when one block producer would rather leave them out. Today, if the party building a block ignores a transaction, the rest of the protocol has limited ways to force it in. With EIP-7805, several validators would say, in effect, "we saw these valid transactions waiting in the public mempool." The next block then has to include them, or validators can refuse to support that block. For builders, this matters when reliable access to L1 is part of the product, including privacy tooling, regulated onramps, or apps serving users who may be filtered by some infrastructure providers. + +For app builders, the Hegotá item to watch most closely is account abstraction. EIP-8141, Frame Transactions, would add a transaction type where validation, execution, and gas payment are split into frames. In practice, that means a smart account could verify a transaction itself, define its own signature rules, approve who pays gas, and execute one or more actions without depending on the ERC-4337 EntryPoint, bundlers, or app-run relayers. + +That changes product assumptions. Gas sponsorship becomes a native account pattern instead of infrastructure every app has to arrange separately. Alternative signature schemes become easier to support, including passkeys today and a path away from ECDSA if post-quantum migration becomes necessary. If EIP-8141 or a similar native account abstraction design lands, the builder model shifts from "an EOA signs a transaction" to "an account defines how it validates, pays for, and executes a transaction." + +That is the direction, not a promise. EIP-8141 is Draft, and as of May 2026 it is only "considered for inclusion" in Hegotá, meaning client teams are discussing it but have not committed to shipping it in that upgrade. The practical 2026 build path for account UX is still EIP-7702 plus ERC-4337 wallet flows, but builders should design as if programmable accounts are becoming the default account model. + +## What to build differently now {#what-to-build-differently-now} + +Start by re-checking old fee assumptions. If your deployment playbook still treats Ethereum mainnet as a 10 to 30 gwei environment by default, it is probably routing too much work away from L1. Mainnet is worth considering first when your app depends on shared liquidity, composability with existing protocols, neutrality, or high-value state that should live where Ethereum's security and social consensus are strongest. + +Use L2s for the reasons that still matter, including distribution, very high transaction volume, app-specific ecosystems, or per-action costs that need to be as close to zero as possible. The point is not "mainnet for everything." The point is that "mainnet is too expensive" should no longer be the first filter. + +On the account side, build against wallet capabilities instead of raw EOAs. Your frontend should be ready for batched calls, sponsored gas, session keys, passkeys, and recovery flows to arrive through wallets. EIP-7702 and ERC-4337 are the practical tools today. Native account abstraction is the direction to track next. + +Stop treating Ethereum mainnet as the expensive settlement layer you only touch at the end, and stop treating user accounts as static ECDSA keys that must hold ETH before they can do anything. Ethereum in 2026 is moving toward cheaper L1 execution and programmable accounts. Build for that world. + +## Further reading {#further-reading} + +- [Pectra Mainnet Announcement](https://blog.ethereum.org/en/2025/04/23/pectra-mainnet) +- [Fusaka Mainnet Announcement](https://blog.ethereum.org/2025/11/06/fusaka-mainnet-announcement) +- [Protocol Priorities Update for 2026](https://blog.ethereum.org/2026/02/18/protocol-priorities-update-2026) +- [Checkpoint #9 (Apr 2026)](https://blog.ethereum.org/2026/04/10/checkpoint-9) +- [Pectra 7702 guidelines on ethereum.org](https://ethereum.org/en/roadmap/pectra/7702/) +- [EIP-7702 Set Code for EOAs](https://eips.ethereum.org/EIPS/eip-7702) +- [EIP-7928 Block-level Access Lists](https://eips.ethereum.org/EIPS/eip-7928) +- [EIP-7805 Fork-choice enforced Inclusion Lists (FOCIL)](https://eips.ethereum.org/EIPS/eip-7805) +- [EIP-8141 Frame Transaction](https://eips.ethereum.org/EIPS/eip-8141) +- [Forkcast Hegotá Upgrade](https://forkcast.org/upgrade/hegota/) +- [Etherscan Gas Tracker](https://etherscan.io/gastracker) +- [EIP-7773 Glamsterdam Hardfork Meta](https://eips.ethereum.org/EIPS/eip-7773) +- [Glamsterdam roadmap on ethereum.org](https://ethereum.org/roadmap/glamsterdam/) diff --git a/public/content/latest/privacy-apps-on-ethereum/index.md b/public/content/latest/privacy-apps-on-ethereum/index.md new file mode 100644 index 00000000000..a232ad5894a --- /dev/null +++ b/public/content/latest/privacy-apps-on-ethereum/index.md @@ -0,0 +1,99 @@ +--- +title: "How to build privacy apps on Ethereum with zero-knowledge proofs" +description: "One reusable pattern powers anonymous voting, mixers, airdrops, and membership systems on Ethereum. Learn the commitment-nullifier-proof cycle and how zero-knowledge tooling makes it practical to build today." +author: "Philip Krause" +team: "EF Builder Growth" +tags: + - "zero-knowledge proofs" + - "privacy" + - "Noir" +published: 2026-05-12 +image: /images/developers/blog/latest-post-header-2.png +breadcrumb: Privacy apps on Ethereum +lang: en +--- + +Ethereum is radically public by design. Every address, balance, transaction, contract call, and event is visible to anyone with a block explorer. That transparency is useful when you want verifiability. It is a problem when users need to vote, claim, withdraw, or prove membership without linking every action back to the same wallet. + +Anonymous membership is the reusable pattern that powers a large class of privacy apps on Ethereum. People register first, then later prove they belong to the group without revealing which member they are. A zero-knowledge proof is the bridge between the registration wallet and the acting wallet, and the bridge does not reveal who crossed it. + +The surrounding product changes, but the privacy skeleton stays the same. + +## The pattern, explained through anonymous voting {#the-pattern-explained-through-anonymous-voting} + +The pattern has three pieces. A commitment registers each member. A Merkle tree turns those commitments into a crowd. A proof and a nullifier let one member act once without revealing which member acted. + +### Step one: registering {#step-one-registering} + +Every voter creates two private values offchain, the secret and the nullifier. The voter hashes those values into a public commitment, then registers that commitment onchain. + +The commitment is the public registration record. The secret and nullifier are the private note the voter needs later. Lose the note and the voter cannot prove membership. Leak it and someone else may be able to vote in the user's place. + +Because the commitment is a hash, observers cannot recover the private values inside it. The commitment says "someone registered" without revealing who will later use that registration. + +### Step two: building the crowd {#step-two-building-the-crowd} + +As more voters register, the app collects their commitments into a Merkle tree. A Merkle tree compresses a long list of values into a single hash, called the root. Change any value in the list and the hash changes, so the root acts as a tamper-evident summary of the whole set. + +That tree is your anonymity set. If ten users are in the tree, an observer can narrow a later action down to one of those ten. If ten thousand users are in the tree, the action is much harder to link to one person. A private app with a tiny anonymity set is usually not very private, even if the cryptography is correct. + +### Step three: acting anonymously {#step-three-acting-anonymously} + +When the poll opens, the voter should not vote from the same wallet that registered the commitment. Voting from the registration wallet would link the vote straight back to the registrant and undo the privacy work. Instead, the voter creates a zero-knowledge proof. The statement is encoded as a circuit that says, "I know private values that produce a registered commitment, and I am revealing the correct nullifier hash for this poll." + +The proof convinces the verifier contract that the statement is true. It does not reveal the secret, the nullifier, or which commitment was used. + +The nullifier is what prevents double voting. Alongside the proof, the voter publishes a nullifier hash. The voting contract stores that hash after accepting the vote. If the same private note is used again for the same poll, it produces the same nullifier hash, and the contract rejects the second vote. Combined with the proof, this leaves the contract knowing only that some registered voter acted once, not which one. + +## The reusable gate {#the-reusable-gate} + +That same proof-and-nullifier pair works beyond voting. Strip away the voting story and what you have is a privacy gate for smart contract functions. + +Before the function runs, the contract checks the Merkle root, verifies the proof, confirms the nullifier hash has not been used, and binds the public inputs to the right app, chain, poll, claim, or withdrawal. If those checks pass, it marks the nullifier as used and runs the rest of the function. + +Put that gate in front of a vote and you get anonymous voting. Put it in front of an airdrop claim and you get anonymous claims. Put it in front of a withdrawal function and you get the core of a mixer-style withdrawal flow. Same commitment tree, same nullifier idea, same proof pattern. What changes is the function body and the surrounding app logic. + +## What runs where {#what-runs-where} + +The private work usually happens offchain. The user stores the note, and a client app builds the witness and runs the prover to produce the proof. An indexer tracks commitments and Merkle roots. A bundler propagates the UserOperation onchain and an ERC-4337 paymaster sponsors the gas, so a fresh wallet does not need ETH from a user's known wallet first. + +The public enforcement happens onchain. The verifier contract checks the proof. The app contract checks valid roots and unused nullifiers, stores the nullifier hash, and runs the public action. + +The sensitive UX is note handling. Treat the secret and nullifier like keys. Do not put them in analytics, logs, URLs, error reports, or normal server-side telemetry. Once the note leaks, the privacy is gone, no matter how strong the proof. + +## The tooling caught up {#the-tooling-caught-up} + +You do not need to hand-code the underlying cryptography. A common path is to write the circuit in a high-level zero-knowledge language, generate a Solidity verifier, and call that verifier from the app contract. + +The right stack depends on the job. Circom with snarkjs is a long-established path for app-level circuits. Noir with Barretenberg is a newer developer-friendly path. Halo2 and gnark are lower-level circuit libraries. zkVMs such as RISC Zero or SP1 prove normal programs, but can be more expensive to prove than a small custom circuit. + +For anonymous membership, reach for an existing protocol before writing your own circuit. Semaphore packages group membership and nullifier-based double-use prevention into contracts and JavaScript libraries. For private voting and governance, MACI is the specialized path because it adds anti-collusion properties. Mature protocols are often safer than new circuits. + +## The proof is not enough {#the-proof-is-not-enough} + +Even a perfect proof fails if the wallet flow leaks the link. Register from wallet A and later act from wallet A, and anyone watching can connect the transactions. Fund wallet B from wallet A right before acting, and that funding transaction creates the same problem. + +This is why bundlers and paymasters matter. The acting wallet should be fresh, and it should not need to receive ETH from a wallet the user is trying to separate from the action. + +The same problem exists offchain. Submitting registration and action transactions from the same IP address, RPC provider, or session can weaken the privacy the circuit provides. Frontends can leak through analytics, local storage, and support logs. A zero-knowledge proof hides the values inside the proof. It does not hide everything around the transaction. + +Public inputs are another place privacy apps fail. Anything marked public in the circuit, emitted as an event, included in calldata, or stored by the contract is visible. Review public inputs as carefully as access control on a Solidity contract. + +## What this changes for builders {#what-this-changes-for-builders} + +Privacy on Ethereum is shippable. Builders can compose the pieces into real applications. The stack is a circuit for the private statement, a verifier for proof checking, an app contract for public rules, an indexer for Merkle data, and a bundler plus paymaster for unlinkable submission and gas sponsorship. + +The hard parts are product design, key management, metadata hygiene, audits, and growing the anonymity set. Get any of them wrong and the privacy the proof gave is gone. + +## Further reading {#further-reading} + +1. [Zero-knowledge proofs (ethereum.org)](https://ethereum.org/zero-knowledge-proofs/) +2. [Semaphore Documentation](https://docs.semaphore.pse.dev/) +3. [MACI Documentation](https://maci.pse.dev/) +4. [Circom Documentation](https://docs.circom.io/) +5. [Noir Documentation](https://noir-lang.org/) +6. [Halo2 Book](https://zcash.github.io/halo2/) +7. [gnark Documentation](https://docs.gnark.consensys.io/) +8. [RISC Zero Documentation](https://dev.risczero.com/api/) +9. [SP1 Documentation](https://docs.succinct.xyz/docs/sp1/introduction) +10. [EIP-4337: Account Abstraction via EntryPoint Contract](https://eips.ethereum.org/EIPS/eip-4337) diff --git a/public/content/latest/why-build-on-ethereum/index.md b/public/content/latest/why-build-on-ethereum/index.md new file mode 100644 index 00000000000..065c04e8ad2 --- /dev/null +++ b/public/content/latest/why-build-on-ethereum/index.md @@ -0,0 +1,107 @@ +--- +title: "Why build on Ethereum" +description: "Decentralization, censorship resistance, permissionless deployment, and composability are not separate selling points. They reinforce each other. A practical guide to why builders should choose Ethereum." +author: "Philip Krause" +team: "EF Builder Growth" +tags: + - "decentralization" + - "censorship resistance" + - "composability" +published: 2026-05-12 +image: /images/developers/blog/latest-post-header-1.png +breadcrumb: Why build on Ethereum +lang: en +--- + +Builders choose infrastructure by the promises their app needs to keep. + +Most software promises depend on an operator. A cloud provider keeps the server running. A platform keeps the account open. A payment processor keeps the merchant enabled. An API provider keeps the key valid. That is fine for many products. It is not enough when the product's value depends on neutral access, shared state, and commitments that users and other developers can verify for themselves. + +Ethereum is built for the second case, where neutral access and verifiable commitments are the product. No one owns it. The chain runs across many countries, many operators, and multiple independent client implementations, and no single company, validator, or foundation can quietly rewrite the rules. For a builder, that means it is not just a place to host code. It is a place to make public commitments. You can ship without asking anyone, users can keep reaching what you deploy, other developers can build on it without your permission, and your app can continue to work even when any one party, including you, stops cooperating. + +## Decentralization {#decentralization} + +Decentralization is the foundation those properties stand on. Ethereum delivers it through a network of computers, called nodes, that each store a copy of the chain and check every transaction. Each node runs client software. A subset of nodes, called validators, take turns proposing and confirming new blocks through a process called consensus. To participate, validators put up ETH as collateral, called stake, that they lose if they break the rules. Around 13,700 to 14,000 nodes were tracked in Etherscan's node tracker in April 2026, distributed across the United States, Germany, China, the United Kingdom, Russia, Japan, and dozens of other countries. + +Decentralization is also economic. About 32 to 36 million ETH, around 27 to 29% of supply, is staked as collateral that the protocol slashes when validators provably misbehave. An attacker would need to acquire and risk a meaningful fraction of that stake to corrupt the chain. At April 2026 ETH prices, that means tens of billions of dollars would be at risk. + +The other dimension is the software itself. Every Ethereum node runs two pieces of software side by side. An execution client runs the EVM and tracks contract state. A consensus client handles proof-of-stake. It tracks which validators propose blocks, which blocks the network accepts, and when a block becomes final. Healthy decentralization needs multiple independent implementations of each, so a bug in one client does not automatically become a bug in Ethereum. + +The execution layer has five major clients in production. Geth runs at roughly 50%, Nethermind around 25%, Besu around 9%, Reth around 8%, and Erigon around 7%. The consensus layer runs on Lighthouse, Prysm, Teku, Nimbus, Lodestar, and other clients. Ethereum is not a single-client chain on either layer. + +Geth's near-50% share is the real fragility. A bug in a minority client is painful for its operators, but the rest of the network can continue. A severe bug in a majority client is more dangerous. That is why client diversity is a live operational priority. + +That priority has been tested. Ethereum has never had a full chain halt since genesis on July 30, 2015. The closest it has come to a major incident was on May 11 to 12, 2023, when the consensus layer, called the Beacon Chain, failed to finalize for about 25 minutes and then later for about 64 minutes. The cause was a Prysm client bug. Finality requires more than two-thirds of validators to attest, and Prysm's share at the time was high enough that its issue briefly pulled the network below that threshold. + +A finality stall is not the same as a chain halt. New blocks kept being produced, transactions kept being included, and most users and applications kept working. What stalled was Ethereum's strongest settlement guarantee. Under normal consensus assumptions, a block older than roughly 13 minutes cannot be reverted. Bridges, exchanges, and other systems that wait for finality before crediting deposits would have paused those flows. The chain itself recovered automatically once enough validators caught up, without manual intervention. + +For builders, that history matters. If other people are going to hold assets in your contracts, route orders through your market, or build on your primitive, they need the foundation underneath it to keep running through bugs, client failures, and institutional pressure. + +## Censorship resistance {#censorship-resistance} + +Decentralization is the structure. Censorship resistance is one of the practical things it buys. Users should not need permission from a company, government, relay, validator, RPC provider, or app operator to send a valid transaction to your contracts. + +That does not mean every transaction lands in the next block. It means no single party can keep a valid transaction off the chain forever. Each block is proposed by a different validator, who works with outside parties, called builders and relays, to assemble it. If one of them filters your transaction, the next slot has a different set, and eventually one of them includes it. Censorship has to persist across that whole rotating cast, which is much harder than one operator saying no. The post-Tornado Cash period showed what that looks like under pressure. + +Tornado Cash is a privacy mixer contract that breaks the onchain link between deposit and withdrawal. After OFAC sanctioned it in August 2022, several major MEV-Boost relays refused to forward blocks containing transactions from sanctioned addresses. The share of blocks built through those OFAC-compliant relays peaked near 79% in November 2022. The other 21% came from relays and builders that did not filter, so Tornado Cash transactions still landed, just slower. The expected wait rose from about 12 seconds to about a minute. + +That looked alarming, and it was. Then the share fell. New relays launched explicitly without filters, including Ultra Sound and Agnostic, and proposers were free to add them to their MEV-Boost setup. No one could force every proposer onto a filtering relay, so the share could not stay at its peak. By early 2023 it was below 50%, and through the rest of 2023 it ranged between 27% and 47%. OFAC removed Tornado Cash from the sanctions list in March 2025. The episode remains Ethereum's clearest censorship-resistance stress test. + +Ethereum is also moving more of this guarantee into the protocol itself. A planned upgrade called FOCIL (EIP-7805) adds inclusion lists. Randomly selected validators publish transactions they see in the public mempool, and the next block is expected to satisfy those lists. If a block ignores them, the rest of the network can reject it. So no one can stop your users from using your app. + +## Permissionless {#permissionless} + +Censorship resistance is about whether users can keep reaching your app after you ship. Permissionlessness is about whether you can ship in the first place. + +Deploying on Ethereum does not require a partnership, account, listing approval, app-store review, or commercial agreement. Anyone can deploy code, call a contract, run a node, index data, build a wallet, or publish an interface. The base layer does not know whether you are a startup, a bank, a solo developer, an agent, a DAO, or a user with no company at all. + +That changes the builder model. On a platform, the platform owner can change terms, revoke keys, block regions, remove apps, or make access conditional on a business relationship. On Ethereum, the protocol evaluates transactions by the same public rules for any caller. A contract deployed today runs by those public rules for every address as long as the chain keeps running. + +This does not remove every dependency. Most users do not reach your contracts directly. They go through a frontend, a wallet, and an RPC provider, and any of those layers can break or filter. Frontends can be taken down. RPC providers, the services that route most app and wallet requests to the chain, can refuse to forward transactions or block specific regions and addresses. Wallets can choose what they display. + +The base execution environment stays open underneath. If your frontend goes down, a user can still call the contract directly, and another developer can build a new interface. If a wallet stops supporting your token, the contract still works. If one RPC provider filters, an app can route through another or run its own node to reach the network. + +## Composability {#composability} + +Permissionlessness gets your code onto the chain. Once it is there, no one can take it down, so other developers can build on top of your contracts, and you can build on theirs. + +WETH is the cleanest example. It is a contract that wraps ETH so it can be used like a standard token in other contracts. It sits at one fixed Ethereum address, holds about 1.8 million WETH as of May 2026, has roughly 3.25 million holders, and acts as a common unit across DEXs, lending markets, vaults, and bridges. It is code that thousands of other contracts and apps can use directly. + +That pattern repeats across the ecosystem. From genesis to early 2025, Ethereum saw tens of millions of contract deployments and roughly 2.5 million unique bytecodes by Zellic's count. Standards like ERC-20 for fungible tokens and ERC-721 for non-fungible tokens (NFTs) became coordination layers. A token your contract emits can be traded on a DEX, borrowed against in a money market, indexed by analytics tools, displayed in wallets, and bridged or wrapped by other systems without each team negotiating a custom agreement. + +As of May 2026, around $46 billion sat in DeFi on Ethereum. That money is locked inside thousands of working protocols, including assets, markets, oracles, wallets, account systems, governance contracts, bridges, analytics, and developer tools. All of it is code you can call directly on day one, instead of building from scratch or waiting for partnerships. + +## The agent economy {#the-agent-economy} + +Permissionless access and censorship resistance, with decentralization underneath them, matter even more for the next wave of users entering Ethereum. AI agents are that wave, and they pay for services, hold capital, and settle with other agents through transactions and contract calls, all without a human in the loop. An agent has no card to charge, no platform account to suspend, and no human to call when a relay refuses to forward a transaction. That is why both stop being optional for that kind of software, and Ethereum's properties are a direct match for what an agent actually needs. Ethereum is where that economy is expected to play out, and that could grow the user base immensely. + +Whether you ship the agent or ship the contracts the agent calls, the same problems show up. On a typical hosted stack, the agent's identity is rented from a platform account that can be revoked. Its payments depend on a human's card or API key. Its rules run on a server an operator controls. Its continuity depends on a host that can disappear. Every one of those dependencies is what Ethereum's base layer is designed to remove. + +On Ethereum, none of that depends on an operator. The agent's keys are its own, and the rules it signs against cannot be unilaterally rewritten. Its transactions go through the same rotating cast of validators, builders, and relays that protects any other address from targeted blocking. State transitions happen in public, so the contracts on the other side of the call do not have to trust an operator to report what happened. + +The rails are already in place. Smart contracts, stablecoins, and account abstraction give an autonomous actor a working address, a working balance, and programmable spending limits today. Standards for agent identity and machine-native payments are catching up. ERC-8004 defines onchain registries for agent identity, reputation, and validation. x402 uses the HTTP 402 status code to let clients, including agents, pay APIs and digital services in stablecoins without traditional accounts. Adoption is early but moving, and the integration surface is small. Accept x402 payments at your endpoints, register or check identity through ERC-8004, and treat agent addresses as first-class users in your contracts. + +For any builder picking a chain to ship on, agents are the next user class forming, and the rails are already live. The contracts you ship today can serve them tomorrow without waiting for a future protocol. + +## Conclusion {#conclusion} + +Decentralization, censorship resistance, permissionless deployment, and composability are not separate selling points. They reinforce each other. Decentralization makes censorship resistance credible and lets users keep reaching what is shipped. Permissionless deployment lets builders ship. Composability turns those apps into shared infrastructure. Autonomous agents can transact through it and no one can stop them. What you ship is a public commitment. It keeps running without you. + +## Further reading {#further-reading} + +- [Ethereum Foundation Checkpoint #9 (April 2026)](https://blog.ethereum.org/2026/04/10/checkpoint-9) +- [clientdiversity.org](https://clientdiversity.org/) +- [Etherscan Node Tracker](https://etherscan.io/nodetracker) +- [beaconcha.in validators](https://beaconcha.in/charts/validators) +- [Post-mortem: May 2023 mainnet finality](https://medium.com/offchainlabs/post-mortem-report-ethereum-mainnet-finality-05-11-2023-95e271dfd8b2) +- [mevwatch.info](https://www.mevwatch.info/) +- [The Block: OFAC-compliant blocks fall to 27%](https://www.theblock.co/post/230179/ethereums-ofac-compliant-blocks-fall-to-27-marking-a-drop-in-protocol-level-censorship) +- [Hegotá Headliner Proposal: FOCIL (EIP-7805)](https://ethereum-magicians.org/t/hegota-headliner-proposal-focil-eip-7805/27604) +- [EIP-7805: Fork-choice enforced Inclusion Lists (FOCIL)](https://eips.ethereum.org/EIPS/eip-7805) +- [EIP-8004: Onchain Agent Identity](https://eips.ethereum.org/EIPS/eip-8004) +- [coinbase/x402 GitHub](https://github.com/coinbase/x402) +- [CoinDesk: x402 demand has not materialized](https://www.coindesk.com/markets/2026/03/11/coinbase-backed-ai-payments-protocol-wants-to-fix-micropayment-but-demand-is-just-not-there-yet) +- [WETH on Etherscan](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2) +- [Zellic: All Ethereum contracts](https://www.zellic.io/blog/all-ethereum-contracts/) +- [DefiLlama: Ethereum chain](https://defillama.com/chain/ethereum) +- [OpenZeppelin: Technical Risk Assessment on Blockchain Networks (April 2026)](https://openzeppelin.com/hubfs/OpenZeppelin%20%7C%20Technical%20Risk%20Assessment%20on%20Blockchain%20Networks.pdf) diff --git a/public/images/developers/blog/latest-landing-hero.png b/public/images/developers/blog/latest-landing-hero.png new file mode 100644 index 00000000000..bf1f11c2caf Binary files /dev/null and b/public/images/developers/blog/latest-landing-hero.png differ diff --git a/public/images/developers/blog/latest-post-header-1.png b/public/images/developers/blog/latest-post-header-1.png new file mode 100644 index 00000000000..b3e6dc3eb23 Binary files /dev/null and b/public/images/developers/blog/latest-post-header-1.png differ diff --git a/public/images/developers/blog/latest-post-header-2.png b/public/images/developers/blog/latest-post-header-2.png new file mode 100644 index 00000000000..274c9f35a05 Binary files /dev/null and b/public/images/developers/blog/latest-post-header-2.png differ diff --git a/public/images/developers/blog/latest-post-header-3.png b/public/images/developers/blog/latest-post-header-3.png new file mode 100644 index 00000000000..aa8f038e7de Binary files /dev/null and b/public/images/developers/blog/latest-post-header-3.png differ diff --git a/src/components/TutorialMetadata.tsx b/src/components/TutorialMetadata.tsx index 27ac5d78e07..4896c349410 100644 --- a/src/components/TutorialMetadata.tsx +++ b/src/components/TutorialMetadata.tsx @@ -10,6 +10,7 @@ import Emoji from "@/components/Emoji" import Translation from "@/components/Translation" import TutorialTags from "@/components/TutorialTags" +import { cn } from "@/lib/utils/cn" import { getLocaleTimestamp } from "@/lib/utils/time" import { Flex } from "./ui/flex" @@ -38,25 +39,49 @@ const TutorialMetadata = ({ const hasSource = frontmatter.source && frontmatter.sourceUrl const published = frontmatter.published const author = frontmatter.author + const team = frontmatter.team const address = frontmatter.address + const hasTags = !!frontmatter.tags?.length + const hasSkill = !!frontmatter.skill + const hasTopRow = hasTags || hasSkill return ( - - - + {hasTopRow && ( + + {hasTags && ( + + + + )} + {hasSkill && ( + + {t(getSkillTranslationId(frontmatter.skill as Skill))} + + )} - - {t(getSkillTranslationId(frontmatter.skill as Skill))} - - - + )} + {author && (
{author}
)} + {team && ( +
+ + {team} +
+ )} {hasSource && (
diff --git a/src/components/ui/__stories__/Select.stories.tsx b/src/components/ui/__stories__/Select.stories.tsx index 5a2338a70af..dd50a37a8df 100644 --- a/src/components/ui/__stories__/Select.stories.tsx +++ b/src/components/ui/__stories__/Select.stories.tsx @@ -139,7 +139,7 @@ export const ErrorState: Story = { render: () => (