BlockFrameNFT · SDE1 · Ontario, remote · 2021–2022

A collection is not a wallet. A frame must not hold the key.

In 2021 I was collecting a Gary Vee token and sitting in a Discord where people were arguing about what NFTs still could not do. A week of that turned into the product: one view across every wallet and chain, a way to lease a token instead of selling it, and a frame that could show either one without ever taking the key. BlockFrameNFT was later acquired by TokenFrame.

The gap was a missing join

The technical channel on that Discord was not price talk. It was people comparing what each chain made easy and what it made painful. I was already living the painful version. The token I was collecting sat in one wallet. Other tokens sat in other wallets, and those wallets were not on the same chain. Nothing could add them up.

After about a week of pushing on that, the gap was obvious. A collector's portfolio was scattered on purpose by the ledgers. Each chain is its own namespace. An address on one chain is not an address on another. There is no query you can send that means "everything this person owns." The wallets were never going to connect themselves. Someone had to build the join, and also answer the second thing people kept saying: sometimes you do not want to sell the token. You want to lease it and earn the rent.

Reading a portfolio without becoming a custodian

The app I shipped connected those wallets and drew one collection. The constraint that makes this hard is what you are not allowed to store. A wallet proves ownership with a private key. The moment our process holds that key, we are a custodian, and a compromised phone is a drained wallet. So the app never asks for a key. It asks for addresses, the public half, and it reads chain state through each chain's APIs.

That read is not one protocol. Chains do not share a token format, a metadata shape, or a notion of finality. One indexer is ahead. Another has not seen the last block. An honest portfolio has to keep the chain on the token, or two different assets look like one balance. Native code sat under the Flutter layer because some of that reading, and the wallet connections themselves, did not belong in Dart. The UI joins the results. It does not invent them, and it does not sign anything.

A lease is not a transfer

Selling an NFT moves the token. The ledger changes owners. Leasing must not do that. The owner keeps the asset, and someone else gets a right to show it for a while and pay rent for that right. If the frame only understands "the token is in this wallet," a lease is invisible. The token is still in the owner's wallet. The renter has nothing to point at.

So ownership and display-right are different proofs. Ownership is the token sitting at an address. A lease is a time-bounded permission to cast that token somewhere else. When the lease ends, the permission ends. The token never moved. The frame has to accept either proof, and it has to stop rendering the moment the one it was given is no longer valid.

The enclave is where the cast happens

The in-house frame was the thing we built for that cast. A secure place to put a token you own or a token you lease, and show it, without the device becoming another wallet. Casting is the dangerous step. You are taking a representation of the asset out of the ledger and putting it on hardware that sits in a room. If the key, or an unlimited right to display, lands in the same memory as the UI, then whoever compromises the UI has the asset.

An enclave is a region of the processor the normal operating system cannot read. The proof goes there: this address holds the token, or this lease still permits the cast. The media is decoded for the panel from inside that boundary. Flutter, running in the normal world, receives pixels. It does not receive the key, and it does not receive a proof it could replay somewhere else. This was the first time I worked at that layer. Up to then, native integration meant a method channel into iOS or Android. A method channel still lands in the normal world. The enclave is past it. The processor enforces the split. Application code cannot opt out of it.

Flutter had to leave the phone

The portfolio was a phone app. The frame was not a phone. Same toolkit, different embedder. On iOS and Android the embedder is the platform runner: a surface, input, a thread, the bridge into native APIs. On the frame the embedder is the board. I learned Flutter for embedded here, getting that same UI onto hardware whose job was to be a screen and a lock, not an app store.

The split is the whole design. Embedded Flutter owns what a person sees and touches. The enclave owns whether a cast is allowed. Talk between them is deliberate and small. The UI says which token and which proof. The enclave says yes or no, and supplies the image. Neither side is trusted with the other's job.

What I walked out with

I had gone in collecting a token and arguing in a Discord. I came out able to work two layers under a Flutter widget: the embedder that puts the toolkit on a device, and the processor boundary that decides a cast is safe. The company was later acquired by TokenFrame. The part I keep is the rule the product was built on. You can join every wallet a person has. You can rent the right to show what they own. You still do not take the key.

Back to selected work