Token curated registry, the DataBroker DAO way

DataBroker DAO is the first marketplace to sell & buy sensor data.

Since the platform is built on revolutionary blockchain technology, we want to zoom in on some technical aspects. In this article, we’ll dive into a concept that is increasing in popularity: the token curated registry. We want to give you some insights into what this term means, and how we implemented it in DataBroker DAO.

There are a lot of great articles out there explaining the concept of a token curated registry (this one written by ConSensys’ Mike Goldin or this one covering the Delphi approach to name a few). What they all describe, is the following:

  1. it lists something;
  2. stakeholders of the system determine the appearance/sorting index/… on the list

In practice, a certain amount of tokens (a stake) has to be locked-up to enlist something in the registry. The higher the stake, the higher the guarantee that what you put in the registry is of sound quality. Some TCRs put in place a voting system. A certain amount of users needs to vote that the (potential) listing is sound before it appears publicly in the registry.

Others opt for a challenging system, that allows users to mark a listing as bad.

The DataBroker DAO way

For DataBroker DAO, we chose for the challenge approach. That way a sensor owners can add a new stream to the registry without having to wait for the minimum amount of stakeholders to approve it. At the same time, users are still granted the opportunity to remove bad content from the registry.

Adding data

When a sensor owner wants to add new data to the marketplace, he will have to lock-up a certain amount in tokens (a stake) to be listed in the registry.If they want to, sensor owners can stake more DTX. This allows the listed streams/sets to appear more prominently in the listings (e.g., sorting, or additional badges in the interface). A higher listing improves the chances of the data being bought. At the same time increases the guarantees a buyer has that the data is of good quality and that it contains the advertised information.

Challenging data

A data buyer that is unhappy with the quality of data can challenge an entry in the registry by staking DTX tokens. The UI will mark this entry with a negative reputation score. In itself, it does not have any effect on selling of the data. Upon reaching a certain threshold of challenges, a DataBroker DAO administrator will check the data. Upon finding issues with the advertised data, its stake is distributed equally over all challengers and the DataBroker DAO platform wallet, and the entry is removed from the registry. If the data is deemed sound, the tokens that were staked by the challenger(s) get distributed to the data seller and the platform. This gives data sellers an incentive to maintain a good standing and to deliver data as advertised.

Data buyers are encouraged to report bad data to recoup the lost funds due to bad data, and discouraged from reporting false challenges. The seller can reduce lost funds due to unfair bad reputation. The DataBroker DAO platform and its administrators are encouraged to handle these disputes quickly and efficiently and are rewarded for their time and effort.

Technical implementation

Since DataBroker DAO is an opensource project, the distributed API can be found on GitHub. For the scope of this article, we’ll zoom in on some parts of the Solidity smart contracts we wrote to power the DataBroker DAO token curated registry.

Enlisting data

The enlist method in the TokenCuratedRegistry.sol contract looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function enlist(uint _stakeAmount, uint _price, string _metadata) external {
// Stake must be above a certain amount
require(_stakeAmount >= minEnlistAmount);

// Transfers tokens from user to Registry contract
require(token.transferFrom(msg.sender, this, _stakeAmount));

// Add listing to listings
listingFactory.createListing(
msg.sender,
_price,
_stakeAmount,
this,
_metadata
);
}

We expect the amount of DTX tokens to be locked-up as stakeAmount, the price in DTX tokens as price and an IPFS-hash metadata for different extra properties we want to attribute to our listings.

After some checks and transfering the stake to the TCR contract to be locked up, we call a factory ListingFactory.sol that will create the actual listing for us:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createListing(address _owner, uint _price, uint _stakeAmount, address _tcr, string _metadata) public {
// Create listing
Listing _newListing = new Listing(_owner, _price, _stakeAmount);

// Add metadata
_newListing.updateMetaData(_metadata);

// Event
ListingCreated(address(_newListing));

// Add listing to token curated registry
TokenCuratedRegistry tcr = TokenCuratedRegistry(_tcr);
TokenCuratedRegistry.addListing(address(_newListing));
}

We chose to create a seperate listing contract to give us more flexibility later, because we want to add purchase subcontracts on these listing contracts. When the listing is created, we call another method on the TCR contract to actually add the listing to the TCR:

1
2
3
4
5
6
7
8
function addListing(address _listing) external {
Listing listing = Listing(_listing);
listings[_listing] = listing;
listingsIndex.push(_listing);

// Event
Enlisted(_listing, listing.stake(), listing.price());
}

The listing contract itself looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
contract Listing {

uint public price = 0; // Price
uint public stake = 0; // Number of unlocked tokens with potential risk if challenged
uint public challenges = 0; // Number of unresolved challenges on the listing
uint[] public challengeIDs; // Array of the different challenge IDs
uint public challengesStake = 0; // Stake with challenged stake added

bool public whitelisted = true; // Should the data appear on the listing or not
address public owner; // Owner of Listing

/**
@dev Contructor
@param _owner Address of the owner
@param _price Price for the listing
@param _stakeAmount Amount staked for the listing
*/

function IListing(
address _owner,
uint _price,
uint _stakeAmount,
)

public
{

owner = _owner;
price = _price;
stake = _stakeAmount;
}
}

Raising a challenge

Challenges are also raised on the TokenCuratedRegistry.sol contract:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function challenge(address _listing, uint _stakeAmount) external {
require(_stakeAmount >= minChallengeAmount);

Listing listing = listings[_listing];

// Takes tokens from challenger
require(token.transferFrom(msg.sender, this, _stakeAmount));

// Add challenge to the challenges mapping
Challenge memory challenge = Challenge({
challenger: msg.sender,
stake: _stakeAmount,
resolved: false,
listing: _listing
});
challenges[challengeID] = challenge;

// Increase the number of challenges on the right listing
listing.setChallenges(listing.challenges() + 1);
// Add challengeID to listing
listing.addChallengeID(challengeID);
// Add to the challengesStake on a listing
listing.setChallengesStake(listing.challengesStake() + _stakeAmount);

Challenged(_listing, _stakeAmount, challengeID);
}

Check it out

You can check out our new discovery interface here.
This article was originally posted on the DataBroker DAO Medium, check it out here.