Dealing with Asset Transactions
Overview
Neo N3 has only one type of digital assets, i.e. NEP-17 assets, which are managed by BALANCE. The exchanges mainly deal with user balance queries, deposits, withdrawals, and other operations of this type assets.
Following flow charts show the work processes of these operations:
Network fee
The network fee, as a reward for the consensus nodes generating blocks, is charged when the user submits a transactions to Neo blockchain. There is a base fee for each transaction and the calculation formula is shown below. The transaction is only executed if the fee paid by the user is greater than or equal to the base fee; otherwise, the transaction will be treated as invalid.
-
VerficationCost: Fees for instructions executed by NeoVM to verify transaction signatures.
-
tx.size: The transaction data byte length
-
FeePerByte: Transaction fee per byte, currently defined as 0.00001 GAS in PolicyContract.
System fee
The system fee is charged for the instructions executed by NeoVM. For each instruction fee refer to System Fee . The total system fee you need to pay depends on the number and type of the instructions executed by your smart contract. The following figure shows the calculation formula:
Instructions fee
In Neo N3, NeoVM instructions fee has decreased to 1/1000 of the original fee in Neo Legacy, which significantly reduces the development cost.
In comparison with Neo Legacy:
Dealing with query transactions
The way for a exchange itself to query balance of the user deposit address is different than the way it deal with the user's request of balance querying.
Querying the user deposit address balance
The exchange needs to do the following:
-
Construct JSON files to invoke either of the following RPC methods:
-
getnep17balances (Plugin TokensTracker is required)
-
invokefunction (Plugin RpcServer is required)
-
-
Send a
getnep17balances
request to the Neo RPC server to get the asset hash and amount. -
Send the invokefunction requests twice to the Neo RPC server to get the corresponding asset symbol and decimals, respectively.
-
Calculate the user balance according to the returned values.
To query the balance of an asset for a particular user, use invokefunction
to call the balanceOf
method of asset.
Invoking getnep17balances to query
In JSON, a general getnep17balances request body is in the following form:
After sending the request you will get the following response:
As we can see in the request above, there are two kinds of assets which hashes are "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5" and "0xd2a4cff31913016155e38e474a2c06d08be276cf". Now we need to call invokefunction to get symbol and decimals of the asset, which will be elaborated below.
In the example above the asset A symbol is NEO, decimals is 0, and the user A balance is 2 NEO. For the asset B, its symbol is GAS, decimals is 8, and the balance is 700000000/10⁸ GAS (7 GAS).
Invoking invokefunction to query
In JSON, a general invokefunction request body is in the following form:
You need to replace these strings when querying the user's balance:
-
script hash
The script hash of the NEP-17 asset you are querying. For example:
-
NEO is0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5
-
GAS is0xd2a4cff31913016155e38e474a2c06d08be276cf
-
-
method name
The name of the method you are invoking. To query the user's balance, you need to invoke these three methods:
balanceOf
-
Syntax:
public static BigInteger balanceOf(byte[] account)
-
Remarks: "balanceOf" returns the token balance of the '''account'''.
-
Syntax:
public override byte Decimals { [Safe] get => 8; }
-
Remarks: "decimals" returns the number of decimals used by the token.
-
Syntax:
public override string Symbol { [Safe] get => "EXAMPLE"; }
-
Remarks: "symbol" returns the token symbol.
decimals
symbol
-
-
optional arguments
Optional. If the method you are invoking requires arguments, you can pass them by constructing these parameters into an array. For example, "balanceOf" in NEP-17 returns the token balance of the "account":
public static BigInteger balanceOf(byte[] account)
So you need to pass the account as an argument in the "balanceOf" method.
Example
Invoking balanceOf
Suppose the account address is NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag, you need to convert it into Hash160 type and construct this parameter as a JSON object:
Then you can construct the JSON message as the following:
Request Body:
After sending the request, you will get the following response:
To get the balance divide the returned value by decimals, without needing of data conversion.
Invoking decimals
Request Body:
After sending the request, you will get the following response:
It returns integer 8.
Invoking symbol
Request Body:
After sending the request, you will get the following response:
It returns "R0FT" which can be decoded to "GAS".
Calculating the User Balance
According to all the returned values, we can calculate the user balance as follows: The balance = return / 10decimals
Dealing with users' queries
The actual user balance in the exchange is recorded in the exchange database. The exchange needs to write programs to monitor each transaction of each block, record all deposits and withdrawals transactions in the database, and modify the user balance in the database accordingly.
Dealing with User Deposits
To get the user deposits information the exchange needs to do the following:
-
Get each block details using the
getblock
API, including details of all the transactions in the block. -
Invoke the
getapplicationlog
API to get the details of each "InvocationTransaction" transaction and analyze the transaction content to complete the user deposit.
Invoking getapplicationlog
This API is used to get transaction information.
After correctly installing the ApplicationLogs plug-in and starting the neo-cli node, you can find a folder "ApplicationLogs" is generated under the root path. The complete contract log is recorded in this directory, and each NEP-17 transaction is recorded in a leveldb file.
The following shows an example of the API invoking result.
This example shows the log of a successful token transfer, however, in case of a failed transfer or NeoVM exception, the outcomes can be:
Failed transfer: no Transfer notification event is returned, execution ends in a
HALT
state with a stack value ofFalse
.NeoVM exception: a Transfer notification event may or may not be returned, but execution ends in a
FAULT
state.
The parameters related to a transaction in the file are the following:
-
contract : the script hash of smart contract. For exchanges, it is the script hash of NEP17 assets type and the unique identity of the asset. For example, here "0xd2c270ebfc2a1cdd3e470014a4dff7c091f699ec" is the NEP17 asset script hash.
-
eventname : the event identifier of smart contact. Exchanges only need to listen on “transfer” transactions to find out users' transfer transactions. There may be more than one eventname in the Notifications array, and only those with the Transfer keyword are NEP17 Transfer data.
-
state : The objects included in the array are:
-
from account: The first object in the array is the account address where the asset is transferred from. Its type "bytearray" and the value "uXtKzX+CD2HS1NT5rqXrUEmN31U=“ can be decoded to "NcphtjgTye3c3ZL5J5nDZhsf3UJMGAjd7o" with base64.
-
to account: The second object in the array is the account address where the asset is transferred to. Its type "bytearray" and the value "7ztGBn8vR7L38EQqojcghdCHCO8=“ can be decoded to "Nhiuh11SHF4n9FE6G5LuFHHYc7Lgws9U1z" with base64. If the address is an exchange account address, it is a deposit transaction.
-
amount: The last object in the array is the transfer amount, which value is 800000000000. Since the decimal is 8 bit the value is actually 8000.00000000.
In Neo, hexadecimal strings are processed in big-endian order if they are preceded by 0x, or little-endian order if they are not.
-
Regarding the data format conversion of the transfer in the file, you can refer to Neo3 data conversion .
Dealing with User Withdrawals
The exchange can choose one of the following way to send assets to users:
-
Neo-CLI command:
send
-
RPC method:
sendfrom
-
RPC method:
sendtoaddress
-
RPC method:
sendmany
Neo-CLI Command: send
Syntax
send <id|alias> <address> <amount>|all [from=null] [signerAccounts=null]
Parameters
-
id|alias
: asset ID or asset abbreviations, e.g. neo, gas -
address
: address to transfer assets to -
amount|all
: transfer amount -
from
: address to transfer assets from -
signerAccounts
: signer's address
This command verifies the wallet password.
Example
Transfer 100 Neo to the address NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag:
If you are not sure of the asset ID, you can enter list asset
to view all assets in the wallet.
In above example, we can also replace the asset ID with asset abbreviation, as shown below:
RPC Method: openwallet
Before you can invoke any of the wallet-related RPC methods you must invoke the method
openwallet
first.
The key "params" includes an array of two parameters.
"params":[path, password]
For example, to open the wallet a.json
with a password 111111
, you can construct a JSON file as follows and send it to RPC server.
Request body:
After sending the request, you will get the following response:
RPC Method: sendfrom
The key "params" includes an array of four parameters.
"params":[script hash, address from, address to, amount]
For example, to send 10 NEO from NcphtjgTye3c3ZL5J5nDZhsf3UJMGAjd7o to Nhiuh11SHF4n9FE6G5LuFHHYc7Lgws9U1z, construct a JSON file as follows and send it to RPC server.
Request body:
After sending the request, you will get the following response:
RPC Method: sendtoaddress
The key "params" includes an array of three parameters.
"params":[script hash, address, amount]
For example, to send 1000 GAS to Nhiuh11SHF4n9FE6G5LuFHHYc7Lgws9U1z , construct a JSON file as follows and send it to RPC server.
Request Body:
After sending the request, you will get the following response:
RPC Method: sendmany
The key "params" includes an array of at least two parameter:
"params":[address from(optional), []]
For example, to send 100 NEO and 1000 GAS to Nhiuh11SHF4n9FE6G5LuFHHYc7Lgws9U1z from NcphtjgTye3c3ZL5J5nDZhsf3UJMGAjd7o, you can construct a JSON file as follows and send it to RPC server.
Request Body:
After sending the request, you will get the following response: