Tellers and Shops


From a usage perspective, the Dether protocol was created from day one with the idea of building a worldwide map of POIs (Points Of Interests) for any related crypto service. As a consequence, two main types of services exist in the current state of the world. On the one hand, individuals willing to buy and sell crypto. On the other hand, there are shops (retailers) willing to sell goods and services for crypto. Sometimes, an individual can have both activities. But in order to have the most accurate map possible, we chose tos differentiate those actors from the protocol level.


Tellers are individuals willing to actively buy and/or sell crypto for cash locally. They can make a profit from it as they are free to chose their fee percentage on trades they operate. They first need to own a zone first in order to be visible on the map. As a zone owner, a teller is the exclusive crypto seller that appears on that zone on the map.

How does one become a Dether teller?

First, you need to own a zone: detherJS can be used for that.

Either the zone is free (no one owns the zone) and it is then possible to create a point of sale on it. Or the zone is already taken, and the only way to get it is to open an auction.

Create a zone

in the smart contract

DTH tokens are sent through the ERC223 transfer() function of the DTH contract to the ZoneFactory.sol contract. The _data field is parsed with the information you have entered. If all the requirements are OK, and new Zone.sol and a new Teller.sol are created. These two contracts will be specific to this zone. Even if the teller decides to leave its zone, these 2 contracts will remain associated with this geohash6 forever, so only the owner changes.

* Wait for a tranfer from DTH TOKEN CONTRACT to create zone and teller contract associated with the zone.
function tokenFallback(address _from, uint _value, bytes memory _data) // GAS COST +/- 3.763.729
require(msg.sender == address(dth), "can only be called by dth contract");
require(_data.length == 8, "createAndClaim expects 8 bytes as data");
address sender = _from;
uint dthAmount = _value;
bytes2 country = toBytes2(_data, 0);
bytes6 geohash = toBytes6(_data, 2);
require(geo.zoneIsEnabled(country), "country is disabled");
require(geo.zoneInsideBiggerZone(country, bytes4(geohash)), "zone is not inside country");
require(geohashToZone[geohash] == address(0), "zone already exists");
require(ownerToZone[sender] == address(0), "address already own a zone");
require(activeBidderToZone[sender] == address(0), "sender is currently involved in an auction (zoneFactory)");
// deploy zone + teller contract
address newZoneAddress = createClone(zoneImplementation);
address newTellerAddress = createClone(tellerImplementation);
country, geohash, sender, dthAmount,
address(dth), address(this), taxCollector, newTellerAddress // audit feedback
ITeller(newTellerAddress).init(address(geo), newZoneAddress);
// store references
geohashToZone[geohash] = newZoneAddress;
zoneToGeohash[newZoneAddress] = geohash;
ownerToZone[sender] = newZoneAddress;
// send all dth through to the new Zone contract
require(dth.transfer(newZoneAddress, dthAmount, hex"40"));
emit NewZoneCreated(geohash, newZoneAddress);

In detherJS

In detherJS , you need to call this function with the right parameters and enough DTH tokens:

createZone(password: string, country: string, geohash6: string, amount: number, txOptions: ITxOptions): Promise‹ContractTransaction›

Defined in dether.ts:589


