COTI MultiDAG — Issuing Tokens Upon COTI’s DAG Based Trustchain

COTI
COTI
Published in
13 min readMar 11, 2020

Abstract

This article details how the COTI network implements its multi-currency capabilities (MultiDAG) and demonstrates the issuance of new token (TestToken) over the network with code snippets and full command detailed overview.

Introduction

COTI is a new-generation blockchain optimized for payments and value transfer. Our goal is to efficiently and securely transfer client payments nominated in a multitude of tokens or coins. This objective led us to a solution, which is similar to Ethereum ERC-20 tokens functionality but utilizes a completely unique technology that solves the scalability, privacy, trust and regulation structure required for payment solution deployment. In COTI network Issuing a new token does not require coding smart contracts, of any kind nor at any level of network interaction.

The DAG model provides COTI with the framework for exceptional performance and efficient transaction processing. There are multitudes of token types and uses, which are worthwhile to implement atop the DAG.

COTI uses several independent Clusters that each support one token, which makes the whole network efficient and adjustable. A transaction in a Cluster can be attached to transactions in the same Cluster because different Clusters can implement various transaction confirmation rules.

COTI MultiDAG Framework

COTI Network, starting from version 2.0.0, supports Multi-Currency at the Infrastructure and consensus level. COTI tokens (issued by users on the COTI network) are transferred exactly in the same way as COTI native coins are transferred and required the same transaction confirmation process and consensus to get approved.

Using the COTI MultiDAG, transactions sent in the network can now include base transactions bundled with different tokens.

Standard COTI MultiDAG transaction bundle:

COTI MultiDAG transactions can be set as “multi-currency”, meaning a transaction bundle can contain base transactions nominated in different tokens. Since our DAG as a data structure is agnostic to the transaction payload, this means the flow of attaching transactions to the DAG and TrustChain consensus stays the same for MultiDAG.

The new MultiDAG bundle structure was defined in a way which gives it a lot of flexibility:

  • There can be more than one transfer tokens in the bundle (if needed) — so different tokens transactions can be associated with one transaction bundle.
  • The fees collected by Full Nodes in the network can be paid in any token, based on the definition of specified by the Full Node.
  • The network fee can be paid only in COTI native coin.

In addition to the above, base transactions in a transaction bundle can not violate the balance rule: the total amounts of all base transactions within a transaction should be zero for each token, or:

where i is a number of a token in the bundle.

Token Types

The DAG model provides COTI with the framework for exceptional performance and efficient transaction processing. There are multitudes of token types and uses, which are worthwhile to implement atop the DAG. COTI’s focus on efficient payment solution requires us to develop further sophisticated and exact (rather than abstract) token models.

There are four token types in the COTI network:

  1. Regular tokens are transfer-only tokens. Payment requests are not possible with these tokens and no buyer/seller protection is offered. The fee is fixed and payable in COTI native coin.
  2. Rated tokens have a definite rate, which can be fixed. Or this rate can be supplied by the Financial Server or someone else without any obligation to buy or sell tokens. The fee is payable in COTI native coin. In addition, payment requests are not possible and no buyer/seller protection is offered.
  3. Payment tokens need to be tradeable and liquid. This type of token allows payment requests and buyer/seller protection.
  4. Global Payment tokens are the same as payment tokens, but these types of token can also be accepted as a means to pay the network fee. At this phase and in the near future the only Global payment token which will be available is COTI native coin.

MultiDAG Addresses

In COTI MultiDAG, any created address can store any set of existing tokens. The address balance is created when some transaction credits token to the address.

Token Deployment Life Circle

Any new issued token deployment process in the COTI network starts with token generation command.

At this stage, the token originator uses the COTI Financial-Server service to create a new data package. This entity certifies and confirms two components:

  1. That the desired token name and symbol belong to the token originator’s public key
  2. To propagate this data to the COTI network

The total token supply is also included in the package.

After the network is notified of the new token existence, the originator can start to mint tokens. The amount of tokens that can be minted can reach the number of the total token supply. Minted tokens can be credited to any existing address by the minting transaction itself. It is also possible to partially mint the token and only mint as many as token as needed.

