In our first post in this series, we explored how Web3 technologies could be used to build a modern REIT with tokenized real estate assets. In our second post, we discussed tokenization in general and how both non-fungible tokens (NFTs) and fungible tokens can each play a role in real estate.
Today, let’s focus on a single question:
How do we actually model an individual building on-chain?
In our implementation, a building would - at a high level - be represented by a custom contract that would be the ‘owner’ of connected, supporting contracts that enable the functionalities of the building itself and its stakeholders.
Each building would be a set of contracts that would allow it to independently manage itself in a decentralized fashion. We decided that a NFT should be used to specify all the metadata about the building, so that standard NFT tools could use that data in a common way. Therefore, each building could be thought of as a NFT in a NFT collection. A Building NFT in such a collection will have its assigned metadata, but a building will also have at least one assigned vault, a treasury, building tokens representing shares for this building, and a related governance contract to make the entire solution work.
So while many of the fundamentals are already familiar from the first and second posts in this series, here we introduce the nitty-gritty of “building deployment.” We’ll discuss three key pieces:
Representing the physical building on chain
Representing real-world property data - For example: a pinned JSON file in IPFS (via Pinata, Filecoin, or other decentralized storage solutions) and the on-chain references to that data.
Deploying the building - Using a “building factory” contract that automatically creates the building’s metadata NFT (and other parts to be discussed in later articles).
We’ll walk through code snippets drawn from the Hedera Accelerator DeFi EIP repository to show how these ideas come to life, especially ERC721Metadata.sol & everything in the buildings contract subdirectory.
This codebase is still in progress and therefore unaudited, but the logic of how to deploy a building is there and ready to be examined. As we move on in this series, this repository will grow and be polished on a rolling basis, and we will also introduce the frontend in the coming weeks.
At the core of this entire process is a physical property: the actual building sitting at some address in the real world. This building has a certain location and size in square feet, has been built in a certain year, and can be in a residential or urban area.
All these attributes matter tremendously in real estate investments, but the challenge is making them discoverable and provable in the context of the blockchain. That’s where the NFT comes in.
Each property is represented by a NFT. We issue one NFT per building, so each NFT is truly unique, just like the real-world building it represents.
This NFT is more than a collectible image, and serves multiple purposes:
Hence, the NFT acts as a digital title that can be transferred or traded, bridging the physical asset with the programmable world of smart contracts.
When we talk about “modeling a building”, we’re not talking about 3D graphics. We’re talking about capturing the property’s essential details - location, year built, local regulations, or even high-res images - and representing them on-chain in a consistent way.
They will most probably be key/value pairs, for instance:
If we look into our codebase, we can see that the ERC721Metadata contract exposes the following function:
function setMetadata(uint256 tokenId, string memory key, string memory value) external onlyTokenOwner(tokenId) whenUnfrozen(tokenId) { _setMetadata(tokenId, key, value); }
onlyTokenOwner(tokenId) ensures that whoever sets or updates metadata controls the building’s NFT, and whenUnfrozen(tokenId) means that if the metadata is “frozen,” no further edits can be made, guaranteeing immutability for certain fields once finalized.
From then on, any contract or frontend can query those fields like this:
function getMetadata( uint256 tokenId, string memory key ) public view returns (string memory)
And retrieve exactly what was stored.
There may actually be multiple NFT collections being created:
The NFT containing the demographics (name/title/size)
The NFT containing the insurance information (also called COPE data).
ERC-721 is flexible enough to represent different categories of metadata. You can mint as many NFTs as needed, some purely for “legal doc references,” others for “demographic info,” etc.
In most scenarios, you don’t want to store large documents or images directly on-chain (that can get expensive, fast). Instead, we use InterPlanetary File System (IPFS) or a pinning service like Pinata to store those bigger files. Then, the on-chain contract keeps track of the IPFS “content identifier” (CID).
The diagram below (imagine Diagram #1) shows how a typical flow might work:
You upload your building’s JSON file, perhaps including an address, square footage, images, and any compliance docs, to IPFS/Pinata.
Pinata returns a CID or gateway URL, e.g. ipfs://Qm1234....
Your smart contract stores that CID or URL in a mapping (tokenID → string) so that the building’s NFT can always reference it.
In the repo, the ERC721Metadata examples show how an NFT can have a tokenURI or a metadata K/V store that points off-chain. A snippet might look like this:
function setTokenURI(uint256 tokenId, string memory uri) external onlyOwner { require(_exists(tokenId), "ERC721: URI set of nonexistent token"); _tokenURIs[tokenId] = uri; emit MetadataUpdated(tokenId, uri); }
Then, when minting the building NFT, you can provide a tokenURI (e.g., ipfs://Qm123abc...) that points to a JSON document in IPFS. For example:
{ "name": "Building at 123 Main Street", "description": "A multi-unit residential property built in 2010", "image": "ipfs://QmSomeHashForThePhoto", }
The NFT’s contract can store just the tokenURI, while the heavy lifting (the actual file or JSON) resides off-chain. This is cost-effective and ensures large data can still be accessed via decentralized means.
Buildings in the real world change. They might undergo renovations or expansions; occupancy might vary over time. The NFT’s metadata should be able to reflect that. In many systems, you might have:
This balance ensures your on-chain building identity remains accurate over time while preserving critical historical facts.
A real estate platform with thousands of building NFTs needs a way to find properties by certain attributes. Our demo application’s approach can support:
function filterTokens(string memory key, string memory value) external view returns (TokenDetails[] memory) { uint256[] memory tokenIds = metadataIndex[_keyValueHash(key, value)]; return _getTokenDetails(tokenIds); }
This means if you’re building a frontend, you can easily run queries like “Show me all buildings where status=UnderConstruction” or “Find all buildings with size >= 1,000 sq ft.”
Note: Real on-chain filtering may require a bit of indexing or an off-chain indexer. The method above is a simplified example showing how to store references for more advanced filtering in your UI.
Let’s say you’re onboarding a new property:
1. Mint the Building NFT:
buildingFactory.newBuilding("ipfs://QmXYZ...") // Deploys building contract + mints tokenId=1 for that property
2. Set Basic Metadata:
building.setMetadata(1, "location", "123 Main Street"); building.setMetadata(1, "yearBuilt", "2010"); building.setMetadata(1, "status", "UnderConstruction");
3. Later Update (e.g. construction completed):
building.setMetadata(1, "status", "Completed");
4. Freeze (once you want to lock something):
building.freezeMetadata(1); // "yearBuilt" can no longer be changed, ensuring historical integrity
Meanwhile, the tokenURI might point to an IPFS JSON with pictures and additional off-chain data - like the insurance binder or scanned floor plans.
Storing metadata is just half the story. We also need to mint a "building token" that investors can buy, sell, or hold to represent partial ownership of that property, as well as contracts to receive yield, pay expenses and govern the day to day activities of the properties like spending and policy changes.Instead of manually deploying each building's contracts by hand, we can automate the whole process with a "factory" contract. Conceptually, for our building NFT, it might look like this:
The admin (or DAO) calls the factory contract with info such as the building name, the IPFS CID, and desired token parameters (total supply, share token name, etc.)
The factory mints an ERC-721 building NFT that references the building’s metadata in IPFS.
At a high level, each building now has an unique NFT that identifies it, with pointers to real-world data.
From a coding standpoint, you can see how we piece it together in the Hedera Accelerator DeFi EIP repo. The building factory is just one example of how to orchestrate multiple contracts behind a single, user-friendly function call.
When looking through the repository, you will see references to the vault, as well as the treasury and governance and other pieces. Don’t worry - as this series progresses, more and more will be explained. For the purpose of this week’s blog post, however, we will solely focus on one function of the BuildingFactory contract:
newBuilding(tokenURI), which deploys a new “Building” contract (via BeaconProxy, for instance), and mints an ERC-721 NFT that references tokenURI for IPFS metadata.
/** * newBuilding Creates new building with create2, mints NFT and store it. * @param tokenURI metadata location */ function newBuilding(string memory tokenURI) public virtual { BuildingFactoryStorage storage $ = _getBuildingFactoryStorage(); BeaconProxy buildingProxy = new BeaconProxy( $.buildingBeacon, abi.encodeWithSelector(Building.initialize.selector, $.uniswapRouter, $.uniswapFactory, $.nft) ); uint256 tokenId = IERC721Metadata($.nft).mint(address(buildingProxy), tokenURI); address identity = IdentityGateway($.onchainIdGateway).deployIdentityForWallet(address(buildingProxy)); $.buildingDetails[address(buildingProxy)] = BuildingInfo( address(buildingProxy), tokenId, tokenURI, identity, address(0) // ERC3643 token lazy deploy ); $.buildingsList.push($.buildingDetails[address(buildingProxy)]); emit NewBuilding(address(buildingProxy)); }
A proxy contract is deployed for the new building.
A unique NFT is minted and owned by that building contract, linking real-world property metadata to this on-chain representation.
An optional identity is established for compliance or identity checks.
All these details are stored in the factory’s state and logged via an event.
That’s the entire “birth” process of a new on-chain building in your system!
After reading this post, you should see how:
In future posts, we’ll cover more capabilities of the building factory and see how these NFTs connect to vaults that distribute rental income, how governance can let token holders vote on renovations, and how composability unlocks DeFi-style possibilities.
Happy Building!