How I Built My Own Cryptocurrency With FlureeDB

Boiled down to its bare essentials, a cryptocurrency has three key characteristics:

  1. It is a digital asset.
  2. That asset is exchanged on an immutable ledger of transactions (a blockchain).
  3. Identities on the ledger are confirmed via cryptography.

A successful cryptocurrency (read: one that actually holds value) also depends on having a vibrant network. This could mean that there are enough individuals buying and selling the currency that it acts as a viable and relatively-liquid store of value. Alternatively (or additionally), the cryptocurrency is exchanged for an in-network good or service (i.e. Ethereum’s gas-powered smart contracts). FlureeDB is well-suited towards the latter.

In this example, I make a frills-free cryptocurrency using FlureeDB. By the end of the tutorial, you will be able to spend the crypto in your wallet and receive crypto from other users. This is a small, simple project that can be expanded to enable service-exchange within a larger enterprise application. For example, an enterprise (or cross-enterprise) application could use this lightweight crypto to meter network usage, enable micro-transactions, and unlock application features.

Naming Our Crypto

Every cryptocurrency needs a name, and the existence of cryptos like “Unobtanium Condensate” makes it critical to include this section in our tutorial. If your crypto name makes people cringe, it might be time to hit the drawing board again.

For inspiration, I used the Coin Name Generator, and I’m calling my crypto “CashCashCash”, or 3C for short (okay, maybe I shouldn’t be one to judge).

Creating a Schema

To begin creating CashCashCash, I first generate a schema.

In FlureeDB, we use collections, which are roughly analogous to relational database tables. Each collection has attributes. For our cryptocurrency, we use one collection, which I call crypto.

Crypto has three attributes:

  1. crypto/balance, which tells you how much crypto you have.
  2. crypto/user, which is a reference to a _user (a built-in system collection for users)
  3. crypto/walletName, which is a unique name for each crypto wallet.
[{
"_id": "_collection",
"name": "crypto"
},
{
"_id": "_attribute",
"name": "crypto/balance",
"type": "int"
},
{
"_id": "_attribute",
"name": "crypto/user",
"type": "ref",
"restrictCollection": "_user"
},
{
"_id": "_attribute",
"name": "crypto/walletName",
"type": "string",
"unique": true
}]

Ensuring Non-Negative Balances

Once we have a crypto/balance attribute, it would be helpful to never let our balance fall below zero. We do this by creating a database function, nonNegative?, that returns true if a value is non-negative and false if the value is negative.

Then, we add this function to the _attribute/spec for crypto/balance. Whenever we add or edit an attribute, if that attribute contains a spec or multiple specs, the value is tested against all referenced functions. In our case, we only have one referenced function, nonNegative?.

[{  "_id":  ["_attribute/name", "crypto/balance"],
"spec": ["_fn$nonNegative?"],
"specDoc": "Balance cannot be negative."
},
{
"_id": "_fn$nonNegative?",
"name": "nonNegative?",
"code": "(< [-1 (?v)])"
}]

Currently, our functions are written using a special FlureeDB function syntax, inspired by Clojure. In the future, we aim to support multiple languages/ function syntax patterns.

Creating a Crypto User Role

In FlureeDB, query and transaction permissions are controlled by rules. First, rules are defined, and grouped into roles. These roles, in turn, are assigned to auth records.

In this example, we assign auth records to users for convenience, but all queries and transactions can be performed directly by auth records.

The below transaction creates two users, cryptoMan and cryptoWoman. Both auth records are given the role, cryptoUser. A cryptoUser can query their crypto, and transact on any crypto/balance. No one, other than the root user, can transact on crypto/user. This means that cryptoMan and cryptoWoman cannot create new wallets. This may not be the ideal behavior for your own cryptocurrency, but you can use the code below as a template.

Note that by default, each database has a [“_fn/name”, “true”] and [“_fn/name”, “false”], which return true or false, respectively. By using these function in rules,we allow or deny access to perform the _rule/ops specified in the _rule/collection and _rule/attributes.

