@adland/react
v0.26.0
Published
React components for displaying and tracking ads from [0xSlots](https://0xslots.org) — the on-chain advertising protocol.
Readme
@adland/react
React components for displaying and tracking ads from 0xSlots — the on-chain advertising protocol.
Install
npm install @adland/reactPeer dependencies: react ^18 || ^19, react-dom ^18 || ^19
Quick Start
import { Ad, AdImage, AdTitle, AdDescription, AdBadge } from "@adland/react";
function AdSlot() {
return (
<Ad slot="0x123..." auth="farcaster" context="sidebar">
<AdImage className="h-20 w-20 rounded" />
<AdTitle className="font-semibold" />
<AdDescription className="text-sm text-gray-600" />
<AdBadge />
</Ad>
);
}Components
Uses the compound component pattern — nest sub-components inside <Ad>.
<Ad> (Root)
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| slot | string | — | Slot contract address (on-chain fetch) |
| data | AdData | — | Static ad data (skips on-chain fetch) |
| chainId | number | 8453 | BASE or BASE Sepolia |
| rpcUrl | string | — | Custom RPC URL |
| auth | "farcaster" \| "none" | "none" | Auth method for verified tracking |
| context | string | — | Placement context (e.g. "sidebar") |
Sub-components
| Component | Description |
|-----------|-------------|
| <AdImage> | The wide asset — a banner, an Open Graph card, a post's media |
| <AdIcon> | The square one — an app icon, a favicon, a token logo, an avatar |
| <AdTitle> | Title, or a post author's display name |
| <AdDescription> | Description, or a post's text |
| <AdHandle> | @handle — posts only, renders nothing otherwise |
| <AdTimeAgo> | 3h, then a date past a week — posts only |
| <AdCreator> | Who published the thing being advertised, where the type names one |
| <AdBadge> | Ad type badge with icon |
| <AdLabel> | "AD" label |
AdImage and AdIcon are siblings rather than alternatives: an ad has both,
and which is right depends on the shape of your slot, not on the ad. A mini app
publishes a 2000×2000 mark and an 840×560 banner — a wide card wants the second,
a compact row the first.
State components
Conditionally render based on ad state:
<Ad slot="0x123...">
<AdLoading>Loading...</AdLoading>
<AdError>Failed to load</AdError>
<AdEmpty>Your ad here</AdEmpty>
<AdLoaded>
<AdImage />
<AdTitle />
</AdLoaded>
</Ad>Hook
Access ad context from any child component:
import { useAd } from "@adland/react";
function Custom() {
const { data, isLoading, error, isEmpty } = useAd();
if (!data) return null;
return <p>{data.data.title}</p>;
}Utilities
import {
getAdImage,
getAdIcon,
getAdTitle,
getAdDescription,
getAdCreator,
getAdPost,
getAdType,
formatTimeAgo,
parseAdTimestamp,
adCardIcon,
adCardLabel,
XLogo,
} from "@adland/react";@adland/react/core carries the accessors, adCardLabel and the web adapter
with no React anywhere in the graph — for a server route, an edge function, or a
non-React integration.
Ad Types
Six types, in two shapes:
| type | shape |
|---|---|
| link, miniapp, token, farcasterProfile | a thing — picture, name, description |
| cast (Farcaster), tweet (X) | a post — author, handle, text, time |
A slot renders whatever its current occupant put there, and the type can change under a layout written before it existed — so handling only one shape will eventually be wrong.
getAdPost(data) returns { text, name, handle, avatar, at, image } for a post
and null for everything else, which is how a layout asks without knowing what
it holds:
function Body() {
const { data } = useAd();
return getAdPost(data) ? <Post /> : <Thing />;
}Laid out as a thing, a post is ruined: the author's avatar gets stretched across the banner and the words — the entire point — end up truncated into a subtitle.
Tracking
Impressions and clicks are tracked automatically:
- Impressions — triggered via
IntersectionObserver(50%+ visibility) - Clicks — tracked on ad interaction
- Deduplication — one impression per slot per session
- Farcaster auth — verified identity when
auth="farcaster"
License
MIT
