-
Notifications
You must be signed in to change notification settings - Fork 45
Instructions of Listing Token for Exchanges
The purpose of this document is to describe how to list VSYS smart contract (Euclid) based Token for Exchanges, including deposit and withdraw token.
All of contract functions in Euclid consists of sealed operation code. Currnetly, there is two kinds of token contract is available on blockchain. The contract functions are as the following:
Function Index | Token | Token with Split |
---|---|---|
0 | Supersede | Supersede |
1 | Issue | Issue |
2 | Destroy | Destroy |
3 | Send | Split |
4 | Transfer | Send |
5 | Deposit | Transfer |
6 | Withdraw | Deposit |
7 | TotalSupply | Withdraw |
8 | MaxSupply | TotalSupply |
9 | BalanceOf | MaxSupply |
10 | GetIssuer | BalanceOf |
11 | GetIssuer |
We can see the different between these two token is one contains Split function while another not. The token contract with split function likes stock in financal market. We can split/revert-split token by this function.
Feature 1: Split is an operation which impacts on blockchain. After split/revert-split token, the token Unity will be changed. The amount of token that user held on blockchain will also be changed accordingly (calculated in regular unit).
For example, one token, regular unit named DDD, mininum unit named SD, unity is 1000 SD/DDD. So 1 DDD = 1000 SD. The mininum amount will be 0.001 DDD. Bob held 1.0 DDD. After token issuer set new Unity to 200, 1 token split into 5. When Bob check token balance in wallet, he will found he held 5.0 DDD.
Feature 2: Whatever Unity is, the amount calculated in mininum unit will not be changed.
For example, one token, regular unit named DDD, mininum unit named SD, unity is 1000 SD/DDD. Bob held 1.0 DDD. Calculated in mininum unit, it is 1000 SD (1.0 DDD * 1000 SD/DDD = 1000 SD). After token issuer set new Unity to 200, 1 token split into 5. When Bob check token balance in wallet, he will found he held 5.0 DDD. Calculated in mininum unit, it is still 1000 SD (5.0 DDD * 200 SD/DDD = 1000 SD).
Split is an operation which impacts on blockchain. So how centralized exchange handles split token deposit and withdraw? Let us introduce the Exchange Unity Split/Revert-split Protocol.
-
First, exchange should check and store the token unity on blockchain via API/JSON-RPC. All Token deposit and withdraw amount should calculate in mininum unit, and then broadcast the transaction on blockchain.
-
In regular Split workflow, Token Issuer should notice exchange that they will split token at some time. The exchange will stop all business (trading, deposit, withdraw and cancel all orders) of this token before split. Then Issuer does split operation and exchange updates the unity. Finally, exchange resumes all business of this token.
-
In some abnormal case, exchange somehow did not get notice from token issue and business did not stop while token splitting. For exchange use self-store unity to calculate the amount of deposit and withdraw in mininum unit, the total value of Token sent on chain is the same. Only they will be different amount (in regular unit display) between exchange and his/her wallet. (For example, token issuer set new Unity to 200 from 1000. When user deposit 100 Token to exchange, he/she will see just 20 Token displayed at exchange; If he/she withdraw 10 Token from exchange to his/her own wallet, he/she will see 50 Token transfered from exchange. Though the regular unit amout is different, the total value of deposit and withdraw is the same. The abnormal display will keep showing until exchange stop business and update new unity. Once done, user will see consistent balance and transaction amount in exchange and his/her own wallet.)
Please refer Full Node Setup Instructions
We strongly suggest you use SDK to do integration. Currently, we provide the following version of SDK:
Python version SDK pyvsystems. The pyvsystems specification is here. Sample code refers here.
JavaScript version SDK js-v-sdk
Java version SDK java-v-sdk
Golang version SDK go-v-sdk
C# version SDK (Work in progress)
You could open http://<full node ip>:9922
in browser to find all APIs you can use.
Install curl for post http request if you do not have curl.
$ sudo apt-get install curl
To test connection, you could use
$ curl -X GET 'http://<full node ip>:9922/blocks/height'
If success, you will get response more or less like the following:
{
"height": 2400326
}
Please refer Create Wallet and Check Address in Node
We can check Token info via HTTP GET /contract/tokenInfo/{tokenId}
$ curl -X GET 'http://<full node ip>:9922/contract/tokenInfo/TWsVx4qkjs2iPTw8zrhcKq1FDe1ygzhDEwiFY4ap7'
If success, you will get response more or less like the following:
{
"tokenId": "TWsVx4qkjs2iPTw8zrhcKq1FDe1ygzhDEwiFY4ap7",
"contractId": "CEsHcSQdNbjHv9q8sPaEGKyDCJ243s6JYoQ",
"max": 2100000000000000,
"total": 11000000000000,
"unity": 100000000,
"description": "1TXtSrHmSLPYFxXWRkEs4BQscUFb6yaPfP7RTpR31Rk"
}
In response, max
and total
are returned as minimum unity amout. max
means the max supply of amount is 21000000.0 Token. total
means the
circulating supply is 110000.0 Token. unity
= 100000000 means the minimum amount of this Token is 0.00000001. description
is a string which encoded by Base58.
We can check Contract info via HTTP GET /contract/info/{contractId}
$ curl -X GET 'http://<full node ip>:9922/contract/info/CEsHcSQdNbjHv9q8sPaEGKyDCJ243s6JYoQ'
If success, you will get response more or less like the following:
{
"contractId": "CEsHcSQdNbjHv9q8sPaEGKyDCJ243s6JYoQ",
"transactionId": "HcXbHTkde1qg7H313Wgzg3j7GC2Dk74mm6JwPvSykrvD",
"type": "TokenContract",
"info": [
{
"data": "AUCvVbLeLU5LiLxyYgEYcnccUDxJuk3JMeA",
"type": "Address",
"name": "issuer"
},
{
"data": "AUCvVbLeLU5LiLxyYgEYcnccUDxJuk3JMeA",
"type": "Address",
"name": "maker"
}
],
"height": 6616970
}
In response, transactionId
is the transaction ID that contract created. type
is contract type description. If it is token contract which contains "Split" function, the value of this field will be "TokenContractWithSplit". If it is token contract which does not contain "Split" function, the value of this field will be "TokenContract". Token info
contains 2 wallet address: one is Issuer, which manages issue, destroy, split action; another is Maker, which is creator of this contract. Maker can set Issuer by Supersede function. height
shows the contract is created in which block height.
We can check Token balance via HTTP GET /contract/balance/{address}/{tokenId}
$ curl -X GET 'http://<full node ip>:9922/contract/balance/AU8R9ri7eG968zuJuLQVLMiUzRNXvQwNPwE/TWsVx4qkjs2iPTw8zrhcKq1FDe1ygzhDEwiFY4ap7'
If success, you will get response more or less like the following:
{
"address": "AU8R9ri7eG968zuJuLQVLMiUzRNXvQwNPwE",
"tokenId": "TWsVx4qkjs2iPTw8zrhcKq1FDe1ygzhDEwiFY4ap7",
"balance": 1234400000,
"unity": 100000000
}
In response, balance
is show as the mininum unity amount. For example, 1234400000 means the user holds 1234.4 Token.
(Sending Token is a complex progress. We suggest you use SDK to send token)
If user withdraws Token to his/her own wallet, the exchange can call HTTP POST /contract/execute to send Token.
$ curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'api_key: <api_key>' -d '{ \
"sender": "ATtRykARbyJS1RwNsA6Rn1Um3S7FuVSovHK", \
"contractId": "CF8eRsiyXSP9qnrby9AqgzJGJbjYx9aXkbi", \
"functionIndex": 4, \
"functionData": "14uNyNb5nFsicZ6dELMKjNAHz3PLM3mjiu6pHsbTrgTYYWb5WRy", \
"attachment": "", \
"fee": 30000000, \
"feeScale": 100 \
}' 'http://<full node ip>:9922/vsys/payment'
In POST JSON response,
contractId
is contract ID which we get in step 3.
functionIndex
is the index of executed function. For token contract with split function, the index here is 4; for token contract that no split function, the index here is 3.
fee
is transaction fee. Currently it is constant value 30000000 (0.3 VSYS).
feeScale
Currently it is constant value 100.
sender
is wallet address of sender.
attachment
could be any text (max 140 characters) with base58 encoding.
functionData
is packaged function parameters with base58 encoding.
The instruction of encoding functionData:
The contract function of "Send Token" contains two parameters. The first parameter is recipient address. The second one is amount of sending token.
function send(Account recipient, Amount amount)
And Type ID of parameter (we called DataEntry) is as the following:
PublicKey = 1
Address = 2
Amount = 3
Int32 = 4
ShortText = 5
ContractAccount = 6
Except ShortText type, one DataEntry encodes with Type ID + Byte Value.
For example, if we want to send 1.5 Token to AU1L8NVL9NTvu4n4XbP3fPJ8b5gQGjRG17W. The token unity is 1000. First, we decode the address with Base58 and get 26 bytes:
(Base58 decode AU1L8NVL9NTvu4n4XbP3fPJ8b5gQGjRG17W)
05 54 66 33 43 4d 30 9c 92 cf 07 47 da 4b 40 53 15 22 60 c1 e8 4f 62 05 dd d1
And then we calculate the amount in mininum unit and convert to 8 bytes:
(1.5 * 1000 = 1500, convert 1500 to 8 bytes)
00 00 00 00 00 00 05 dc
So we can get these bytes:
00 02 (The num of parameters)
02 (The type ID of the first parameter)
05 54 66 33 43 4d 30 9c 92 cf 07 47 da 4b 40 53 15 22 60 c1 e8 4f 62 05 dd d1 (The value of the first parameter)
03 (The type ID of the second parameter)
00 00 00 00 00 00 05 dc (The value of the second parameter)
Then we concat these bytes
00 02 02 05 54 66 33 43 4d 30 9c 92 cf 07 47 da 4b 40 53 15 22 60 c1 e8 4f 62 05 dd d1 03 00 00 00 00 00 00 05 dc
Finally, encode with Base58, we get
14uNyNb5nFsicZ6dELMKjNAHz3PLM3mjiu6pHsbTrgTYYWb5WRy
If success, you will get response more or less like the following:
{
"type": 9,
"id": "FZcxfa6oD9VumAseRnDx7bUWeMmbs8c4R9TreEhRwNh3",
"fee": 30000000,
"timestamp": 1568346104946082600,
"proofs": [
{
"proofType": "Curve25519",
"publicKey": "DREQ2s3dQzaKTsvnmKWTnj59ptM1NzXwmap8jYVdPeLC",
"address": "AUCJE7djDBbFrgeKRZ4UMREujAeqnD6L6Ph",
"signature": "3NPGPyxWW8PDh1Hc3hubZcRPk3KJBkKsjidTVaFR32Jv1obLfBAxEY5Vq27ZNzfPqcvvi64QtaKcZTXZMxbjXGqx"
}
],
"contractId": "CF8eRsiyXSP9qnrby9AqgzJGJbjYx9aXkbi",
"functionIndex": 4,
"functionData": "14uNyNb5nFsicZ6dELMKjNAHz3PLM3mjiu6pHsbTrgTYYWb5WRy",
"attachment": "",
"feeScale": 100
}
To simplify contract parse on chain, we also provide token transaction query node for exchange to check the token transaction history. Exchange can parse contract by itself as well. For parse instructions, please refer here
Use HTTP GET /token/transactions?tokenId={tokenId}&address={address}&limit={limit}&start={startIndex} API. start
is optional. In reponse for each query, you will get LastEvaluatedKey, which you can use it as start input value for next paging query. For example,
$ curl -X GET 'https://token-explorer-dev.vcloud.systems/api/v1/token/transactions?tokenId= TWZ11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxhx1wSY&address=ARMMfCxxxxxxxxxxxxxxxxxxxxxxxiAZFSS&limit=10
If success, you will get response more or less like the following:
{
"address": "address_in_query_parameter_or_null",
"limit": "limit_in_query_parameter_or_default_10",
"result": {
"Count": 1,
"Items": [
{
"confirmations": 6700,
"content": {
"data": {
"amount": 500000000000000,
"amountStr": "500000000000000",
"recipient": "ARBGvExxxxxxxxxxxxxxxxxxxxxxaMD6yyP"
},
"functionIndex": 3
},
"contractId": "CC3KZzxxxxxxxxxxxxxxxxxxxxxxxpEPfnM",
"createdAt": 0,
"functionIndex": 3,
"functionIndexType": "send",
"height": 6442672,
"signer": "ARMMfCxxxxxxxxxxxxxxxxxxxxxxxiAZFSS",
"status": "Success",
"timestamp": 1560000000000,
"timestampStr": "1560000000000000",
"tokenId": "TWZ11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxhx1wSY",
"transactionFee": 0.3,
"transactionId": "8UJzh7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWbp4YB",
"updatedAt": 0,
"verifications": 1
}
],
"LastEvaluatedKey": 1569395070031
},
"tokenId": "tokenId_in_query_parameter"
}
Use HTTP GET /token/transactions?tokenId={tokenId}&startHeight={from}&endHeight={to}&limit={limit}&start={startIndex} API
$ curl -X GET 'https://token-explorer-dev.vcloud.systems/api/v1/token/transactions?tokenId= TWZ11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxhx1wSY&limit=10&startHeight=6100000&endHeight=6100100'
If success, you will get response more or less like the following:
{
"address": "address_in_query_parameter_or_null",
"limit": "limit_in_query_parameter_or_default_10",
"result": {
"Count": 1,
"Items": [
{
"confirmations": 6700,
"content": {
"data": {
"amount": 500000000000000,
"amountStr": "500000000000000",
"recipient": "ARBGvExxxxxxxxxxxxxxxxxxxxxxaMD6yyP"
},
"functionIndex": 3
},
"contractId": "CC3KZzxxxxxxxxxxxxxxxxxxxxxxxpEPfnM",
"createdAt": 0,
"functionIndex": 3,
"functionIndexType": "send",
"height": 6442672,
"signer": "ARMMfCxxxxxxxxxxxxxxxxxxxxxxxiAZFSS",
"status": "Success",
"timestamp": 1560000000000,
"timestampStr": "1560000000000000",
"tokenId": "TWZ11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxhx1wSY",
"transactionFee": 0.3,
"transactionId": "8UJzh7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWbp4YB",
"updatedAt": 0,
"verifications": 1
}
],
"LastEvaluatedKey": 1569395070031
},
"tokenId": "tokenId_in_query_parameter"
}
Use HTTP GET /transactions/info/{id} API
$ curl -X GET 'https://token-explorer-dev.vcloud.systems/api/v1/token/transaction/HNvRwxxxxxxxxxxxxxxxxxxxxxxxxxxxxxVU9N4o'
If success, you will get response more or less like the following:
{
"result": {
"confirmations": 6800,
"content": {
"data": {
"amount": 500000000000000,
"amountStr": "500000000000000",
"recipient": "ARMMfCxxxxxxxxxxxxxxxxxxxxxxxxiAZFAA"
},
"functionIndex": 3
},
"contractId": "CC3KZzxxxxxxxxxxxxxxxxxxxxxxxxEPfasd",
"createdAt": 0,
"functionIndex": 3,
"functionIndexType": "send",
"height": 6440000,
"signer": "ARBGvExxxxxxxxxxxxxxxxxxxxxxxxMD1yyP",
"status": "Success",
"timestamp": 1569390000000,
"timestampStr": "1569390000000000000",
"tokenId": "TWZ11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxhx3wSY",
"transactionFee": 0.3,
"transactionId": "HNvRwxxxxxxxxxxxxxxxxxxxxxxxxxxxxxVU9N4o",
"updatedAt": 0,
"verifications": 1
}
}
Use HTTP GET /blocks/height API
$ curl -X GET 'http://<full node ip>:9922/blocks/height'
Response:
{
"height": 6145911
}
If a transaction if packaged into a block, the API GET /transactions/info/{id}
will return the height of transaction. We can confirm a transaction if node block height is 31 blocks higher than the height of transaction.
Some common id of transaction types:
2 = Payment transaction
3 = Lease transaction
4 = Cancel lease transaction
5 = Minting transaction
8 = Register contract transaction
9 = Execute contract function transaction
(Sending Token is a complex progress. We suggest you use SDK to send token)
To make sure asset is safty, the exchange collects Token from hot wallet and stores these Token to cold wallet. When user widthdraw Token, the exchange transfer out to user from cold wallet.
And you can write your own program to generate Token payment signature as well. For example, if you want to send Token which JSON format like this:
{
"fee": 30000000,
"feeScale": 100,
"timestamp": 1547722056762119200,
"contractId": "CF8eRsiyXSP9qnrby9AqgzJGJbjYx9aXkbi",
"functionIndex": 4,
"functionData": "14uNyNb5nFsicZ6dELMKjNAHz3PLM3mjiu6pHsbTrgTYYWb5WRy",
"senderPublicKey": "B2Khd89jtnpuzGdnyGRcnKycZMBCo6PsotFcWWi1wMDV",
"attachment": "HXRC"
}
Convert these fields into bytes:
type_id: 09
contract_id: 06 54 b0 b1 ac b4 bd 2c db 7d 04 9f 96 e0 0b a7 a6 9e cb da 81 b9 72 6b b0 e9
function_index: 00 04
function_data: 00 02 02 05 54 66 33 43 4d 30 9c 92 cf 07 47 da 4b 40 53 15 22 60 c1 e8 4f 62 05 dd d1 03 00 00 00 00 00 00 05 dc
length of attachment: 00 03
attachment: 31 32 33
tx_fee: 00 00 00 00 01 c9 c3 80
fee_scale: 00 64
timestamp: 15 7a 9d 02 ac 57 d4 20
And then combine togather:
09 06 54 b0 b1 ac b4 bd 2c db 7d 04 9f 96 e0 0b a7 a6 9e cb da 81 b9 72 6b b0 e9 00 04 00 02 02 05 54 66 33 43 4d 30 9c 92 cf 07 47 da 4b 40 53 15 22 60 c1 e8 4f 62 05 dd d1 03 00 00 00 00 00 00 05 dc 00 03 31 32 33 00 00 00 00 01 c9 c3 80 00 64 15 7a 9d 02 ac 57 d4 20
Finally, we used ed25519 of curve25519 library to signature.
(For reference only. The signature will be different if generate again)
72 74 61 73 6d 50 31 4c 63 48 79 5a 63 71 35 36 67 52 34 78 57 45 35 78 68 54 78 59 35 33 6f 6f 6f 4d 32 53 61 36 63 75 42 52 61 78 72 71 33 39 63 54 56 6b 39 4d 67 4d 76 6e 38 61 5a 45 6d 4e 78 6b 56 39 55 39 63 62 41 6a 43 50 4d 68 48 46 6f 51 33 57 69 66 57
And then encode with base58 and put in signature
field.
{
"fee": 30000000,
"feeScale": 100,
"timestamp": 1547722056762119200,
"contractId": "CF8eRsiyXSP9qnrby9AqgzJGJbjYx9aXkbi",
"functionIndex": 4,
"functionData": "14uNyNb5nFsicZ6dELMKjNAHz3PLM3mjiu6pHsbTrgTYYWb5WRy",
"senderPublicKey": "B2Khd89jtnpuzGdnyGRcnKycZMBCo6PsotFcWWi1wMDV",
"attachment": "HXRC"
"signature": "rtasmP1LcHyZcq56gR4xWE5xhTxY53oooM2Sa6cuBRaxrq39cTVk9MgMvn8aZEmNxkV9U9cbAjCPMhHFoQ3WifW"
}
Pass this JSON to full node. And full node broadcasts to network with API /contract/broadcast/execute
.