Once the minting operation is executed, the tokens are minted to a wallet address, thus they can be used for transfers or payments for goods and services. The COTI payment solution allows merchants to define which set of tokens are good for the payment and payers can choose from the list.

Token Deployment Demo

Initial Phase

For the demo purposes we have defined two users, Alice and Bob, having addresses Alice.Address1, Alice.Address2, Bob.Address1, Bob.Address2.

Alice has some COTI coins at address Alice.Address1.

All Python scripts below use Alice’s seed from the configuration file, not revealing it in scripts.

Alice =

75aa862f0bded141247fe5816a7ce293aa871be73e728f7de23349e9684941125ca18f60c873e3f3893f4bf7edaf898e219ffb906fbc9c45ab113187622be5f8

[“7f988725bc77fdf9192c1254863ad54e7f7d9378641cc1d5e3c294907fedb719e4be13d4aafeeea0b08e10691ebe5bedbaa32ec9f6689beec65fe7d4453a5972fcf78615”,

“3525d7121ac56e631011f6d1912e8c09ae5800668ea2715c69136e39e10ad79727ae485a87891c4d45be8a0423bea75761e5225833180acc7b0802e335487119d1f329de”]

Bob =

64c17a8af92593d815791a9ca6965d39ba6400fcd90a5722ccd57f4c1f2af84742c6caa3e8f0279ef328c3185653371c9dd3d63cdec9b2d9143a4fba98f1111e

[“efe2978caa0ede29d010a6ebf3f0fddf19ed21369a070aee34e8a5d0c7ab408eae0de7e4d29021db0a6aa3e6e25469c8bb469b0a767dc22d4c722f1b93c8ce3ed7389035”,

“6a255ad99789ac8e73fe4a4b7b2355a93457397981d9a492740f840c03f0b1293e360e0e1dbe6b4da9095676244b737cfa224b7f03f42e0e3f8b5d2aac5229ddf6870a06”]

At first phase we request to retrieve the balance of the two users

Request in Postman — get balance for [Alice.Address1] => there are some COTI at the address.

Request in Postman — get token balance for [Alice.Address1, Alice.Address2, Bob.Address1, Bob.Address2] => result []

Token Generation

The next step is to generate the new TestToken. In COTI MultiDAG, this functionality is a staged process designed to be fair and transparent.

All stages are implemented with a standard COTI wallet and using the python script provided in this article:

  1. Request for token generation fee — There is a fee for the token generation. It is a small amount of native COTI coins. The new token originator sends a request to the Financial server, including the new: requested token name, symbol, and total supply. The Financial server checks that the name and symbol are available, calculates the fee and returns the prepared Token Generation Fee-Base-Transaction Data to the requestor. The data is valid for 1 hour.
  2. Fee payment — The user wallet sends the transaction to pay the generation fee and keeps the hash.
  3. Token generation — The new token originator sends a request to the Financial server, detailing the new token. It includes token name, symbol, token description, total supply, scale, the originator’s public key, and the hash of the fee payment transaction. If the request is valid and the transaction is confirmed, the Financial server creates the new token and notifies the network.

Example:

Set the node and user parameters in the parameters.json file:

Set new token data = {“Name”: “TestToken”, “Symbol”:”CTT”, “TotalSupply”:1000000000} to token.json

Once define execute the following script:

$ python demo_get_TGBT.py token.json TGBT.json

After the execution of this script, we have the TGBT data set in TGBT.json file.

Then we need to use the following command:

$ python demo_pay_TFBT.py TGBT.json

This script prints the fee payment transaction hash.

Then we need to add the transaction hash to our token.json file and use the next command:

$ python demo_generate_token.py token.json

The script returns the hash of the new generated token.

After the synchronization process finishes, we can view the newly generated tokens at the Genezis address:

Request in Postman — get token balance for [Genezis] => returns that there are 1000000000 CTT

In the future, this work will be performed in a decentralized way by the consensus of COTI DSP nodes.

Token Minting

