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.

9 min read • View on GitHub • More from gallery-so

A curator stands between a museum archive and a control room while a single artwork moves through wallets, metadata crates, and server racks. The scene explains Gallery’s inversion: blockchain data is not the destination, it is raw material for a social product.
Gallery’s backend treats onchain assets as inputs to curation, identity, and social interaction.
Key Takeaways

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.

m4thew, Co-founder · Introducing Gallery

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.

A close-up hand sorts tangled metadata tags from multiple chain-specific containers into a clean tray of prioritized fields. The image explains how Gallery resolves inconsistent NFT metadata before it reaches the gallery wall.
The hard work is not fetching metadata. It is deciding which fields are canonical.

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.

Weston, Co-founder/Developer · X Post by Weston

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.

LayerWhat it ownsWhy it exists
`cmd/server`GraphQL API and client-facing readsIt is the product surface.
`cmd/tokenprocessing`Metadata fetch and media transformationIt absorbs the messy ingestion work.
`cmd/feed`Activity feed generationIt turns events into readable social context.
`cmd/emails` / `cmd/pushnotifications`Outbound communicationIt fans the graph back out to users.
`service/`Shared auth, multichain, and RPC logicIt 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

ProjectPrimary jobCore data modelSocial layerDisplay philosophyBackend implication
OpenSea-style marketplacesTrading and discoveryListings and ownershipLight profile featuresFunctional firstOptimize for volume and commerce
OnCyberImmersive display3D spaces and placed objectsLimited social graphSpatial and theatricalOptimize for rendering and experience
Lazy.comSimple portfolio hostingMinimal collection pagesThin social layerFast and minimalOptimize for setup speed
GalleryCurated social displayWallets, galleries, follows, admiresCentral to the productEditorial and collector-ledOptimize 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.