Inspired by Paragraph I wanted to use Farcaster to add Disqus style comments to my digital garden.
Previously I handled “comments” using a Discuss on Twitter
CTA that linked to the search results for the posts public URL. The Twitter API did not factor into the equation, all conversation was abstracted away from the actual content. Simple, but far from ideal.
const comments = `https://mobile.twitter.com/search?q=${encodeURIComponent(
`https://your-domain.com/${slug}`
)}`;
The Farcaster approach is fairly straightforward — use the Searchcaster api to query for the public URL of a page. If any results are returned, display them (permissionlessly!) below the content. The one caveat is that the api updates every 30 minutes, so sometimes you need to wait before comments load on a post.
This takes two calls to get the basic functionality going. getTopLevelCasts
and getMerkleRootCasts
. Note: I am using Next13 for this, which extends fetch()
with some additional features.
getTopLevelCasts
returns any cast that directly mentions the content URL.
// getTopLevelCasts.tsx
export default async function getTopLevelCasts(path: string, slug: string) {
const uri = `https://searchcaster.xyz/api/search?text=your-domain.com/${slug}`;
const res = await fetch(uri, {
next: {
revalidate: 1 * 30,
},
}).then((res) => res.json());
const casts = res.casts;
return casts;
}
getMerkleRootCasts
is used in a loop, where each topLevelCast
's merkleRoot
is passed in to fetch any descendant casts.
// getMerkleRootCasts.tsx
export default async function getMerkleRoot(merkleRoot: string) {
const uri = `https://searchcaster.xyz/api/search?merkleRoot=${merkleRoot}`;
const res = await fetch(uri, {
next: {
revalidate: 1 * 30,
},
}).then((res) => res.json());
const casts = res.casts;
return casts;
}
Ultimately I only need the merkleRoot
from getTopLevelCasts
. I’m not rendering any content from that call directly. getMerkleRootCasts
returns the topLevelCasts
and its descendants, which makes the list structure a bit cleaner.
// comments.tsx
<ul>
{_topLevelComments.map((comment) => {
return (
<li key={comment.merkleRoot}>
<CommentBody {...comment} />
{comment.numReplyChildren > 0 && (
<ul>
{_merkleRootComments[0]
.map((comment) => {
return (
<li key={comment.merkleRoot}>
<CommentBody {...comment} />
</li>
);
})
.slice(0, -1)}
</ul>
)}
</li>
);
})}
</ul>
Comment threads can be structured various ways. This may change down the line as this digital garden grows, but it is perfectly sufficient for now. I encourage you to explore the response from the Searchcaster API and experiment with how to best display your threads.