Lillian Podlog
Fluree PBC
Published in
10 min readSep 10, 2018

--

How I Built My Own Cryptocurrency With Fluree

EDIT: Updated May 15, 2019 to work with downloadable version 0.9.5.

How I Built My Own Cryptocurrency With Fluree

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 predicates. For our cryptocurrency, we use one collection, which I call wallet.

wallet has three predicates:

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

Adding Sample Data

Before we add in any rules around our data, it is a best practice to add in some initial data to work with. Here, we’ll add two users and two wallets.

[{
"_id": "_user$cryptoMan",
"username": "cryptoMan"
},
{
"_id": "_user$cryptoWoman",
"username": "cryptoWoman"
},
{
"_id": "wallet$cryptoMan",
"name": "cryptoMan",
"balance": 200,
"user": "_user$cryptoMan"
},
{
"_id": "wallet$cryptoWoman",
"name": "cryptoWoman",
"balance": 200,
"user": "_user$cryptoWoman"
}]

Decide Who Can Edit What

Now, we are going to create rules around who can edit which predicates. Note that there are many ways of building the same application. The steps in this tutorial are not the only way, and may not even be the best way!

We’re going to place the following restrictions on the predicates in the wallet collection:

- wallet/name — only the owner of the wallet can edit

- wallet/balance — anyone can edit

-wallet/user — no one can edit.

First, we’re going to create a function, which checks to see if the user attempting to make the update (?user_id) is the owner of the wallet. The full function is:

(contains? (get-all (?s \”[{wallet/user [{_user/auth [_id]}] }]\”) [\”wallet/user\” \”_user/auth\” \”_id\”]) (?auth_id))

This function starts with

(?s \”[{wallet/user [{_user/auth [_id]}] }]\”)

This is the subject being updated (a wallet), and following the subject to get all the auth associated with that wallet’s user.

We then get all the _ids associated with those auth records, and that is returned as a set.

(get-all (?s \"[{wallet/user [{_user/auth [_id]}] }]\")[\"wallet/user\" \"_user/auth\" \"_id\"])

In this case, it is a set of one, single _id. Then we see if this _id matches the auth record attempting the update.

[{
"_id": "_fn$ownWallet",
"name": "ownWallet?",
"code": "(contains? (get-all (?s \"[{wallet/user [{_user/auth
[_id]}] }]\") [\"wallet/user\" \"_user/auth\" \"_id\"])
(?auth_id))"
},
{
"_id": "_rule$editOwnWalletName",
"id": "editOwnWalletName", "doc": "A cryptoUser can only edit their own wallet/name", "fns": ["_fn$ownWallet"], "ops": ["transact"], "collection": "wallet", "predicates": ["wallet/name"]}]

Then, we’ll use the built-in smart functions, true and false to make sure wallet/balance can be updated by anyone, and the wallet/user cannot be edited by anyone.

[{
"_id": "_rule$editAnyCryptoBalance",
"id": "editAnyCryptoBalance",
"doc": "Any cryptoUser can edit any crypto/balance.",
"fns": [["_fn/name", "true"]],
"ops": ["all"],
"collection": "wallet",
"predicates": ["wallet/balance"]
},
{
"_id": "_rule$cantEditWalletUser",
"id": "cantEditWalletUser",
"doc": "No one should be able to edit a wallet/user.",
"fns": [["_fn/name", "true"]],
"ops": ["transact"],
"collection": "wallet",
"predicates": ["wallet/user"],
"errorMessage": "You cannot change a wallet/user."
}]

Now, we have three rules that we need to connect to the auth records. First, we’ll create a new predicate, _auth/descId (short for descriptive id), which will help us easily identify auth records.

[{
"_id": "_predicate",
"name": "_auth/descId",
"type": "string",
"unique": true
}]

In the following transaction, we’ll group all three rules into cryptoUser role, create two new _auth records, and add those _auth records to our cryptoMan and cryptoWoman users.

