Skip to content

Commit 52b2a3a

Browse files
committed
Improve blog index and docs content
1 parent 0e67443 commit 52b2a3a

11 files changed

Lines changed: 194 additions & 34 deletions

File tree

src/docs/01-getting-started/01-what-is-ethui.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ ethui is a tool for interacting with Ethereum chains in a developer-friendly way
1212

1313
At the surface, it replaces traditional wallets (MetaMask, Rabby, etc.). But it also provides a number of features that make it more interesting for power users and developers, such as:
1414

15-
- An [anvil][anvil]-aware syncing process
16-
- quick feedback loops, by bypassing popups and password-prompts when testing conditions apply
17-
- Support for test-focused wallets
18-
- built-in contract explorer
19-
- Automatic indexing of ABIs found in local [forge][forge] projects
15+
- An [anvil][anvil]-aware syncing process.
16+
- Quick feedback loops by bypassing popups and password prompts when testing conditions apply.
17+
- Support for test-focused wallets.
18+
- Built-in contract explorer.
19+
- Automatic indexing of ABIs found in local [forge][forge] projects.

src/docs/02-features/03-auto-impersonation.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ ethui automatically uses `anvil_impersonateAccount` whenever submitting transact
99

1010
This allows you to submit transactions without having to unlock your wallet, and enables [fast mode](/docs/features/fast-mode) to work on secure wallets as well.
1111

12+
If you want to explicitly pick which account is being impersonated, use the [Impersonator wallet](/docs/wallets/impersonator).
13+
1214
## Why?
1315

1416
This is actually a security feature that happens to be very convenient for development.
@@ -27,3 +29,6 @@ By auto-impersonating accounts, you now skip the signing step, but are still abl
2729
- no signature is ever produced from your private key
2830
- [fast mode](/docs/features/fast-mode) can now be available as well, since the confirmation steps that would be needed for a signature is now optional
2931

32+
## Related
33+
34+
- [Impersonator wallet](/docs/wallets/impersonator)

src/docs/03-wallets/03-keystore.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,19 @@ slug: keystore
66
# Keystore
77

88
The `Keystore` wallet allows you to use a keystore file to unlock your wallet. The keystore file format encodes a single private key in a file.
9+
10+
## When to use it
11+
12+
Choose this option when you already have a JSON keystore file (from geth, foundry, or another wallet export) and want to unlock it with a password.
13+
14+
## Setup
15+
16+
1. Export or locate your JSON keystore file.
17+
2. In ethui, create a new wallet and select **Keystore**.
18+
3. Choose the keystore file and enter its password.
19+
4. Confirm the derived address before using it.
20+
21+
## Tips
22+
23+
- Treat the keystore file like a password-protected secret.
24+
- If you lose the password, the keystore cannot be recovered.

src/docs/03-wallets/04-pkey.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,15 @@ slug: pkey
88
The `Private Key` wallet allows you to use a single private key directly.
99

1010
While this method is secure (the key is password-protected), it is not the most convenient or recommended way to use a wallet. It is supported only for scenarios where a user already has a private key and needs to access it.
11+
12+
## Setup
13+
14+
1. Copy your private key from a trusted source.
15+
2. In ethui, create a new wallet and select **Private Key**.
16+
3. Paste the key and set a strong password.
17+
4. Verify the derived address matches your expected account.
18+
19+
## Security notes
20+
21+
- Only paste keys on a trusted machine.
22+
- Prefer keystore or hardware wallets for long-term use.

src/docs/03-wallets/06-impersonator.mdx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,15 @@ The `Impersonator` wallet allows you to impersonate any account on the chain, by
99

1010
This is useful for testing, as it allows you to submit transactions without having to unlock your wallet, and enables [fast mode](/docs/features/fast-mode) to work on secure wallets as well.
1111

12-
## Why?
12+
## How it works
1313

14-
This is actually a security feature that happens to be very convenient for development.
14+
The wallet sends `anvil_impersonateAccount` for the address you choose, so you can run transactions as that account without signing locally.
1515

16-
Let's say you're spawning the following anvil node:
16+
## Setup
1717

18-
```sh
19-
anvil --chain-id 1
20-
```
18+
1. Start an anvil node.
19+
2. Create a new wallet and select **Impersonator**.
20+
3. Enter the address you want to impersonate.
21+
4. Submit transactions as usual.
2122

