Over the past few weeks, I built a few apps that integrated with Mirror, to try and understand how their protocol worked. I started using their internal APIs and, from there, worked my way to more decentralized sources. Here’s how I did it (and how you can too).
The idea for The Actual Write Race was to build a list of all existing Mirror publications, and rank them based on the number of articles they had written (simulating Mirror’s $WRITE race) to add a fun touch.
To get the list of publications, we can query the Mirror GraphQL API (live at https://mirror-api.com/graphql
) with the following query:
query FetchPublications {
publications {
ensLabel
displayName
avatarURL
contributor {
displayName
avatarURL
address
}
}
}
The publications
query will give us most of the data we need, and if I were to build a simple listing I could have stopped here, but I also need the number of entries to rank them. Ideally, I should just be able to fetch the entries
key on the above query, but due to how Mirror has structured their API, the entries are set to null
when querying the publication list. Instead, we can use a second query to fetch the entries for each publication and check the length of those.
query PublicationEntries($ensLabel: String!) {
publication(ensLabel: $ensLabel) {
ensLabel
entries {
digest
}
}
}
It’s not the best system, and it has a blatant N+1 issue, but it’s the best I could get for this project, and the data is only fetched once a day, so it didn’t end up being an issue. If you want to learn more about this project, the source is available on GitHub.
The Mirror interface is beautiful, and I wanted to take a chance at recreating it with Tailwind CSS, so I decided to build a custom Mirror client with Next.js. The hard part of this project turned out to be retrieving Mirror entries in a decentralized way (querying the blockweave instead of Mirror’s API).
To start, we need to know the wallet address of the publication owner. Since Mirror subdomains are ENS names, we can do this by resolving {publication}.mirror.xyz
with any ENS resolver. With this information, we can query the blockweave (which conveniently offers a GraphQL API hosted at https://arweave.net/graphql
) by retrieving transactions created by Mirror and signed by that wallet address:
query FetchTransactions($address: String!) {
transactions(first: 100, tags: [{ name: "App-Name", values: ["MirrorXYZ"] }, { name: "Contributor", values: [$address] }]) {
edges {
node {
id
tags {
name
value
}
}
}
}
}
Since Mirror supports editing entries by pushing additional transactions, we need to check the Original-Content-Digest
tag to make sure we only take the latest edition of each entry into account. We’ll use that original digest as the slug for the post (emulating Mirror) and the node ID to fetch the entry from the blockweave using the Arweave NPM library.
const getPaths = async () => {
const {
data: {
transactions: { edges },
},
} = await queryGraphQL()
edges.map(({ node }) => {
const tags = Object.fromEntries(node.tags.map(tag => [tag.name, tag.value]))
return { slug: tags['Original-Content-Digest'], path: node.id }
}).filter(entry => entry.slug && entry.slug !== '').reduce((acc, current) => {
const x = acc.find(entry => entry.slug === current.slug)
if (!x) return acc.concat([current])
else return acc
}, [])
}
const getEntries = async () => {
const paths = await getPaths()
return Promise.all(
paths.map(async entry => JSON.parse(
await arweave.transactions.getData(entry.path, { decode: true, string: true }), entry.slug)
)
)
}
This will get you an array of entries following this format, you can then just parse the markdown bodies and render your entries.
Then, to fetch the contents of a single entry, you can query by the original content digest (which we’re using as a slug).
query FetchTransaction($digest: String!) {
transactions(tags: { name: "Original-Content-Digest", values: [$digest] }) {
edges {
node {
id
}
}
}
}
Keep in mind in this example we’re not verifying the signature of any of these entries, so anyone could add new entries with a random string as the signature. Ideally, you’d use a library like eth-sig-util
to make sure all entries are authentic.
If you’re curious about the source of my Mirror client, it’s available on GitHub. You can also see it live at m1guelpf.blog.
With these two data sources (Mirror’s GraphQL API & the blockweave), you can build anything on top of Mirror. Here are a few ideas:
Make sure to send me any cool apps you build with Mirror! You can find me at @m1guelpf on Twitter.