gallery-backend: Gallery Backend: The Social Network Engine Hidden Inside an NFT Indexer
How `gallery-so/gallery-backend` turns multichain metadata, wallet auth, and feed generation into one Go monorepo built for curation, not just storage.
- Gallery’s backend is built around the social graph first, so blockchains matter mainly as a noisy input to curation, identity, and feed generation.
- Its multichain registry, token processing pipeline, and GraphQL surface are all designed to normalize messy NFT metadata into one coherent product object.
- Wallet signatures are the trust boundary, which makes ownership verification part of the social model rather than a separate security layer.
- The Go monorepo trades portability for shared domain logic, type-safe SQL, and operational clarity across several services.
Gallery Is a Social App Wearing an Indexer’s Clothes
Most NFT backends start with the chain and end with the user. Gallery does the reverse. It starts with a person who wants to curate, admire, and share a collection, then builds the backend around the messy reality of making that possible across multiple chains.
That inversion is the real story in gallery-so/gallery-backend. The repo is not just storage and sync logic. It is the machinery behind follows, admires, feeds, galleries, notifications, wallet login, and media processing, all tied together by a Go monorepo.
We built Gallery because we wanted a better way to share our own collections. The existing tools felt like spreadsheets or marketplaces, not places to appreciate art.
Why NFT Metadata Forced a Custom Multichain Registry
Gallery cannot trust any single NFT metadata pattern. Different chains, contracts, and marketplaces expose different fields, and the useful image may be buried behind a fallback path or a chain-specific naming convention.
That is why `platform/platform.go` matters. It acts like a routing table for metadata chaos, mapping contract families to the field priorities the rest of the system should trust.
// platform keywords tell downstream workers what to trust first
keywords := platform.KeywordsFor(contractAddress)
for _, key := range keywords {
if uri := metadata[key]; uri != "" {
return uri
}
}
return metadata["image"]
This is the quiet superpower of the backend. The registry does not merely store metadata. It makes a judgment about which metadata should win when the ecosystem cannot agree on a standard.
Wallet Login Is the Trust Boundary
In a social product, identity is the product. Gallery uses wallet signatures to establish that the person behind a profile actually controls the address they claim. That makes authentication part of the social graph, not a separate admin concern.
nonce := auth.NewNonce()
message := auth.NoncePrepend + nonce.Value
signature := walletSignature
if err := authenticator.Verify(message, signature, publicKey); err != nil {
return err
}
return session.FromWallet(publicKey)
The point is not that this is clever crypto. The point is that the backend treats ownership as a prerequisite for participation. You do not just log in. You prove you can speak for the wallet that anchors your social identity.
One of the biggest technical challenges is accurately indexing metadata across different chains and standards. Our indexer is designed to handle this complexity.
The GraphQL Schema Is the Product Surface
Gallery’s schema does more than expose data. It encodes the product. Directives like @authRequired and @goField(forceResolver: true) turn authorization and lazy resolution into schema-level decisions.
type GalleryUser {
id: ID!
wallets: [Wallet!]! @goField(forceResolver: true)
galleries: [Gallery!]! @goField(forceResolver: true)
followers: [GalleryUser!]! @goField(forceResolver: true)
admiring: [GalleryUser!]! @goField(forceResolver: true)
}
type Mutation {
admireGallery(id: ID!): Gallery! @authRequired
followUser(id: ID!): GalleryUser! @authRequired
}
That matters because the schema is where the social model becomes visible to clients. If a field is expensive, private, or relationship-heavy, the schema forces the team to name that cost explicitly.
Token Processing Is Where the Real Compute Lives
NFT systems are full of bad images, missing metadata, giant assets, and awkward file formats. Gallery isolates that pain in `cmd/tokenprocessing`, which makes media handling a real service instead of a utility script.
That separation is a clue. When media normalization gets its own process, it means the bottleneck is throughput, not convenience.
The Monorepo Makes Shared Logic a Feature, Not a Liability
Gallery splits into specialized services, but keeps shared domain logic in one repository. That is not aesthetic minimalism. It is a way to keep feeds, emails, push notifications, auth, and chain logic aligned when they all depend on the same concepts.
| Layer | What it owns | Why it exists |
|---|---|---|
| `cmd/server` | GraphQL API and client-facing reads | It is the product surface. |
| `cmd/tokenprocessing` | Metadata fetch and media transformation | It absorbs the messy ingestion work. |
| `cmd/feed` | Activity feed generation | It turns events into readable social context. |
| `cmd/emails` / `cmd/pushnotifications` | Outbound communication | It fans the graph back out to users. |
| `service/` | Shared auth, multichain, and RPC logic | It prevents business rules from drifting across services. |
The upside is obvious: one domain model, one set of conventions, one place to change the rules. The cost is also obvious: tighter coupling to the repo and to the infrastructure choices baked into it.
Type-Safe SQL Keeps the Backend Honest
Gallery leans on `sqlc` instead of an ORM. That is a good fit for a backend where schema drift and hidden query behavior would quickly turn into production pain.
-- sqlc keeps the query explicit and the Go types generated
SELECT id, wallet_address, created_at
FROM wallets
WHERE user_id = $1
ORDER BY created_at DESC;
Raw SQL gives the team direct control over performance. Generated types keep the interface honest. In a data-heavy social backend, that is a much better trade than opaque abstraction.
Gallery’s Real Competitors Are Not Other Backends
| Project | Primary job | Core data model | Social layer | Display philosophy | Backend implication |
|---|---|---|---|---|---|
| OpenSea-style marketplaces | Trading and discovery | Listings and ownership | Light profile features | Functional first | Optimize for volume and commerce |
| OnCyber | Immersive display | 3D spaces and placed objects | Limited social graph | Spatial and theatrical | Optimize for rendering and experience |
| Lazy.com | Simple portfolio hosting | Minimal collection pages | Thin social layer | Fast and minimal | Optimize for setup speed |
| Gallery | Curated social display | Wallets, galleries, follows, admires | Central to the product | Editorial and collector-led | Optimize for curation and relationship data |
That comparison is the point. Gallery is not trying to be the broadest NFT destination. It is the most opinionated one about what a collection should feel like once ownership data has been normalized.
What This Architecture Suggests About Web3 Products
Gallery’s backend treats blockchain data as substrate, not destination. That is why the repo feels less like an indexer and more like a social product engine that happens to speak multichain.
For any team building around ownership, identity, media, and social proof, the lesson is clear. The hardest part is rarely capturing data. It is turning that data into something people can trust, browse, and care about.