22-
This could open you up for [replay attacks](https://chain.link/education-hub/replay-attack), where you end up signing a payload that can actually end up being used on the real mainnet chain to execute a real transaction, either maliciously or by mistake.
23-
24-
By auto-impersonating accounts, you now skip the signing step, but are still able to include transactions on the test chain. In the end:
25-
26-
- you never have to actually unlock your wallet
27-
- no signature is ever produced from your private key
28-
- [fast mode](/docs/features/fast-mode) can be used
23+
For the background on why this is safe and how it avoids signing, see [Auto Impersonation](/docs/features/auto-impersonation).

src/docs/03-wallets/07-ledger.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ slug: ledger
66
# Ledger
77

88
The `Ledger` wallet allows you to connect to a Ledger device. Currently, this is the only type of hardware wallet supported.
9+
10+
## Setup
11+
12+
1. Connect your Ledger device via USB.
13+
2. Unlock it and open the Ethereum app.
14+
3. In ethui, create a new wallet and select **Ledger**.
15+
4. Choose the account path and confirm the address on the device.
16+
17+
## Troubleshooting
18+
19+
- If the device is not detected, reconnect the cable and reopen the Ethereum app.
20+
- Make sure the Ledger firmware and Ethereum app are up to date.

src/routes/_l.blog/-manifest.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
1+
import banner01 from "#/blog/01-announcing-ethui/banner.png";
12
import * as blog01 from "#/blog/01-announcing-ethui/index.mdx";
3+
import banner02 from "#/blog/02-ethui-0.2.0-ui-level-up/banner.png";
24
import * as blog02 from "#/blog/02-ethui-0.2.0-ui-level-up/index.mdx";
5+
import banner03 from "#/blog/03-ethui-0.3.1-mainnet/banner.png";
36
import * as blog03 from "#/blog/03-ethui-0.3.1-mainnet/index.mdx";
7+
import banner04 from "#/blog/04-instant-foundry-abi-explorer/banner.png";
48
import * as blog04 from "#/blog/04-instant-foundry-abi-explorer/index.mdx";
9+
import banner05 from "#/blog/05-ethui-0.5.0-impersonation/banner.png";
510
import * as blog05 from "#/blog/05-ethui-0.5.0-impersonation/index.mdx";
11+
import banner06 from "#/blog/06-ethui-0.6.0-going-multi-chain/banner.png";
612
import * as blog06 from "#/blog/06-ethui-0.6.0-going-multi-chain/index.mdx";
13+
import banner07 from "#/blog/07-ethui-0.7.0-eth-lisbon/banner.png";
714
import * as blog07 from "#/blog/07-ethui-0.7.0-eth-lisbon/index.mdx";
15+
import banner08 from "#/blog/08-ethui-1.1.1-ledger-devtools/banner.png";
816
import * as blog08 from "#/blog/08-ethui-1.1.1-ledger-devtools/index.mdx";
17+
import banner09 from "#/blog/09-ethui-1.6-a-new-beginning/banner.png";
918
import * as blog09 from "#/blog/09-ethui-1.6-a-new-beginning/index.mdx";
19+
import banner10 from "#/blog/10-ethui-1.7-forms/banner.png";
1020
import * as blog10 from "#/blog/10-ethui-1.7-forms/index.mdx";
21+
import banner11 from "#/blog/11-ethui-1.13-were-back/banner.png";
1122
import * as blog11 from "#/blog/11-ethui-1.13-were-back/index.mdx";
23+
import banner12 from "#/blog/12-ethui-1.14/banner.png";
1224
import * as blog12 from "#/blog/12-ethui-1.14/index.mdx";
25+
import banner13 from "#/blog/13-ethui-explorer/banner.png";
1326
import * as blog13 from "#/blog/13-ethui-explorer/index.mdx";
27+
import banner14 from "#/blog/14-stacks/banner.png";
1428
import * as blog14 from "#/blog/14-stacks/index.mdx";
29+
import banner15 from "#/blog/15-ethui-1.27-hardhat-support/banner.png";
1530
import * as blog15 from "#/blog/15-ethui-1.27-hardhat-support/index.mdx";
1631

1732
// Helper to get og-banner image for a slug
@@ -62,11 +77,30 @@ const rawBlogManifest = [
6277
blog15,
6378
];
6479

80+
const bannerImages: Record<string, string> = {
81+
"announcing-ethui": banner01,
82+
"ethui-0.2.0-ui-level-up": banner02,
83+
"ethui-0.3.1-mainnet": banner03,
84+
"instant-foundry-abi-explorer": banner04,
85+
"ethui-0.5.0-impersonation": banner05,
86+
"ethui-0.6.0-going-multi-chain": banner06,
87+
"ethui-0.7.0-eth-lisbon": banner07,
88+
"ethui-1.1.1-ledger-devtools": banner08,
89+
"ethui-1.6.0-a-new-beginning": banner09,
90+
"ethui-1.7.0-forms": banner10,
91+
"ethui-1.13.0-were-back": banner11,
92+
"ethui-1.14.0": banner12,
93+
"ethui-explorer": banner13,
94+
stacks: banner14,
95+
"ethui-1.27-hardhat-support": banner15,
96+
};
97+
6598
// Enhanced blog manifest with og-banner images
6699
export const blogManifest = rawBlogManifest.map((post) => ({
67100
...post,
68101
frontmatter: {
69102
...post.frontmatter,
70103
ogBanner: getOgBannerForSlug(post.frontmatter.slug),
104+
bannerImage: bannerImages[post.frontmatter.slug],
71105
},
72106
}));

src/routes/_l.blog/_l.index.tsx

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,44 @@ export const Route = createFileRoute("/_l/blog/_l/")({
2222

2323
function RouteComponent() {
2424
return (
25-
<ul className="prose">
26-
{postsList.map(({ title, slug }) => (
27-
<li key={slug} className="border-b pb-2">
28-
<Link
29-
to="/blog/$slug"
30-
params={{ slug }}
31-
className="font-medium text-lg no-underline transition-colors hover:text-primary"
32-
>
33-
{title}
34-
</Link>
35-
</li>
36-
))}
37-
</ul>
25+
<div className="flex flex-col gap-6">
26+
<header className="prose">
27+
<h1>Blog</h1>
28+
<p>
29+
Product updates, release notes, and behind-the-scenes development
30+
stories from the ethui team.
31+
</p>
32+
</header>
33+
<div className="grid gap-6">
34+
{postsList.map(({ title, slug, banner, ogBanner, bannerImage }) => (
35+
<article key={slug} className="border">
36+
<Link
37+
to="/blog/$slug"
38+
params={{ slug }}
39+
className="block no-underline"
40+
>
41+
<div className="grid gap-4 md:grid-cols-[140px_1fr] md:items-stretch">
42+
{(bannerImage || ogBanner) && (
43+
<img
44+
src={bannerImage || ogBanner}
45+
alt={`${title} banner`}
46+
loading="lazy"
47+
className="h-full w-full object-cover"
48+
/>
49+
)}
50+
<div className="space-y-2 p-4">
51+
{banner?.date && (
52+
<p className="text-gray-500 text-sm">{banner.date}</p>
53+
)}
54+
<h2 className="font-semibold text-gray-900 text-xl">
55+
{title}
56+
</h2>
57+
</div>
58+
</div>
59+
</Link>
60+
</article>
61+
))}
62+
</div>
63+
</div>
3864
);
3965
}

src/routes/docs/-search-form.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@ import {
66
} from "@ethui/ui/components/shadcn/sidebar";
77
import { Search } from "lucide-react";
88

9-
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
9+
interface SearchFormProps extends React.ComponentProps<"form"> {
10+
value: string;
11+
onValueChange: (value: string) => void;
12+
}
13+
14+
export function SearchForm({
15+
value,
16+
onValueChange,
17+
...props
18+
}: SearchFormProps) {
1019
return (
1120
<form {...props}>
1221
<SidebarGroup className="py-0">
@@ -18,6 +27,9 @@ export function SearchForm({ ...props }: React.ComponentProps<"form">) {
1827
id="search"
1928
placeholder="Search the docs..."
2029
className="pl-8"
30+
type="search"
31+
value={value}
32+
onChange={(event) => onValueChange(event.target.value)}
2133
/>
2234
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 select-none opacity-50" />
2335
</SidebarGroupContent>

src/routes/docs/-sidebar.tsx

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from "@ethui/ui/components/shadcn/sidebar";
2020
import { Link, useRouterState } from "@tanstack/react-router";
2121
import { ChevronRightIcon } from "lucide-react";
22+
import { useMemo, useState } from "react";
2223
import { docsManifest } from "./-manifest";
2324
import { SearchForm } from "./-search-form";
2425

@@ -27,6 +28,43 @@ export function DocsSidebar({
2728
}: React.ComponentProps<typeof Sidebar>) {
2829
const routerState = useRouterState();
2930
const currentPath = routerState.location.pathname;
31+
const [query, setQuery] = useState("");
32+
const normalizedQuery = query.trim().toLowerCase();
33+
34+
const filteredSections = useMemo(() => {
35+
if (!normalizedQuery) {
36+
return docsManifest.sections;
37+
}
38+
39+
return docsManifest.sections
40+
.map((section) => {
41+
const sectionMatches = section.title
42+
.toLowerCase()
43+
.includes(normalizedQuery);
44+
if (sectionMatches) {
45+
return section;
46+
}
47+
48+
const filteredChildren = section.children.filter(({ frontmatter }) =>
49+
frontmatter.title.toLowerCase().includes(normalizedQuery),
50+
);
51+
52+
if (filteredChildren.length === 0) {
53+
return null;
54+
}
55+
56+
return {
57+
...section,
58+
children: filteredChildren,
59+
};
60+
})
61+
.filter(
62+
(section): section is (typeof docsManifest.sections)[number] =>
63+
section !== null,
64+
);
65+
}, [normalizedQuery]);
66+
67+
const hasResults = filteredSections.length > 0;
3068

3169
return (
3270
<Sidebar {...props}>
@@ -41,10 +79,15 @@ export function DocsSidebar({
4179
</Link>
4280
</SidebarMenuItem>
4381
</SidebarMenu>
44-
<SearchForm />
82+
<SearchForm value={query} onValueChange={setQuery} />
4583
</SidebarHeader>
4684
<SidebarContent className="gap-0">
47-
{docsManifest.sections.map(({ title, slug: sectionSlug, children }) => (
85+
{!hasResults && normalizedQuery && (
86+
<div className="px-4 py-6 text-muted-foreground text-sm">
87+
No docs match "{query}".
88+
</div>
89+
)}
90+
{filteredSections.map(({ title, slug: sectionSlug, children }) => (
4891
<Collapsible
4992
key={title}
5093
title={title}

0 commit comments

Comments
 (0)