The next step in the process is to mint our tokens. In COTI MultiDAG, token minting is already implemented in a decentralized way. The Financial server only prepares data on request and performs the minting transaction after the consensus for the request is reached.

Token minting also requires fee payment. It is a reasonably low fee and is proportional to the minted amount. Thus, if the originator creates a token with a total supply of one billion but mints only 1000 tokens, the issuer will only pay the for token which were actually minted. For more efficient user experience, COTI uses 2-step fee requests: only retrieving the quote at first stage and then the allocation of the required token mint cap.

The stages for minting a token are:

  1. Get fee quote — at this stage, the wallet requests a fee for the required amount of tokens and receives the quote. Quotes are not stored or counted by any server. Automated wallets (exchange app, for example) can skip this stage.
  2. Fee request — at this stage the wallet requests for the prepared token minting Fee-Base-Transaction Data. The data is valid for 1 hour. It includes all data needed for actual token minting.
  3. Minting — After a wallet is created and sent a transaction containing valid Token Minting Fee Base Transaction, the minting process begins. The COTI network validates the transaction. If the transaction is correct, then fee payment is possible and the required minting amount is available. At the last stage, the transaction receives confirmation from the COTI DSP consensus. This is the trigger for token minting and creation of Initial type transaction from the Genesis address to the receiver address supplied.

Example:

Set the token transfer data = {“Sender”:Alice.Address1, “Receiver”:Bob.Address2, “Token”: hash-from-the-previous-stage, “Amount”:1000} to transfer.json

Once set use the following script:

$ python demo_token_transfer.py transfer.json

The script returns the transaction hash.

Request in Postman — get token balance for [Genezis, Alice.Address1, Alice.Address2, Bob.Address1, Bob.Address2] => token balances after all transfers.

Request in Postman — get balance for [Alice.Address1] => the rest of Alice’s COTI after all operations.

Transferring a Token

Set the token transfer data = {“Sender”:Alice.Address1, “Receiver”:Bob.Address2, “Token”: hash-from-the-previous-stage, “Amount”:1000} in transfer.json

After that, execute the following Python script:

$ python demo_token_transfer.py transfer.json

The script returns the transaction hash.

Request in Postman — get token balance for [Genezis, Alice.Address1, Alice.Address2, Bob.Address1, Bob.Address2] => token balances after all transfers.

Request in Postman — get balance for [Alice.Address1] => the rest of Alice’s COTI after all operations.

Payments and Transfers in Non-Native Tokens

The standard COTI wallet allows the user to select a token to transfer and to check balances in different tokens. Regarding payments for goods and services, the token is defined during check-out and it is part of the payment request sent to the network through the merchant app. The wallet has to create and sign the transaction according to the request:

Python Scripts Code Snippets

demo_get_TGBT.py

import osif "__file__" in globals():os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))else:os.sys.path.append(os.path.dirname(os.path.abspath('.')))import sysimport jsonimport requestssettings_file = "settings.json"token_file = sys.argv[1]with open(settings_file) as f:settings = json.load(f)with open(token_file) as f:token = json.load(f)tokenFeeRequest = {}tokenFeeRequest["currencyData"] = {}tokenFeeRequest["currencyData"]["name"] = token["Name"]tokenFeeRequest["currencyData"]["symbol"] = token["Symbol"]tokenFeeRequest["currencyData"]["totalSupply"] = token["TotalSupply"]tokenFeeRequest["currencyData"]["scale"] = token["Scale"]url = settings["FinancialServer"] + "/currencies/token/generate/fee"headers = {'Content-Type': "application/json"}res = requests.post(url, headers = headers, json = tokenFeeRequest)if res.status_code != 201:print("token fee error")print(res)print (res.json())exit(0)bt_file = sys.argv[2]with open(bt_file, "w") as f:json.dump(res.json()["tokenServiceFee"], f)

demo_pay_TFBT.py

