Skip to content

Build note / Blast Bags

One backend, a web store and a native iOS app.

Blast Bags sells sealed Pokemon packs with published odds and a floor value on every pack. The interesting engineering is not the storefront. It is what happens after someone opens one.

Isaac Benyakar ·

A rip pack is a sealed pack you buy and open on camera, or on a screen. Blast Bags, built for Bhurst Entertainment, publishes the odds on every pack and guarantees a floor value, so the worst outcome is a known outcome. That constraint is a product decision, and it turns out to be an engineering one too, because a floor value is a promise the system has to be able to keep on every single pull.

Store credit is money, so it is a ledger

After opening a pack you can do three things with a card: keep it in a vault, ship the physical card to your door, or sell it back instantly for store credit.

That third option is where a naive build quietly breaks. It is tempting to treat store credit as a number on the user row and add or subtract from it. Do that and you will eventually have a balance nobody can explain, because a number that only stores its current value cannot tell you how it got there.

Credit here is a ledger. Every movement is an entry: a sellback, a purchase, an adjustment made by an admin, a promotional grant. The balance is derived, not stored. This is slower to write and it is the only version you can defend when someone asks why their balance is what it is, or when you need to reverse exactly one bad transaction without recomputing a person's entire history by hand.

The physical side has the same shape. A card in the vault is inventory that belongs to a customer, sitting in a building, waiting for one of two instructions. Shipping is fulfillment. Selling back converts an object into a ledger entry. The admin suite exists because both of those need a human who can see the whole state and correct it.

The reveal is a receipt

Opening a pack is an animated slide-to-rip. It is theater, and I mean that as praise: the animation is doing real work, because a result that simply appears feels like it was decided by the server after you clicked.

The honest framing is that it was. The outcome is determined server side, as it must be. The animation is not deciding anything. What it is doing is giving the result a moment of weight so the number on the screen registers as a thing that happened to you rather than a row that got written. Published odds make the system fair. The reveal is what makes it feel fair, and those are two different jobs.

The iOS app is a client, not a second product

There is a native SwiftUI app: storefront, vault, rewards, and the live-break broadcast dashboard. It runs on the same backend as the web product.

That sentence is doing a lot of work. The common failure with a companion app is that it becomes a second product with its own truth, and then you are maintaining two definitions of what a vault is, two ideas about when credit settles, and a support burden where the answer depends on which screen the customer was looking at. Every rule that matters lives on the server. The app renders state and sends intent, the same as the website does. When the sellback rule changes, it changes once.

What earns its own code on the native side is the stuff that is genuinely native: the feel of the reveal, the broadcast dashboard during a live break, the things where a webview would be obvious. The app currently carries 99 unit tests, which is the number I actually track, because a checkout flow that touches real money is not a place to find out you regressed.

What I would tell someone building this

Decide early whether a balance is a number or a history, because migrating from the first to the second after real money has moved through it is genuinely unpleasant.

And be careful about which promises are load bearing. Published odds and a floor value are marketing lines until you notice that both of them constrain what the pack builder is allowed to generate. The promise on the landing page has to be enforceable in the code that assembles a pack, or it is not a promise.

What it runs on

  • Next.js + TypeScript
  • SwiftUI
  • Supabase
  • Stripe

More detail on the build is in the Blast Bags case study. If you are building commerce where the money and the objects both have to stay in sync, tell me what you are working on.