[ {
"_id": "_user$cryptoMan",
"username": "cryptoMan",
"auth": ["_auth$cryptoMan"]
},
{
"_id": "_user$cryptoWoman",
"username": "cryptoWoman",
"auth": ["_auth$cryptoWoman"]
},
{
"_id": "_auth$cryptoWoman",
"id": "cryptoWoman",
"doc": "cryptoWoman auth record",
"roles": ["_role$cryptoUser"]
},
{
"_id": "_auth$cryptoMan",
"id": "cryptoMan",
"doc": "cryptoMan auth record",
"roles": ["_role$cryptoUser"]
},
{
"_id": "_role$cryptoUser",
"id": "cryptoUser",
"doc": "Standard crypto user",
"rules": ["_rule$viewOwnCrypto", "_rule$editAnyCryptoBalance", "_rule$cantEditCryptoUser"]
},
{
"_id": "_rule$editAnyCryptoBalance",
"id": "editAnyCryptoBalance",
"doc": "Any cryptoUser can edit any crypto/balance.",
"predicate": [["_fn/name", "true"]],
"ops": ["transact"],
"collection": "crypto",
"attributes": ["crypto/balance"]
},
{
"_id": "_rule$viewOwnCrypto",
"id": "viewOwnCrypto",
"doc": "A cryptoUser can only view their own balance",
"predicate": [“_fn$ownCrypto”],
"ops": ["query"],
"collection": "crypto",
"collectionDefault": true
},
{
"_id": "_rule$cantEditCryptoUser",
"id": "cantEditCryptoUser",
"doc": "No one, other than root, should ever be able to edit a crypto/user",
"ops": ["transact"],
"collection": "crypto",
"attributes": ["crypto/user"],
"predicate": [["_fn/name", "false"]],
"errorMessage": "You cannot change a crypto/user. "
},
{
"_id": "_fn$ownCrypto",
"name": "ownCrypto?",
"code": "(contains? (get-all (?e) [\"crypto/user\" \"_id\"]) (?user_id))"
}]

Adding Initial Balance

A real, successful cryptocurrency should include a way of adding more currency to the economy, such as mining. In our very simple example, the “central bank,” AKA our root user, issues our two users 200 CashCashCash each.

[{
"_id": "crypto",
"walletName": "cryptoWoman's Wallet",
"balance": 200,
"user": ["_user/username", "cryptoWoman"]
},
{
"_id": "crypto",
"walletName": "cryptoMan's Wallet",
"balance": 200,
"user": ["_user/username", "cryptoMan"]
}]

Restricting Crypto Spending

At this point, we have a fairly useless cryptocurrency. Anyone can transact anyone else’s crypto/balance, and there are no protections against someone transacting your crypto/balance to 0. In order to prevent this, we can create a rule that only allows you to withdraw from your own crypto/balance or deposit in another user’s crypto/balance.

The _fn/code we use for this is fairly long, so we will break it down together:

(or [
;; You are the root user
(== [0 (?auth_id)])
;; If you are not the root user
(if-else
;; Are you transacting your own crypto?
(contains?
(get-all (?e) [\"crypto/user\" \"_id\"])
(?user_id)
)
;; Yes? New value (?v) must be less than previous value (?pV)
(> [(?pV) (?v)])
;; No? New value (?v) must be more than previous value (?pV)
(< [(?pV) (?v)])
)])

The database function checks whether you are the root user ((== [0 (?auth_id)])). If so, you have no restrictions on which crypto you can add or remove. In your own cryptocurrency, you can choose whether or not to include a “backdoor” for a root user.

If you are not the root user, the function checks whether you are transacting your own crypto or not ((contains? (get-all (?e) [\”crypto/user\” \”_id\”]) (?user_id))).

If you are transacting your own crypto, the new crypto/balance value has to be less than the previous value ((> [(?pV) (?v)])).

If you are transacting another user’s crypto, the new crypto/balance value has to be greater than the previous value ((< [(?pV) (?v)])).

In order to put this _fn/code into effect, you need to first create a function, and then add it to the crypto/balance attribute spec.

[{
"_id": "_fn$subtractOwnAddOthers?",
"name": "subtractOwnAddOthers?",
"code": "(or [(== [0 (?auth_id)])(if-else (contains? (get-all (?e) [\"crypto/user\" \"_id\"]) (?user_id))  (> [(?pV) (?v)]) (< [(?pV) (?v)]))])",
"doc": "You can only add to others balances, and only subtract from your own balance"
},
{
"_id": ["_attribute/name", "crypto/balance"],
"spec": ["_fn$subtractOwnAddOthers?"],
"specDoc": "You can only add to others balances, and only subtract from your own balance."
}]

Crypto Spent = Crypto Received

The last piece of our currency is ensuring that crypto spent is equal to crypto received. After all, it would be utter chaos if cryptoMan could withdraw 1 3C from his own wallet and put 200 3C into cryptoWoman’s wallet. We could see quite the runaway inflation!

For this purpose, we can use the txSpec attribute. _attribute/txSpec is run once per attribute in a transaction. For example, if we create an _attribute/txSpec for crypto/balance, our transactor will group together every flake (fact about a specific entity at a specific point in time) that changes the crypto/balance attribute and only run the txSpec once per transaction. txSpec allows us to perform functions, such as summing all the crypto/balance values in a transaction.

The function (valT) takes no arguments, and sums all the true flakes in a transaction for the given _attribute. Likewise, the function (valF) takes no arguments, and sums all the false flakes in a transaction for the given _attribute. We want to make sure that the sum of all of the crypto/balances being retracted equals the sum of those being added.

[{
"_id": ["_attribute/name", "crypto/balance"],
"txSpec": ["_fn$evenCryptoBalance"],
"txSpecDoc": "The values of added and retracted crypto/balance flakes need to be equal"
},
{
"_id": "_fn$evenCryptoBalance",
"name": "evenCryptoBalance?",
"code": "(== [(valT)  (valF)])",
"doc": "The values of added and retracted crypto/balance flakes need to be equal"
}]

Gloat!

Now, all the pieces of our cryptocurrency are in place. We have created a cryptocurrency with the following features:

  1. Balances can never be negative.
  2. A user may only withdraw from their own account.
  3. A user may only add to another user’s account.
  4. When withdrawing or adding, the amount withdraw has to equal the amount added.

For example, with our final cryptocurrency, no user can perform the following transaction, because it violates feature #4, as listed about.

[{
"_id": ["crypto/walletName", "cryptoMan's Wallet"],
"balance": "#(+ [(?pV) 10])"
},
{
"_id": ["crypto/walletName", "cryptoWoman's Wallet"],
"balance": "#(- [(?pV) 5])"
}]

The following transaction spends as much cryptocurrency as it receives. However, because it is withdrawing from cryptoMan’s Wallet and adding to cryptoWoman’s wallet, only cryptoMan can initiate the transaction.

[{
"_id": ["crypto/walletName", "cryptoMan's Wallet"],
"balance": "#(- [(?pV) 10])"
},
{
"_id": ["crypto/walletName", "cryptoWoman's Wallet"],
"balance": "#(+ [(?pV) 10])"
}]

This simple cryptocurrency can be worked into any existing FlureeDB application. Two ideal use cases for this simple currency are:

  1. An application where users perform many micro-transactions over a given period of time. Users can transact freely without fees and then settle up at the end of the month.
  2. An application where currency is used for in-app processes. For example, if you have a message board application, crypto can be accrued for each helpful comment that you write, and then expended when you want to create your own discussion item.

What other use cases can you think of for this simple cryptocurrency?


Get started on FlureeDB (hosted our downloadable .jar file): www.flur.ee

FlureeDB Documentation: www.docs.flur.ee