Contracts fungibility: making tokens and currencies
This article will help you to understand, how the token-style smart contracts are built in Universa, and how to select and control their “mintability” (ability to issue more amounts of the token than initially issued).
Split-Join permission structure
This is the permission that lets two (or more) some contracts to be joined together (in terms of fungible assets, like money, “to join together two heaps of coins”), keeping their total value invariant; or some contract(s) to be split into two (or more) new contracts, again, keeping the total value invariant (“splitting the heap of coins into smaller heaps”).
This permission has two most important settings for the contracts (and some less important, not important to discuss right now):
- What field of the contracts stores the “amount”, the “value of the heap”. This field is not fixed, so you can choose any field of the contract
- What is the “criteria of compatibility” for the contracts, to participate in the split-join operation.
The latter option of Split-Join permission, the “compatibility criteria”, is most interesting now. This is not even so interesting for the “split” operation (as usually there is one new contract and the other ones are just clones of it, varied only by the “owner” and the amount-storage field). Most important it is for the “join” operation. What makes a node think that two or more contracts, bunched together, are indeed allowed to “merge”?
Contract compatibility criteria
Simple non-mintable tokens
This “compatibility criteria” may be ultra-simple for non-mintable tokens, like even our UTN token.
Quoting the YAML form of our own UTN contract:
join_match_fields: - state.origin
This means that “the other contracts are allowed to merge with this one, if their
state.origin field matches”.
“Origin” field contains the “very first revision” of the contract (actually, its ID). Kind of, “the ID of the root revision”. So indeed, if every contract is derived from the same revision (where the UTN contract has been defined – by the way, it is always available to view at 🔗lnd.im/utn, even though it has been edited and split-joined numerous times after that), they are compatible. Easy.
But if you decide you want to mint some extra, and issue some new contract, it will have the different origin ID. That's a pity; the rule in the previous contract doesn't let you merge the “newly minted tokens” with the previous ones... But actually, that’s not a problem. Because it fully conforms to what you’ve explicitly requested the system to do. To make a token that is “not mintable”, and “not mergeable with any new mints”.
Planning the mintable token, you should have written the original smart contract differently, with a different “criteria of compatibility”. Let’s imagine what the criteria could be.
We cannot use the
state.origin for the criteria, as the
state.origin will be different for the “next minted batch”.
We probably can use the
definition.issuer field, containing the address of the issue (i.e. of your own private key). Good idea!
This means that anybody who has the same private key as the minter (i.e. you – I assume you don’t share your private key with anybody) can make new contracts,... and they’ll automatically become compatible with the previous mints.
And the person having a different private key cannot make the compatible tokens.
Perfect. 75% of the solution is done.
The other 25% is: what if you, for some reason (maybe, development ones) want to issue a different token? When the criteria is specified as just the
definition.issuer, you won’t be able to – any tokens having the same
definition.issuer value (i.e. issued by you) will be compatible/mergeable. But what if you want some other ones?
Still easy: the “compatibility criteria” may require not just one field to match, but multiple fields. Just add some other field as a “coin distinguisher”.
... and what could work better than adding the field containing the name of the token?
Let’s put something like the token title (be that “UTN”, or “uGold”, or whatever) to
definition.data.currency, and make a new compatibility criteria:
join_match_fields: - definition.issuer - definition.data.currency
Should work perfectly. Whenever the contracts are initially issued by the same issuer (signed by the same private key), and when the contracts are named the same – they are compatible. Voila.
And if the same issuer issues a new token but names it differently – it becomes incompatible with the previous token. Just as planned.
This is the idea how our mintable contracts are in fact created, both in the web client (using the template of the mintable contract) and through the Java API.
Though in fact, it may be an overkill for someone.
Mintable tokens: even easier
Because if you just want to be solely able to increase the supply of the contract,... why won't you just add a new permission, letting you specifically (actually, “your private key” specified by the key address) just alter the “amount” field as you wish?
Could be done, too. As long as you own at least a tiniest amount of this token, and as long as (at the moment of its original issue) you've let yourself to alter the amount directly, you could do it. Simplest minting process ever, “you (the original issuer) are just allowed to increase your money”.
Or obviously, the one who is allowed to increase the money, may be not just the issuer. But any different private key. Or even not a SimpleRole with a single address, but a ListRole with group of key owners, in a
QUORUM mode. Like, a list of 10 addresses, the signatures of at least 7 of which are required to “rewrite your balance”.
From the naive and silly “the issuer issues the token which he can always uncontrollably”, we come to something rather like “the board of directors / founders issue the token in a collective decision, and may have the vote-based collective control on further mints”.
And you suddenly see how just the tiniest contract edit moves it from simple “non-mintable token” into the CBDC area.