Field note
How We Turned Animal Parts into Bitcoin Collectibles
The character generator, rarity rules, and inscription infrastructure behind Ordi Gotchis.
- Bitcoin
- Ordinals
- WebGL
- NestJS
- TypeScript
- AWS
The starting point for Ordi Gotchis was a set of bird and animal parts in different colors. We needed to combine those assets into characters, assign their rarity, and make the resulting collectibles available on Bitcoin. The same choices that determined a character's appearance also had to explain its place in the collection.
I was the core developer on the project at Zogi Labs. I built the generation and rarity logic, handled the original inscription work, and set up the Ordinals infrastructure on AWS. Other team members contributed to the UI and other parts of the product. Together, that work supported a WebGL game with Bitcoin-based collectibles.
The generator worked with a finite set of assets and explicit classification rules. The Bitcoin infrastructure had to build and maintain its view of an external system before the application could rely on it. A character's path through the product crossed both: from a reproducible selection of parts to an inscription whose status depended on transactions and indexed chain data.
Making a character reproducible
We organized the artwork into part categories, each containing the available variants. A character was a selection across those categories. That selection provided the inputs for both rendering and rarity classification.
The generator lived in the NestJS backend, with TypeScript for the application logic and TypeORM for persistence. It used a recursive traversal to enumerate combinations: select an option from the first category, continue through the remaining categories, and produce a complete result when every category has a selection.
This is a Cartesian product. If the categories contain n1, n2, through nk options, the unrestricted generator has n1 × n2 × ... × nk possible results. An additional variant in one category expands the combinations involving every other category.
That multiplication affects the work that follows generation. Every complete selection can require a rendered result and associated application data. The size of the asset library therefore influences the size of the collection the software can produce, as well as the amount of processing behind it.
Keeping the selected attributes with the generated result was important. They described how a character had been assembled and gave the rarity logic a concrete input. A character's appearance and classification could both be traced back to that selection.
Composing the artwork
I used HTML and canvas in the generation work to compose the chosen parts. The generator created HTML that loaded the selected assets and drew them onto a 2500 by 2500 canvas using drawImage.
A layered character depends on a shared coordinate system. The eyes, beak, fur and highlights need compatible positioning, and the drawing order must preserve their intended relationship. The combination can be valid as data while the composition still needs those visual constraints to hold.
The generated representation was associated with the character's attributes and rarity in the backend. Preparing content for Bitcoin inscription was a subsequent step. That separation matters because a successful browser render establishes that the artwork can be displayed in its current environment. The inscription process establishes the Bitcoin artifact.
Rarity came from the combination
Our rarity system evaluated how the color or breed attributes matched across the selected parts. The categories included Common, Rare, Epic, MixedBreed and Purebred. Fully matching parts belonged to the Purebred category; the rules for MixedBreed included four matching parts and one different part.
These were explicit classification rules applied to the generated combinations. That made the reason for a character's category explainable through the same attributes used to render it.
An interesting consequence is that an ordinary individual trait can participate in an unusual complete character. A particular color might appear regularly on each part, while a character using that color everywhere occupies a much smaller portion of the combination space.
For a mathematical illustration, consider five part categories with five equally available colors in each. There are 3,125 color combinations, but only five use one color across all five parts. Those fully matching combinations represent 0.16 percent of that hypothetical space.
This example describes the relationship between individual choices and complete patterns. It is not a count of the Ordi Gotchis collection or a statement about minting odds. The distribution of an issued collection depends on which generated results are actually included and how they are allocated.
The useful design property was the connection between appearance and classification. The player could see the color pattern that the application was evaluating. Rarity was attached to a recognizable property of the character, and the backend had the underlying attributes needed to explain the result.
Establishing the collectible on Bitcoin
I handled the original inscription work and the application integrations around it, including the wallet experience involving UniSat.
An Ordinals inscription consists of a content type and content bytes. Creating it uses a commit/reveal process involving two transactions:
- A commit transaction creates a Taproot output committing to a script containing the inscription content.
- A reveal transaction spends that output and exposes the content in the transaction witness.
Ordinals interprets the content and its association with a particular satoshi. The inscription identifier combines the reveal transaction ID with an index identifying the inscription within that transaction.
This gives the application a Bitcoin identifier to associate with its own collectible record. The character's internal record, its selected attributes, and its inscription identifier have separate jobs. The first belongs to the application, the attributes explain the generated character, and the inscription identifier locates the Bitcoin artifact.
Recursive inscriptions add another capability: inscription content can reference content from other inscriptions. The protocol supports reusing images or code, including composing artwork from separately inscribed components. This is a separate mechanism from using recursion in the generation algorithm. One enumerates combinations in application code; the other allows inscription content to refer to on-chain resources.
The distinction is useful when describing a generative collectible. Producing its appearance, classifying its traits, and establishing its inscription are different operations. Each needs a clear relationship to the same character.
Getting the Ordinals server ready
Setting up the Ordinals server on AWS introduced a challenge that had little to do with drawing characters: the infrastructure needed time to synchronize with Bitcoin.
There were several kinds of work behind that synchronization:
Swipe horizontally to view the full table.
| Component | What it establishes |
|---|---|
| Bitcoin Core | A locally validated view of the blockchain. |
| Bitcoin Core's transaction index | The lookup needed to retrieve historical transactions by transaction ID. |
| The Ordinals index | The inscription-specific view used to identify inscriptions and track their locations. |
Bitcoin Core's initial synchronization downloads and validates blocks. The transaction index has its own synchronization status. The Ordinals index supplies another view of that data for inscription operations. These processes can be at different points in their work.
During setup, I encountered a transaction lookup error that pointed to txindex. That error belongs to a specific part of the stack. Bitcoin Core's getrawtransaction normally searches the mempool; historical lookup without a supplied block hash depends on the transaction index. The Ordinals setup guide also calls out waiting for that index to finish synchronizing.
The practical lesson was that a running process and a usable data view are separate milestones. An application relying on an inscription index needs to know whether the index has reached the data relevant to the operation it is checking.
That affects the interpretation of an apparently missing collectible. If the local index has not processed the relevant block, absence from that index cannot establish that inscription failed. The state of the index is part of the evidence needed to understand the result.
Synchronization therefore belongs in provisioning and deployment planning. A fresh server has historical data to process before it can support a current view of the collection. The readiness question extends beyond whether the process has started to whether its data is sufficiently current for the application to rely on it.
Connecting the system to the game
The surrounding application used Next.js and React for the web experience, NestJS and Node.js for backend services, and PostgreSQL and Supabase within the data platform. The WebGL game and wallet interactions sat within that broader application.
The generation work gave us characters with associated attributes and rarity. The inscription work connected collectibles to Bitcoin. The application then had to present those results in terms a player could understand, while the backend and infrastructure supplied the information behind them.
That is where the difference between transaction progress and indexed visibility becomes important. Broadcasting a transaction, seeing it included in a block, and observing its inscription through an index are separate events. They provide different evidence about how far an operation has progressed.
This work became part of the Ordi Gotchis product we brought to production. For a player, the result was a character they could encounter through the game and own as a Bitcoin collectible. Behind it were the selected parts, the rules assigning its rarity, and the infrastructure supporting its inscription.
A successful inscription needed to remain connected to the character the generator had assembled and the category the application had assigned. Preserving that connection, while accounting for how Bitcoin transactions and indexes progress, was the core integration problem I worked on.