import osif "__file__" in globals():os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))else:os.sys.path.append(os.path.dirname(os.path.abspath('.')))import sysimport jsonfrom coticrypto import PrivateKeyFromSeed, KeyForIndexFromSeedfrom cotilibrary import transactionssettings_file = "settings.json"users_file = "users.json"seeds_file = "local.sec"bt_file = sys.argv[1]with open(settings_file) as f:settings = json.load(f)with open(users_file) as f:users = json.load(f)with open(seeds_file) as f:seeds = json.load(f)with open(bt_file) as f:TFBT = json.load(f)sender = {}sender["user"] = users["Alice"]["pk"]sender["address"] = users["Alice"]["addresses"][0]for user in seeds["users"]:if user["user"] == sender["user"]:sender["seed"] = user["seed"]breakelse:print("Error: sender seed not found")exit(0)sender["key"] = PrivateKeyFromSeed(bytearray.fromhex(sender["seed"]))sender["keyForAddress"],_ = KeyForIndexFromSeed(bytearray.fromhex(sender["seed"]), sender["address"][:128])parameters = {}if TFBT["name"] == "TGBT":parameters["type"] = "TokenGenerationFee"elif TFBT["name"] == "TMBT":parameters["type"] = "TokenMintingFee"else:exit(0)parameters["TxDescription"] = "test"parameters["fullnode_fee_url"] = settings["FullNode"] + "/fee"parameters["tx_TS_url"] = settings["TSNode"] + "/transactiontrustscore"parameters["tx_create_url"] = settings["FullNode"] + "/transaction"TxHash = transactions.CreateTransactionTFBT(TFBT, sender, parameters)print(TxHash)

demo_generate_token.py

import osif "__file__" in globals():os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))else:os.sys.path.append(os.path.dirname(os.path.abspath('.')))import sysimport jsonimport requestsfrom coticrypto import PrivateKeyFromSeed, HashKeccak256, HashKeccak224, SignDigestsettings_file = "settings.json"users_file = "users.json"seeds_file = "local.sec"token_file = sys.argv[1]with open(settings_file) as f:settings = json.load(f)with open(token_file) as f:token = json.load(f)with open(users_file) as f:users = json.load(f)with open(seeds_file) as f:seeds = json.load(f)for user in seeds["users"]:if user["user"] == users["Alice"]["pk"]:Alice_seed = user["seed"]breakelse:print("Error: Alice seed not found")exit(0)originatorCurrencyData = {}originatorCurrencyData["name"] = token["Name"]originatorCurrencyData["symbol"] = token["Symbol"]originatorCurrencyData["description"] = "Test token " + token["Name"]originatorCurrencyData["totalSupply"] = token["TotalSupply"]originatorCurrencyData["scale"] = token["Scale"]originatorCurrencyData["originatorHash"] = users["Alice"]["pk"]msg1 = originatorCurrencyData["name"].encode() \+ originatorCurrencyData["symbol"].encode() \+ originatorCurrencyData["description"].encode() \+ str(originatorCurrencyData["totalSupply"]).encode() \+ originatorCurrencyData["scale"].to_bytes(4,byteorder='big')h1 = HashKeccak256(msg1)keyForUser = PrivateKeyFromSeed(bytearray.fromhex(Alice_seed))s1 = SignDigest(bytearray.fromhex(keyForUser), bytearray.fromhex(h1))originatorCurrencyData["originatorSignature"] = {}originatorCurrencyData["originatorSignature"]["r"] = s1[0]originatorCurrencyData["originatorSignature"]["s"] = s1[1]token_request = {}token_request["transactionHash"] = token["TxTGBT"]token_request["originatorCurrencyData"] = originatorCurrencyDatamsg2 = bytearray.fromhex(token["TxTGBT"]) + bytearray.fromhex(HashKeccak224(originatorCurrencyData["symbol"].encode()))h2 = HashKeccak256(msg2)s2 = SignDigest(bytearray.fromhex(keyForUser), bytearray.fromhex(h2))token_request["signature"] = {}token_request["signature"]["r"] = s2[0]token_request["signature"]["s"] = s2[1]url = settings["FinancialServer"] + "/currencies/token/generate"headers = {'Content-Type': "application/json"}res = requests.put(url, headers=headers, json=token_request)if res.status_code != 201:print("token generation error")print(res)print (res.json())exit(0)if res.json()["status"] != "Success":print("token generation error")print (res.json())exit(0)print(res.json()["tokenHash"])