In order to sign transactions as a particular auth record, that auth record’s _auth/id needs to be connected to a public/private key pair. To learn more, you can read about how to derive an auth/id from a public key. You can also generate a public key, private key, and auth id directly in the downloadable version of Fluree.

In the below example, we use a valid auth id/private key pair. If using this example in production, you should generate your own public/private/auth id triple.

[{
"_id": "_role$cryptoUser",
"id": "cryptoUser",
"doc": "Standard crypto user",
"rules": [["_rule/id", "cantEditWalletUser"], ["_rule/id", "editAnyCryptoBalance"], ["_rule/id", "editOwnWalletName"]]
},
{
"_id": "_auth$cryptoWoman",
"id": "Tf6mUADU4SDa3yFQCn6D896NSdnfgQfzTAP",
"descId": "cryptoWoman",
"doc": "cryptoWoman auth record",
"roles": ["_role$cryptoUser"]
},
{
"_id": "_auth$cryptoMan",
"id": "TfDao2xAPN1ewfoZY6BJS16NfwZ2QYJ2cF2",
"descId": "cryptoMan",
"doc": "cryptoMan auth record",
"roles": ["_role$cryptoUser"]
},
{
"_id": ["_user/username", "cryptoMan"],
"auth": ["_auth$cryptoMan"]
},
{
"_id": ["_user/username", "cryptoWoman"],
"auth": ["_auth$cryptoWoman"]
}]

Ensuring Non-Negative Balances

Once we have these rules, it would be helpful to never let our balance fall below zero. We do this by creating a smart 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 _predicate/spec for wallet/balance. Whenever issue a transaction that contains wallet/balance, the smart function nonNegative? is issued.

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

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.

Restricting Crypto Spending

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

[{
"_id": "_fn$subtractOwnAddOthers?",
"name": "subtractOwnAddOthers?",
"code": "(if-else (ownWallet?) (> (?pO) (?o)) (< (?pO) (? o))))",
"doc": "You can only add to others balances, and only subtract from your own balance"
},
{
"_id": ["_predicate/name", "wallet/balance"],
"spec": ["_fn$subtractOwnAddOthers?"],
"specDoc": "You can only add to others balances, and only subtract from your own balance. No balances may be negative"
}]

Testing Our Crypto

We are not quite done with our example yet, but we can test it to this point.

When we submit a transaction without a signature, it is signed with the default auth record. However, to add or subtract balance from a wallet, we need to sign our transactions as a particular auth record. We do this by submitting a request to the /command endpoint.

We can also use a tool in the user interface to sign transactions as a particular private key. To access this tool, we need to go to /flureeql, select "Transact", and then select "Own Private Key" from the dropdown.

The first item we will attempt is cryptoMan adding 5 to cryptoMan’s own wallet/balance. If using the user interface, you need to include the private key in the form. If you're not using the user interface, you will need to sign the following transaction with the private key. You will also need to specify cryptoMan's auth in either the form or the signed transaction.

The private key for cryptoMan is 745f3040cbfba59ba158fc4ab295d95eb4596666c4c275380491ac658cf8b60c. His _auth/id is TfDao2xAPN1ewfoZY6BJS16NfwZ2QYJ2cF2.

A request to /command will return a _tx/id. In order to see if the transaction went through successfully, you will need to query:

{
"select": ["*"],
"from": ["_tx/id", TRANSACTION ID HERE ]
}

If there is an error in the transaction, that will appear in _tx/error. If the transaction was submitted successfully, there will not be a _tx/error.

If you are using the user interface, the “Results” editor will automatically show you the results of issuing the above query after submitting a command.

[{
"_id": ["wallet/name", "cryptoWoman"],
"balance": 205
}]

In the following transaction, cryptoMan will attempt to add balance to his OWN wallet. The transaction should fail with the error message, “You can only add to others balances, and only subtract from your own balance.”

If you are using the user interface, the error message will appear in the “Results” editor. If you’re not using the user interface, remember that a request to /command will only return _tx/id, and you need to query { "select": ["*"],"from": ["_tx/id", TRANSACTION ID HERE ] } in order to see the error.

The private key for cryptoMan is 745f3040cbfba59ba158fc4ab295d95eb4596666c4c275380491ac658cf8b60c. His _auth/id is TfDao2xAPN1ewfoZY6BJS16NfwZ2QYJ2cF2.

[{
"_id": ["wallet/name", "cryptoMan"],
"balance": 205
}]

We can try similar transactions for cryptoWoman. The private key for cryptoWoman is 65a55074e1de61e08845d4dc5b997260f5f8c20b39b8070e7799bf92a006ad19. Her _auth/id is Tf6mUADU4SDa3yFQCn6D896NSdnfgQfzTAP.

The following transaction should succeed.

[{
"_id": ["wallet/name", "cryptoWoman"],
"balance": 195
}]

Whereas adding to her own wallet will fail (signed with the private key for cryptoWoman, which is 65a55074e1de61e08845d4dc5b997260f5f8c20b39b8070e7799bf92a006ad19, and specifying her _auth/id, which is Tf6mUADU4SDa3yFQCn6D896NSdnfgQfzTAP.)

Even if she doesn’t know her current balance, she can use the database function, (?pO) to get the previous value of her wallet/balance.

[{
"_id": ["wallet/name", "cryptoMan"],
"balance": "#(- (?pO) 5)"
}]

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 next section, we can submit the following transactions with the default private key, so we can return to submitting requests to the /transact endpoint (or in the user interface, selecting "Transact" and "Default Private Key" in /flureeql).

Now, we can add a spec, which makes sure that the total balance added to one (or several accounts) is equal to the amount subtracted from another account. For this purpose, we can use the txSpecpredicate. _predicate/spec, which we used to ensure that balances are non-negative, checks every flakes in a transaction that contains a given predicate. On the other hand _predicate/txSpec is run once per predicate in a transaction. For example, if we create an _predicate/txSpec for wallet/balance, our transactor will group together every flake that changes the wallet/balancepredicate and only run the txSpec once. txSpec allows use to do things like sum all the wallet/balance values in a transaction.

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

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

Final Test

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 function in place, no user can perform the following transaction, because it violates feature #4, as listed about.

We can try submitting the following transaction with cryptoWoman’s private key. The following transaction adds to cryptoMan’s wallet and subtracts from cryptoWoman’s wallet. If we hadn’t added the last function (crypto spent = crypto received), the following transaction would have been valid. Now, if signed as cryptoWoman, it will return the error, “The values of added and retracted wallet/balance flakes need to be equal”.

CryptoWoman’s private key is 65a55074e1de61e08845d4dc5b997260f5f8c20b39b8070e7799bf92a006ad19, and her _auth/id is Tf6mUADU4SDa3yFQCn6D896NSdnfgQfzTAP.

[{
"_id": ["wallet/name", "cryptoMan"],
"balance": "#(+ (?pO) 10)"
},
{
"_id": ["wallet/name", "cryptoWoman"],
"balance": "#(- (?pO) 5)"
}]

The following transaction spends as much cryptocurrency as it receives. However, because it is withdrawing from cryptoMan and adding to cryptoWoman, only cryptoMan can initiate the transaction. We can sign it as cryptoMan, and it will go through.

The private key for cryptoWoman is 65a55074e1de61e08845d4dc5b997260f5f8c20b39b8070e7799bf92a006ad19. Her _auth/id is Tf6mUADU4SDa3yFQCn6D896NSdnfgQfzTAP.

But if we sign it as cryptoWoman, it will return an error.

CryptoWoman’s private key is 745f3040cbfba59ba158fc4ab295d95eb4596666c4c275380491ac658cf8b60c, and her _auth/id is TfDao2xAPN1ewfoZY6BJS16NfwZ2QYJ2cF2

[{
"_id": ["wallet/name", "cryptoMan"],
"balance": "#(- (?pO) 10)"
},
{
"_id": ["wallet/name", "cryptoWoman"],
"balance": "#(+ (?pO) 10)"
}]

Gloat!

If all of the tests are working well, then 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

--

--