What Is It?
Tableland has launched an NFT-based voting mechanism for Rigs! If you’re new to Tableland, there are a few components to be aware of:
The protocol itself—a decentralized cloud database, built on SQLite, that allows on-chain SQL to drive off-chain database changes.
Tableland Rigs NFT, which uses the protocol for its metadata and is the basis for The Garage.
The Garage application, which provides a way to interact with your owned Rigs, browse others’ activity, and reads data stored on the protocol.
Rig owners have the ability to pilot or park their NFTs. Upon piloting, the Rig starts accruing block-based Flight Time (”FT”) and stops accruing it when you park.
The Garage’s new Voting feature provides a way to reward those who have earned FT! When voting proposals are created, it’s not simply Rig ownership that gates the ability to vote. It’s the FT earned that dictates voting abilities and power, and casting a vote also increases FT as a reputation reward for participating!
A proposal’s outcome is no longer solely tied to the size of your bag but how much you care about what’s in it!
Why Did We Do It?
Background
We launched Tableland Rigs just over a year ago, and a few months later, we released The Garage. From this point forward, any Rig token owner could choose to pilot their Rig, putting it “in flight.” Users could even set a custom pilot—your other NFTs could “operate” your Rigs, displayed via the metadata. Rigs started to earn FT! That initial release also included a “training” mechanism where you had to earn 172800 blocks (~30 days) of FT before you could first operate a Rig with a custom pilot.
This whole idea of piloting your Rig is a parallel to “soft” staking, where the Rigs owner never gives up ownership, but the token is blocked from transfers while it’s in flight. In this way, FT is earned as a reputation reward for your belief and commitment to the project. Parking is synonymous to unstaking in this comparison.
So, we started thinking more and more about token-based reputation and how it can be used in web3, more broadly. How could we turn FT into something that we could actually use to drive engagement within the community, and reward owners appropriately?
We were already using community votes in various ways within the Tableland ecosystem, and Rig holders were our primary voters, so we decided to start there!
Alternatives
Garage voting will now use FT as a core feature. The more FT you’ve earned, the more impact you can have on a vote! But, why would we develop our own voting mechanism instead of using a tool like Snapshot?
With Snapshot, you can definitely create token-gated voting in a similar fashion, but the weights are only based on the number of NFTs you own. There isn’t a way to automatically pull in additional data that could be valuable to the vote—in this case, the Rig’s total FT earned as a reputation proxy.
The limitation around Snapshot’s proposal/vote features was a key driver for developing our own voting mechanism. Not only that, but rewarding users for their participation is not a native Snapshot feature, which is another capability that the Garage’s voting enables!
What Does it Do?
Voting gives Rigs owners a say in the decision-making process. A proposal’s votes are a product of voting power based on total FT. It gives the most committed community members—those who’ve been piloting their Rigs for the longest—a greater say in the vote’s outcome. Additionally, voting will reward those who cast a vote by increasing their total FT by a set amount!
For example:
Holder A owns 5 Rigs, but never piloted any of them.
Holder B owns 3 Rigs, but only starting flying them yesterday.
Holder C owns 1 Rig, and they’ve been flying since day one.
Here, holder A has zero voting power—they never earned any FT! Holder B has some voting power, but holder C has the most. Upon casting their votes, holder C will have the greatest impact on the outcome; reputation driven results!
How Does It Work?
Upon logging into The Garage, you’ll see any newly created proposals in the dashboard. An admin from the Tableland team will create a proposal, including:
Proposal’s name
Description
Vote options
Weighted or non-weighted
Participation reward (FT)
Timeline (starting/ending block number)
Creating a new proposal will initially take a snapshot of all owner’s current FT. Users are then able to connect their wallets and cast their votes. If weighted votes are enabled, you can also distribute your voting power across various options. When the proposal timeline ends, the defined participation reward is then added to the owner’s total FT balance.
How Did We Make It?
For the developers in the house, you might be interested to know more about how we built this, and how you might be able to use Tableland and similar mechanisms for your own community reputation based voting!
Smart Contracts
You can check out the Voting (and other) contracts in the Rigs GitHub repo’s VotingRegistry
contract for the full implementation. The IVotingRegistry
interface contract is pretty straightforward—a few callouts:
Proposal
: struct that holds information about a proposal (name, timeline, rewards, etc.).createProposal
: method for some to create a proposal—e.g., a contract admin set through an inherited OpenZeppelin AccessControl contract.vote
: a user’s casted vote(s) for a proposal ID, option weights, and comments.distributeVoterRewards
: called externally and only distributes rewards if the ending block number for the vote has occurred.
There are also a number of Tableland tables created or used in the design, shown in the VotingRegistry.sol
contract and set in the constructor. These are shown below with some slight variable name tweaks for readability purposes and notes the table’s column names:
// Store the proposal information (ID, name, description CID, etc.) proposals // Store the options available for a given proposal (proposal ID, ID, description) options // When a proposal is created, take a snapshot of current FT for owners (address, FT, proposal ID) ftSnapshot // Track the votes for a proposal (address, proposal ID, option ID, weight) votes // When a vote closes, track rewards for participation (block #, recipient, reason, amount, proposal ID) ftRewards // This is a reference to the Rigs/Pilots contract—it's where FT is stored for piloting/parking sessions pilotSessions
When a proposal is created, the following occurs:
Insert a new row in the proposals table.
Insert options in the options table.
Snapshot all voting power and store it in ftSnaphot.
Insert the “cross product” of all eligible voter addresses and options and votes.
Later, voting will make updates to the votes table, which looks something like this:
UPDATE votes SET weight = CASE option_id WHEN X1 THEN Y1 WHEN X2 THEN Y2 ELSE 0 END WHERE lower(address) = lower(msg.sender);
Once voting has ended, the ftRewards table gets updated:
INSERT INTO ft_rewards (block_num, recipient, reason, amount, vote_id) SELECT DISTINCT BLOCK_NUM(), address, 'participated in vote', amount, proposal_id FROM votes WHERE weight > 0 and proposal_id = ?
There’s a lot more SQL and useful information in the actual contracts, to be sure to check them out!
Web App
The Garage is a React app (via Vite), deployed using Vercel. You can check it out for yourself as well as the source code with voting-related pages/components; its purpose-built for Rigs and offers the following:
Global dashboard—view stats about all Rigs, like the number piloted or FT across all of them.
Personal dashboard—view information about your owned Rigs.
Gallery—filter the Rigs collection and peak into the NFT details.
Proposals—enter to check out current or past proposals and cast your vote.
The app uses wagmi + RainbowKit for the wallet connection, and the styles are set with Tailwind CSS. From there, a number of other packages help make connections to the Rigs and Voting contracts, execute methods, pull in NFT data, and query information directly from the Tableland network.
A Note on FT & Storage
A core theme to understand is where FT is aggregated. The Tableland protocol uses on-chain actions to drive off-chain data materialization. Instead of storing total FT earned on the blockchain (here, it’s Ethereum), only a subset of that data is stored on-chain—essentially, the most recent piloting/parking action taken. That is, the total FT earned is not stored in the contract.
When a user pilots or parks, it runs SQL statements through a “registry” smart contract. This allows for significant gas and cost savings for the token owners. All piloting/parking still takes place on-chain and stored there as calldata, ensuring anyone can recreate the state of a Rig’s total FT. The full history for all FT data itself exists off-chain on the Tableland protocol!
How Can Someone Use It?
Rigs Community
Once a vote is live, if you’re a Rig owner (with earned FT), you can participate within the Garage. Stay tuned for the first vote—we’ll make announcements on both Discord & Twitter, but here’s a sneak peak!
Developers & Their Own Community
For the developers out there, check out the Rigs GitHub repo, including the ethereum and garage directories to see the Rigs/voting smart contracts and web app, respectively. The implementation details described above can be replicated with your own design. For example, see the VotingRegistry
contract and IVotingRegistry
interface for the voting/proposal layout.
Namely, the Voting hinges on looking up data from a pilot_sessions
table that stores some sort of reputation-related information; this table was created when deploying the TablelandPilots
contract. In our case, the reputation calculation is the sum of FT for all sessions tied to an address. You might have a totally different way of collecting/storing that data. It could even be someone else’s table—e.g., you could use the FT data and tables in your own setup! Regardless, once you have a “core” reputation table, the rest of the Voting contract can be slightly tweaked to account for your specific setup.
Get in Touch!
If you’re not familiar with Tableland Rigs, check out the collection on OpenSea, and The Garage app is open for anyone to view—albeit, voting participation will only work for token owners.
And be sure to give us a follow on Twitter and hop into our Discord if you’d like to discuss this design or the protocol in more detail, and give us a start on the Rigs GitHub repo if this walkthrough was beneficial!