demo_get_fee_quote.py

import osif "__file__" in globals():os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))else:os.sys.path.append(os.path.dirname(os.path.abspath('.')))import sysimport jsonimport requestsimport timefrom coticrypto import PrivateKeyFromSeed, HashKeccak256, SignDigestsettings_file = "settings.json"users_file = "users.json"seeds_file = "local.sec"mint_file = sys.argv[1]with open(settings_file) as f:settings = json.load(f)with open(mint_file) as f:minting = json.load(f)with open(users_file) as f:users = json.load(f)with open(seeds_file) as f:seeds = json.load(f)for user in seeds["users"]:if user["user"] == users["Alice"]["pk"]:Alice_seed = user["seed"]breakelse:print("Error: Alice seed not found")exit(0)key = PrivateKeyFromSeed(bytearray.fromhex(Alice_seed))feeQuoteRequest = {}feeQuoteRequest["currencyHash"] = minting["Hash"]feeQuoteRequest["mintingAmount"] = minting["Amount"]feeQuoteRequest["createTime"] = round(time.time(), 3)creationTime = int(feeQuoteRequest["createTime"] * 1000)feeQuoteRequest["userHash"] = users["Alice"]["pk"]msgT = bytearray.fromhex(feeQuoteRequest["currencyHash"]) \+ str(feeQuoteRequest["mintingAmount"]).encode() \+ creationTime.to_bytes(8,byteorder='big')hT = HashKeccak256(msgT)sT = SignDigest(bytearray.fromhex(key), bytearray.fromhex(hT))feeQuoteRequest["signature"] = {}feeQuoteRequest["signature"]["r"] = sT[0]feeQuoteRequest["signature"]["s"] = sT[1]url = settings["FinancialServer"] + "/currencies/token/mint/quote"headers = {'Content-Type': "application/json"}res = requests.post(url, headers = headers, json = feeQuoteRequest)if res.status_code != 201:print("token Quote error")print(res)print (res.json())exit(0)if res.json()["status"] != 'Success':print("token Quote error")print (res.json())exit(0)quote_file = sys.argv[2]with open(quote_file, "w") as f:json.dump(res.json()["mintingFeeQuote"], f)

demo_get_TMBT.py

import osif "__file__" in globals():os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))else:os.sys.path.append(os.path.dirname(os.path.abspath('.')))import sysimport jsonimport requestsimport timefrom coticrypto import PrivateKeyFromSeed, HashKeccak256, SignDigestsettings_file = "settings.json"users_file = "users.json"seeds_file = "local.sec"mint_file = sys.argv[1]quote_file = sys.argv[2]with open(settings_file) as f:settings = json.load(f)with open(mint_file) as f:minting = json.load(f)with open(quote_file) as f:feeQuote = json.load(f)with open(users_file) as f:users = json.load(f)with open(seeds_file) as f:seeds = json.load(f)for user in seeds["users"]:if user["user"] == users["Alice"]["pk"]:Alice_seed = user["seed"]breakelse:print("Error: Alice seed not found")exit(0)key = PrivateKeyFromSeed(bytearray.fromhex(Alice_seed))tokenFeeRequest = {}tokenFeeRequest["tokenMintingData"] = {}tokenFeeRequest["tokenMintingData"]["mintingCurrencyHash"] = minting["Hash"]tokenFeeRequest["tokenMintingData"]["mintingAmount"] = minting["Amount"]tokenFeeRequest["tokenMintingData"]["receiverAddress"] = minting["Receiver"]tokenFeeRequest["tokenMintingData"]["createTime"] = round(time.time(), 3)tokenFeeRequest["tokenMintingData"]["signerHash"] = users["Alice"]["pk"]if len(sys.argv) >= 4:tokenFeeRequest["mintingFeeQuoteData"] = feeQuotetokenFeeRequest["tokenMintingData"]["feeAmount"] = feeQuote["mintingFee"]Else:feeQuote = NonecreationTime = int(tokenFeeRequest["tokenMintingData"]["createTime"] * 1000)msgT = bytearray.fromhex(tokenFeeRequest["tokenMintingData"]["mintingCurrencyHash"]) \+ str(tokenFeeRequest["tokenMintingData"]["mintingAmount"]).encode() \+ str(tokenFeeRequest["tokenMintingData"]["feeAmount"] if feeQuote else '' ).encode() \+ bytearray.fromhex(tokenFeeRequest["tokenMintingData"]["receiverAddress"]) \+ creationTime.to_bytes(8,byteorder='big')hT = HashKeccak256(msgT)sT = SignDigest(bytearray.fromhex(key), bytearray.fromhex(hT))tokenFeeRequest["tokenMintingData"]["signature"] = {}tokenFeeRequest["tokenMintingData"]["signature"]["r"] = sT[0]tokenFeeRequest["tokenMintingData"]["signature"]["s"] = sT[1]url = settings["FinancialServer"] + "/currencies/token/mint/fee"headers = {'Content-Type': "application/json"}res = requests.post(url, headers = headers, json = tokenFeeRequest)if res.status_code != 201:print("token fee error")print(res)print (res.json())exit(0)if res.json()["status"] != 'Success':print("token fee error")print (res.json())exit(0)if len(sys.argv) >= 4:bt_file = sys.argv[3]else:bt_file = sys.argv[2]with open(bt_file, "w") as f:json.dump(res.json()["tokenServiceFee"], f)

