Disclaimer: I’m in no shape or form an NFT or blockchain expert. I just went ahead and read some smart contracts and standards definitions.

I’ve been reading about the NFT tech and how it will revolutionize the world and other bullshit like that since its inception. While I’m a developer myself, the terminology surrounding the NFTs (and the blockchain itself to be honest) has always been confusing. I thought the tech must be as complex as the jargon is.

Now, blockchain is a pretty neat tech. Smart contracts are also interesting. However, when people say NFT Tech I’m not sure what they are talking about. I don’t want to argue that NFTs are good or bad or they have any worth. That’s subjective.

NFTs totally are not a pyramid scheme

What I want to talk is the actual tech behind NFTs. How they work? Is it created by the Jesus himself? We can go over a smart contract but let’s go reverse and try to create an NFT using TypeScript to see how would one do it in the ancient web 2.0 world.

What is an NFT from a programmers perspective anyway?

First thing first. What is an NFT? It is a unique / non replacable deed for ownership of an asset. It could be a house in theory but it is almost always a shitty jpeg masquerading as art.

Nice!

So, lets define some things;

  • We have tokens (monkeys). Lets make them 1-indexed numbers. Token #1 is a unique NFT, #2 is another and so on.

    • This is how it actually works BTW. Here’s the #1 Bored Ape Yacht Club. It costs ~$150.000 at the moment, even after the market crash.
    • They also have some properties like mouth color or whatever but those are not important now.
  • We have owners (or potential owners) of these precious assets. Lets address them using 20 byte hexadecimals. So 0x129c248447096adaaffbadc93bd79819ab57fd72 would be an owner (This also is how it works.) I’ll store these as strings in my code.

Basic attempt

Let’s create a naive NFT implementation using TS, we need some container for the data and a mapping of NFT indices to owners.

interface Token {
    owner : string // owner address
}

class NFT {
  private _tokens : Token[]

  constructor() {
    this._tokens = [];
  }
}

Nice. So we have a NFT class that can hold an array of tokens. Each token can have an owner. Good start.

We need a way to create new tokens

For this sample, let’s assume we always mint (aka create, spawn, print money out of thin air) a new NFT for 1 ether. The way our code works is that when someone calls it on the blockchain, we receive their address an any ether they sent here (Think about AWS Lambda and how it provides request data to lambda functions. This is like lambda running on blockchain):

class NFT {
  private _tokens : Token[]

  constructor() {
    this._tokens = [];
  }

  mint(addressOfCaller: string, etherSent: number) {
    if (etherSent !== 1) {
      throw new Error("You need to send exactly 1 ether for this to work.");
    }

    this.tokens.push({ owner: addressOfCaller });

    return this.tokens.length;
  }
}

That’s it. Anyone can call the mint function attaching 1 ether and they now have an NFT. I mean, when I say they now have an NFT, they have an array entry denoting the ownership of the array index.

Great, but how are they gonna see their NFT?

We’ll need to add another method. Given the ID of an NFT, we just need to provide a URL. A URL that is freaking off the chain. The art, or whatever is not even in the blockchain, you only have a link to it. Let’s add that method too:

  tokenURI(tokenIndex: number) {
    return "https://some.hostname/awesome_nft/" + tokenIndex;
  }

If you are lucky, you get an ipfs address (The BAYC #1 token that I mentioned before has an IPFS URL). Sometimes you get some S3 bucket URL. There is nothing non fungible about those but I digress. At least ipfs makes a lot more sense.

The thing here is, creator of these NFTs will have the tokens uploaded somewhere, ready to be minted by others. There might even be an algorithmic generation step, off chain. In any case, the actual NFT resides somewhere off the chain and the smart contract only knows that you own #N. Whatever lies beyond the URL for #N is out of scope.

So, you have minted an NFT and you have a method to see it. Our shady NFT implementation is going places now.

Now! Let’s sell it..

Ok, you bought or created this NFT for a reason, and that is to sell it for 10x profit. Here we go:

  transferFrom(addressOfCaller: string, newOwner: string, tokenId: number) {
    if (tokens.length < tokenId || tokenId <= 0) {
      throw new Error("Token does not exist.");
    }

    if (tokens[tokenId - 1].owner !== addressOfCaller) {
      throw new Error("You are not the owner of this token.");
    }

    tokens[tokenId - 1].owner = newOwner;
  }

That’s it! If you own a token, you can set the owner to someone else. As the general theme of this article revolves beyond the idea of owning an array index, it is not a big deal to transfer an NFT. Just change the owner value on that array index.

This is the fully assembled version of our tiny little NFT sample code:

class NFT {
  private _tokens : Token[]

  constructor() {
    this._tokens = [];
  }

  mint(addressOfCaller: string, etherSent: number) {
    if (etherSent !== 1) {
      throw new Error("You need to send exactly 1 ether for this to work.");
    }

    this.tokens.push({ owner: addressOfCaller });

    return this.tokens.length;
  }

  tokenURI(tokenIndex: number) {
    return "https://some.hostname/awesome_nft/" + tokenIndex;
  }

  transferFrom(addressOfCaller: string, newOwner: string, tokenId: number) {
    if (tokens.length < tokenId || tokenId <= 0) {
      throw new Error("Token does not exist.");
    }

    if (tokens[tokenId - 1].owner !== addressOfCaller) {
      throw new Error("You are not the owner of this token.");
    }

    tokens[tokenId - 1].owner = newOwner;
  }
}

Looks cute. But, the actual NFT standard should definately be a lot more complex and full of magical blockchain code right?

Nope. This is basically what the NFT standard is. Go check the page it is not some boring RFC but simply an interface definition of ERC7211.

I mean there is an additional piece of the puzzle, the approval mainly aimed for marketplaces 2. But the actual implementation is not too far from this code.

I’m leaving most of the supporting code for events, validation and approvals for marketplaces but will talk about that last one below. I wanted to show how it works under the covers, not try to implement a fully functional NFT container in TypeScript.

You might argue that yeah, this is not that different from a bank account right? Your account balance is simply a database column on some dbms. You’d be right but I don’t think anyone argues that the banking technology is some groundbreaking new age thing that will change the world and the way we appreaciate art and whatever. We just collectively hate bank tech, that’s it.

My argument is not that “it is not a complex tech ergo it is bad”, on the contrary, I love simple tech. My argument is that the cryto people had been pushing the NFT tech as if this is the reimagination of computing or whatever. The blockchain makes these things pretty easy to implement and make them widely available. The actual token (like Tether) also use a similar interface BTW, they don’t have indexes as they are fungible, they just have amounts mapped to addresses.

Besides all the hype, the NFT tech is a basic CRUD application.


  1. ERC721 is the standard interface for NFTs. Most (all?) of the NFTs you’ll find in the wild basically implements this interface. ↩︎

  2. Marketplaces: So you need some intermediaries being able to list / auction and potentially sell your NFTs. You can not simply go ahead and call transferFrom manually and even if you did, who will act as the escrow here? You need central marketplaces that will do it. Ok, how? There is another set of methods in the actual NFT standard and it makes it possible to appoint other addresses to act on your behalf. You can simply set another address to act as if they own the token so that they can call transferFrom when needed. And this is what marketplaces do. They obtain access from the seller and handle the transfer to buyer when a fee is paid. I believe there is an extension to the standard to require a pre determined fee for any transfer to succeed but I don’t think it’s in use. ↩︎