passwordstringPassword of your wallet
countrystringISO 2 country code (
geohash6string6 character geohash of the zone you want to create
amountnumberAmount of DTH you want to stake on the zone (minimum 100 DTH), the address doing the transaction must have the DTH on its address
txOptionsITxOptionsTransaction options args. By defaults (constants.DEFAULT_TX_OPTIONS)

Returns: Promise‹ContractTransaction›

Add teller info

Once a zone is owned, you can then add the teller's information on the newly deployed teller contract.

in the smart contract

You simply need to call the addTeller() function from the new deployed Teller.sol contract. You need to give some params (see detherJS for the full explanation of the params needed).

function addTeller(
bytes calldata _position,
uint8 _currencyId,
bytes16 _messenger,
int16 _sellRate,
int16 _buyRate,
bytes1 _settings,
address _referrer,
uint _refFee, // referral fees x 10 (exemple, for 21.3 % -> 213) Max is 33.3%. The fees its taken from the harbeger taxes
bytes32 _description
// onlyWhenInited
// onlyWhenZoneEnabled
// require(!isContract(_referrer), 'referrer cannot be a contract');
require(_position.length == 12, "expected position to be 12 bytes");
require(toBytes6(_position, 0) == zone.geohash(), "position is not inside this zone");
require(geo.validGeohashChars(_position), "invalid position geohash characters");
require(_refFee <= 333, 'referral fees should inferior to 33.3 %');
require(_currencyId >= 1 && _currencyId <= 100, "currency id must be in range 1-100");
// _messenger can be 0x0 if he has no telegram
if (_settings & isSellerBitMask != 0) { // seller bit is set => teller is a "seller"
require(_sellRate >= -999 && _sellRate <= 9999, "sellRate should be between -999 and 9999");
} else {
require(_sellRate == 0, "cannot set sellRate if not set as seller");
if (_settings & isBuyerBitMask != 0) { // buyer bit is set => teller is a "buyer"
require(_buyRate >= -999 && _buyRate <= 9999, "buyRate should be between -999 and 9999");
} else {
require(_buyRate == 0, "cannot set buyRate if not set as buyer");
teller.addr = msg.sender;
teller.currencyId = _currencyId;
teller.messenger = _messenger;
teller.buyRate = _buyRate;
teller.sellRate = _sellRate;
teller.position = toBytes12(_position, 0);
teller.settings = _settings;
teller.referrer = _referrer;
teller.refFee = _refFee;
teller.description = _description;
emit AddTeller(_position);
in detherJS

You can call the detherJS.addTeller() function with the right params defined in ITellerArgs .

addTeller(password: string, tellerData: ITellerArgs, txOptions: ITxOptions): Promise‹ContractTransaction›

Defined in dether.ts:337


passwordstringPassword of your wallet
tellerDataITellerArgsArguments of the teller you want to add

Returns: Promise‹ContractTransaction›

You can find a full example here in addTellersAndShops.js,


Shops are the second main entity in the Dether protocol. They represent retailers willing to advertise to the Dether community of users the fact that they accept cryptocurrencies as a means of payment. They don't need to own a zone to be visible. When a shop registers its point of interest on a free zone (a zone that is not owned by a teller), the shop needs to stake at least 42 DTH to be visible. When a shop registers its point of interest on a taken zone (a zone that is owned by a teller), the shops pays a daily fee to the zone owner representing 1/42th of the staked tokens of the shop.

How to register a shop?

In smart contractIt is necessary to send the required amount of DTH token to the [Shop.sol] contract with the ERC223 _transfer()_ function. You'll pass a geohash of 12 characters in params for the position you want. The smart contract will check if the position of the shop is in an opened country, and if the position is an owned zone. If it's on a free zone, the number of staked DTH remains the same until the shops is deleted (the staked DTH are then sent back to the address that created the shop), or until a teller owns the zone. If the zone is owned by a teller, you may need to stake more than 42 DTH depending on the staking price defined by the zone owner.
function tokenFallback(address _from, uint _value, bytes memory _data)
require(_data.length == 95, "addShop expects 95 bytes as data");
// // audit feedback
// require(!isContract(_from), 'shops cannot be a contract');
address sender = _from;
uint dthAmount = _value;
bytes1 fn = toBytes1(_data, 0);
require(fn == bytes1(0x30) || fn == bytes1(0x31), "first byte didnt match func shop");
if (fn == bytes1(0x31)) { // shop account top up
_topUp(sender, _value);
} else if (fn == bytes1(0x30)) { // shop creation
bytes2 country = toBytes2(_data, 1);
bytes12 position = toBytes12(_data, 3);
bytes16 category = toBytes16(_data, 15);
bytes16 name = toBytes16(_data, 31);
bytes32 description = toBytes32(_data, 47);
bytes16 opening = toBytes16(_data, 79);
require(geo.zoneIsEnabled(country), "country is disabled");
require(shopAddressToShop[sender].position == bytes12(0), "caller already has shop");
require(positionToShopAddress[position] == address(0), "shop already exists at position");
require(geo.validGeohashChars12(position), "invalid geohash characters in position");
require(geo.zoneInsideBiggerZone(country, bytes4(position)), "zone is not inside country");
// check the price for adding shop in this zone (geohash6)
uint zoneValue = zoneLicencePrice[bytes6(position)] > floorLicencePrice ? zoneLicencePrice[bytes6(position)] : floorLicencePrice;
require(dthAmount >= zoneValue, "send dth is less than shop license price");
// create new entry in storage
Shop storage shop = shopAddressToShop[sender];
shop.position = position; // a 12 character geohash
shop.category = category; = name;
shop.description = description;
shop.opening = opening;
shop.staked = dthAmount;
shop.hasDispute = false;
shop.disputeID = 0; // dispute could have id 0..
shop.geohashZoneBase = bytes6(position);
shop.licencePrice = zoneValue;
shop.lastTaxTime = now;
stakedDth = stakedDth.add(dthAmount);
// so we can get a shop based on its position
positionToShopAddress[position] = sender;
// a zone is a 6 character geohash, we keep track of all shops in a given zone
shop._index = zoneToShopAddresses[bytes6(position)].push(sender) - 1;

To know the price of the zone, it's possible to call:

getZoneLicencePrice(bytes6 zone6)

If the zone is free, the staking price is 42 DTH without taxes. If the zone is owned, it can be whatever staking price the zone owner has previously set, along with a daily fee of 1/42th of the staking price.

In DetherJS

Everything is handled in dehterJS, you only need to call this function

addShop(password: string, shopData: IShopArgs, txOptions: ITxOptions): Promise‹ContractTransaction›

Defined in dether.ts:438


passwordstringPassword of your wallet
shopDataIShopArgsparams of the shop you want to add
txOptionsITxOptionsTransaction options args. By defaults (constants.DEFAULT_TX_OPTIONS)

Returns: Promise‹ContractTransaction›

To know the staking price to register a shop on a specific zone, you can call:

getLicencePriceInZone(geohash6: string): Promise‹string›

Defined in dether.ts:433



Returns: Promise‹string›