demo_token_transfer.py

import osif "__file__" in globals():os.sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))else:os.sys.path.append(os.path.dirname(os.path.abspath('.')))import sysimport jsonfrom coticrypto import PrivateKeyFromSeed, KeyForIndexFromSeedfrom cotilibrary import transactionssettings_file = "settings.json"users_file = "users.json"seeds_file = "local.sec"transfer_file = sys.argv[1]with open(settings_file) as f:settings = json.load(f)with open(users_file) as f:users = json.load(f)with open(seeds_file) as f:seeds = json.load(f)with open(transfer_file) as f:transfer = json.load(f)sender = {}sender["user"] = users["Alice"]["pk"]sender["address"] = transfer["Address"]sender["feeAddress"] = transfer["FeeAddress"]for user in seeds["users"]:if user["user"] == sender["user"]:sender["seed"] = user["seed"]breakelse:print("Error: sender seed not found")exit(0)sender["key"] = PrivateKeyFromSeed(bytearray.fromhex(sender["seed"]))sender["keyForAddress"],_ = KeyForIndexFromSeed(bytearray.fromhex(sender["seed"]), sender["address"][:128])sender["keyForFeeAddress"],_ = KeyForIndexFromSeed(bytearray.fromhex(sender["seed"]), sender["feeAddress"][:128])receiver = {}receiver["address"] = transfer["Receiver"]parameters = {}parameters["amount"] = transfer["Amount"]parameters["currency"] = transfer["Currency"]parameters["nativeCurrency"] = settings["nativeCurrencyHash"]parameters["TxDescription"] = transfer["Description"]parameters["fullnode_fee_url"] = settings["FullNode"] + "/fee"parameters["tx_nodes_list"] = [{"url": settings["TSNode"] + "/networkFee"}]parameters["tx_TS_url"] = settings["TSNode"] + "/transactiontrustscore"parameters["tx_create_url"] = settings["FullNode"] + "/transaction"TxHash = transactions.SendTxMultiCurrency(sender, receiver, parameters)print(TxHash)

For all of our updates and to join the conversation, be sure to check out our channels:

Website: https://coti.io

Telegram: https://t.me/COTInetwork

Twitter: https://twitter.com/COTInetwork

Github: https://github.com/coti-io

Discord: https://discord.me/coti

Technical whitepaper: https://coti.io/files/COTI-technical-whitepaper.pdf

--

--

COTI
COTI
Editor for

COTI is the fastest and lightest confidentiality layer on Ethereum.