Exchange Integration Ignis
Introduction
The following page explains how to integrate the IGNIS token, the token of Ignis, the first child chain of the Ardor blockchain, into a crypto-currency exchange.
In this document we use IGNIS to refer to the token, Ignis to refer to the child chain and Ardor to refer to the platform in general.
Prerequisites
Ardor node running 24/7 with fully synchronized blockchain.
It is also recommend to setup a testnet node for QA and testing purposes.
All examples in this document:
Use the 26876 testnet port. The mainnet port is 27876.
Use NXT Accounts, however, in ARDOR the NXT prefix is replaced with ARDOR prefix, everything else works the same way.
Ignis also supports numeric account ids from which the ARDOR account ids are derived. These numeric account ids has no error correction therefore we recommend that you'll never use it as any typo will lead to lost user funds.
Accepting Deposits
Unlike Bitcoin's throw away addresses, Ardor addresses are somewhat expensive to maintain.
Each Ardor address and public key combination are maintained forever in the blockchain, however maintaining a completely empty account is less costly than maintaining an account with non-zero balance.
Due to this we do not recommend using a throw away Ardor address per deposit as creating these addresses require higher fees payment.
Instead, we recommend creating at most one deposit address per user or even direct all deposits to the same address and maintain user identity using a message attached to the deposit transaction.
Deposits can be accepted using one of the following approaches:
- Message Based Deposits - IGNIS deposits are sent to a single address, the user is identified by an attached message.
- Address per User - IGNIS is deposited to a new deposit address for each user.
Regardless of the method being used, always use a strong passphrase for the deposit account and protect the deposit account with a public key before accepting deposits.
Message-Based Deposits
Each payment transaction in Ignis can have an attached message, either in plain text or encrypted. This allows you to identify the user using a unique identifier such as customer number or order number specified in the message. Which identifier you will use in the message to connect the payment to a specific user account is up to you and won’t be discussed in this document.
To monitor your account for new incoming payments, the GetBlockchainTransactions API call is used with the parameter executedOnly=true (the executedOnly parameter filters out phased transactions which did not execute).
The API call takes the following parameters:
- chain - child chain identifier use 2 for Ignis
- account - deposit account id
- timestamp - if specified, transactions should be newer than this block timestamp
- type - the type of transaction. For payments this should be 0
- subtype - the transaction subtype. For payments this should be 0 as well
- firstIndex - for pagination purposes
- lastIndex - for pagination purposes
- executedOnly - set to true to filter out phased transactions which did not execute
To monitor a specific account for payment transactions use the following URL:
http://localhost:26876/nxt?
requestType=getBlockchainTransactions&
chain=2&account=ARDOR-XK4R-7VJU-6EQG-7R335&
type=0&
subtype=0&
executedOnly=true&
includePhasingResult=true
This is the JSON response:
{
"transactions": [
{
"signature": "d29763e24e6a37d1ce3e20028626c72ff85c8c89003c19910a480467d321040f706e2b7bf4868958f104d38bc9899e6cbccfdffcb7b2e0a7214e31a134a7363e",
"transactionIndex": 1,
"type": 0,
"fxtTransaction": "14648538114781480244",
"phased": false,
"ecBlockId": "14641691034343322761",
"signatureHash": "c4ff8f67b36e51267421928009a7e7da97d481d0d39a79e157ee15c77e0dd710",
"attachment": {
"version.OrdinaryPayment": 0
},
"senderRS": "ABA-V4D7-D3XW-JX9Z-EAC8K",
"subtype": 0,
"amountNQT": "123000000",
"recipientRS": "ABA-XK4R-7VJU-6EQG-7R335",
"block": "6658279899956636731",
"blockTimestamp": 809066,
"deadline": 15,
"timestamp": 809009,
"height": 13648,
"senderPublicKey": "584486d2ba4dbd7eaeadd071f9f8c3593cee620e1e374033551147d68899b529",
"chain": 2,
"feeNQT": "1000000",
"confirmations": 1,
"fullHash": "8eed7b44f61235a7ec8a69b38dac5b7aaff71bb868187d56853512870f5af7d3",
"version": 1,
"sender": "14454664023893707109",
"recipient": "5873880488492319831",
"ecBlockHeight": 12926
}
... more transactions ...
]
}
Loop over this array of transactions and process the transactions one by one. Note that this response includes both incoming and outgoing payment transactions. You should filter out your own (outgoing) payments by looking at the senderRS account address.
The important information of a transaction response is:
- senderRS - the sender’s account id
- confirmations - the number of confirmations
- amountNQT - the amount sent in NQT form
- attachment.message - optional, an attached plain text message
- attachment.encryptedMessage - optional, an attached encrypted message
- timestamp - the time the transaction was made, in seconds since the genesis block
- blockTimestamp - the time of the block since the genesis block
- confirmations - number of confirmations received for the block in which the transaction is included
For most transactions, waiting for 10 confirmations should be enough. However, for transactions with large amount, special attention should be given to the transaction timestamp and deadline parameters, since blocks can become orphaned and transactions cancelled as a result in case their deadline has passed.
When genesis time + timestamp + deadline * 60 is bigger than transaction.blockTime + 23 hours, a transaction can be accepted when the confirmations count reaches 10.
For phased transactions i.e. phased=true, verify that approved=true then check the block in which the transaction has executed not the block in which it was initially submitted using the executionHeight field. Another alternative is not to accept phased deposit transactions at all i.e. if phased=true do not credit the transaction, but then you'll have to deal with refunding this transaction manually and document that it is not allowed in the deposit dialog.
If genesis time + transaction.timestamp + transaction.deadline * 60 is smaller than transaction.blockTimestamp + 23 hours, you should wait until the transaction has 720 confirmations before crediting the user’s account. 720 blocks is the maximum depth a blockchain reorganization can go. By waiting that long, you ensure the transaction is always included. Transactions that only required 10 confirmations will be put back in the blockchain automatically due to their longer deadline time.
The default deadline in the client is 24 hours, which means that in 99% of the cases only 10 confirmations will be necessary before crediting the user’s account.
Genesis time for the Ardor blockchain is 1st of Jan, 2018 00:00:00 UTC.
To identify the user, you must look at the transaction attachment. If a plain text message is included, attachment.message is set and attachment.messageIsText is set to “true” (as string).
If an encrypted message was attached instead, attachment.encryptedMessage should exist instead. This is not just a string, but an object and contains two keys; data and nonce.
To decrypt the message use the decryptFrom API.
This API call takes the following parameters:
- account - account id that sent you the encrypted message
- data - the encrypted message data extracted from transaction.attachment.encryptedMessage.data
- nonce - the encrypted message nonce extracted from transaction.attachment.encryptedMessage.nonce
- decryptedMessageIsText - set to “true" if the message you’re trying to decrypt is text
- secretPhrase - passphrase of the account that received the encrypted message i.e. the deposit account
Example: to decrypt the message send the following request parameters:
http://localhost:26876/nxt?
requestType=decryptFrom&
secretPhrase=[passphrase from account ARDOR-EVHD-5FLM-3NMQ-G46NR]&
account=ARDOR-XK4R-7VJU-6EQG-7R335&
data=9fd7a70625996990a4cf83bf9b1568830f557136044fb3209dd7343eec2ed96ec312457c4840dabaa8cbd8c1e9b8554b&
nonce=650ef2a8641c19b9fd90a9ef22a2d50af90aa3b0de3d7a28b5ff2ad193369e7a&
decryptedMessageIsText=true
The response is:
{
"decryptedMessage": "test message",
"requestProcessingTime": 2
}
After you have decrypted the message, you can now credit the customer account with the amount specified in transaction.amountNQT.
Note: If you wish you can show pending deposits to the user for transaction which did not yet reach the required number of confirmations.
You could also check for new transactions since a specific block by specifying the last block’s timestamp+1 as the timestamp parameter:
http://localhost:26876/nxt?requestType=getBlockchainTransactions&...×tamp=83099831
To get the last block timestamp, you would look at the last processed transaction blockTimestamp, or use the getBlockchainStatus API described in the account-based deposits section.
Account-Based Deposits
As discussed above, Ardor accounts are an expensive resource and should not be treated like disposable Bitcoin addresses.
For account-based deposits, you basically generate a new random passphrase for each user. The passphrase should be very strong and at least 35 characters long.
Once you have this passphrase, you can get the account id and public key via the getAccountId API call.
- secretPhrase - account passphrase
- publicKey - account public
Note that you only need to specify one of the above parameters, not both. So in our case, you just specify the secretPhrase parameter.
http://localhost:26876/nxt?
requestType=getAccountId&
secretPhrase=1234
The response is:
{"accountRS":"ARDOR-5WUN-YL5V-K29F-F43EJ","publicKey":"fddcda69eeca58e5d783ad1032d080d2758a4e427881b6a4a6fe43d9e7f4ac34", "account":"15577989544718496596"}
On your site’s deposit page, you will need to show the account address extracted from the accountRS field i.e. ARDOR-5WUN-YL5V-K29F-F43EJ
If the account hasn’t yet had any incoming transactions, you will also need to display the publicKey to the user.
The public key doesn’t have to be displayed any more after it has had it’s first incoming transaction.
When a user sends funds to a new account, it needs to add the public key, so that an announcement of this key can be made. Once done, this is no longer needed.
Accounts without a public are only protected only by the 64 bit account address not by the 256 public key.
Tracking New Account-Based Deposits
To track new deposits, it’s easiest to simply inspect all transactions in a block to see if any of them are to account addresses you generated. An alternative method would be to use the getBlockchainTransactions API detailed in message-based deposits.
(This is to be done in a loop)
Use the getBlockchainStatus API to check if there is a new block. This API call has no parameters.
http://localhost:26876/nxt?requestType=getBlockchainStatus
The response includes lastBlock (block id) and numberOfBlocks (the height).
If numberOfBlocks is different from the previous execution of this API request, one or more new blocks have been generated. The transactions from the block which now has 10 confirmations have to be fetched. You should save in your database the height of the last block you processed.
Use the new getExecutedTransactions API to load all transactions executed at a given height either directly for non-phased transactions or indirectly for phased transactions applied at this height.
http://localhost:26876/nxt?
requestType=getExecutedTransactions&
chain=2&
height=25117
For each of the resulting transactions, see if the recipientRS field corresponds to one of the deposit accounts you generated for your users. If so, this is an incoming payment. Credit the user’s internal balance and send the money to your hot wallet.
Similarly to message based deposits handling, special attention should be given to the transaction timestamp and deadline parameters
After all transactions of this block have been checked, see if you’ve processed the previous block before or not (previousBlock).
If not, traverse through the previous blocks chain until you reach the last processed block.
Withdrawing / Sending Money
When a user wants to withdraw to a specific account, you ask him for the account id he wants to withdraw to. When you receive this account id, you must first check if that account has a public key attached to it or not (i.e. if it’s new or not).
To do this, you must use the getAccountPublicKey API. It takes 1 parameter, account.
- account - account you want the public key of
http://localhost:26876/nxt?
requestType=getAccountPublicKey&
account=ARDOR-C6L6-UQ5W-RBJK-AWDSJ
If the account does not have a public key, you will get this error:
{ "errorCode": 5, "errorDescription": "Unknown account" }
When you get this error, you should also ask the user for his public key or at least display a warning explaining the risk of using an account without a public key.
When you have both the account id and public key, you can verify that they are correct by comparing the given account id with the account id generated by the public key using the getAccountId API call.
- publicKey - account public key
We want to calculate only by publicKey so our request looks like this:
http://localhost:26876/nxt?
requestType=getAccountId&
publicKey=28f56a81e0f8555b07eacffd0e697b21cbbbdf3cf620db14522732b763564f13
You’ll get back a response like this:
{"accountRS":"ARDOR-C6L6-UQ5W-RBJK-AWDSJ","publicKey":"28f56a81e0f8555b07eacffd0e697b21cbbbdf3cf620db14522732b763564f13","requestProcessingTime":0,"account":"9827273118446850628"}
Now compare the response accountRS to the account id the user provided. If they are equal, you can go ahead and perform the withdrawal.
Sending Ignis is done via the sendMoney API call. The relevant parameters are:
- chain - the chain id, use 2 for Ignis
- recipient - recipient’s account address
- amountNQT - amount of Ignis (in NQT)
- feeRateNQTPerFXT - conversion rate between 1 child chain NQT to 1 whole Ardor FXT.
- feeNQT - transaction fee for the transaction in NQT. In most cases set to -1 to let the receiving node calculate the minimum fee
- secretPhrase - sender’s account passphrase
- deadline - deadline for the transaction in minutes. Should be set to the maximum value of 1440
- recipientPublicKey - recipient public key as provided by the user, only needed if the user account has no public key yet (on first transaction)
At the moment (Ardor v2.0.4e) the recipientPublicKey is optional, however not specifying it, puts the user's funds at risk. The recipientPublicKey is mandatory in case you like to attach an encrypted message to the withdrawal transaction of a new account.
This request has to be sent using HTTP POST.
The response should look like this if everything went OK:
{ "fullHash": “10788f7ad3f145b5209da6145327d7fed869…”, ... a lot more information ... }
If there’s an error, you may get a response such as this (other errors may apply):
{ "errorCode": 5, "errorDescription": "Unknown account" }
A correctly executed response should always contain the "transaction" field which represents the newly created transaction id.
Adding a Message To a (Payment) Transaction
You can add messages to any kind of transaction.
To do so, specify the below parameters in your request:
- message - plain text message.
- messageIsText - should be set to the string “true” if text.
- messageToEncrypt - plain text message that should be encrypted.
- messageToEncryptIsText - should be set to the string "true" if text.
In case you want to attach a plain text message, specify message and set messageIsText to “true”.
If you want to attach an encrypted message that can only be read by the recipient, specify messageToEncrypt and set messgaeToEncryptIsText to “true”.
Note that these are not mutually exclusive, you can add both a plain text and encrypted message in the same transaction.
Allowing the user to add a message on your withdrawal page is recommended, so that you can coordinate with other services who use a message-based deposit system.
Hot and Cold Wallets
You should not keep all of your user’s deposits in a single hot wallet. A hot wallet is a wallet for which the passphrase is stored somewhere on your server, so that you can send money from it.
Instead, you should have both a hot and cold wallet. The cold wallet should hold most of the coins and not be accessible from any of your servers. Ideally, you’d manually send from your cold wallet to your hot wallet when more coins are needed for day-to-day operations.
So the best thing to do is to have money sent to your cold wallet address, and then send out to your hot wallet manually when needed.
Additional information
Account Format
The account id is stored internally as a 64 bit signed long variable. When used in APIs it is usually returned as both unsigned number represented as string and using alphanumeric Reed-Solomon representation starting with "ARDOR-" prefix.
For example: ARDOR-ER8M-SYV3-R7EK-EUF3L
In API request parameters and response JSON, you will find both representations, the numeric representation is typically displayed as account, sender, recipient. The alphanumeric representation is typically displayed as accountRS, senderRS, recipientRS (simply always add “RS”). RS stands for Reed-Solomon. This form of address improves reliability by introducing redundancy that can detect and correct errors when entering and using Nxt account ID’s.
NQT Amounts
Whole amounts should be converted to NQT format to be used in API calls. NQT is the name given to 0.00000001 IGNIS (or 10^(-8) in mathematical shorthand). The NQT to Ignis ratio is equivalent to the Satoshi to Bitcoin ratio. Simply put, 1 Ignis is 100000000 NQT, therefore to convert ignis to NQT, simply multiply by 100000000.
Asset and currency QNT amounts
Each NXT Asset and Currency (generally referred to as "Holding") has specific number of decimal positions to which this holding is divisible. API requests and responses always expect the holding quantity to be specified as a whole number without decimal positions. We refer to this value as QNT. For example, when transferring 12.34 units of holding XYZ which has 4 decimal positions, specify the QNT value as 123400.
Minimum Fee and bundling
All outgoing transactions require a fee paid in the child chain token. Since 0 is now a valid fee value for child chains, all CreateTransaction APIs will accept it, instead of using it as a request for the server to calculate and use the minimum fee (as in NXT). To let the server calculate the child transaction fee, a value of feeNQT=-1 should be used, and a new feeRateNQTPerFXT parameter must be supplied, to indicate the exchange rate to use when calculating the fee (since minimum fees can only be calculated in the parent chain token ARDR). If feeRateNQTPerFXT is also set to -1, the server will query the currently known bundlers rates for this child chain, also subject to the minBundlerBalanceFXT limit on effective bundler account balance, and use the best one for the fee calculation.
Transaction fee in ARDR varies depending on the status of the recipient account, sendMoney to a new account will charge higher fee than sendMoney to an existing account so don't make any assumption about the ARDR fee the bundler will have to pay to bundle your transactions, unless you check first if the recipient account is new. One way to do this is to invoke the getAccount API on the recipient account and check for errorCode 5 in the response, then predict the fee according to the current minimum fee specifications. A more robust approach is to submit the transaction when setting the broadcast parameter to false then read the minimumFeeFQT field from the response which indicates the minimum required fee in ARDR the bundler will pay for this transaction.
As bundlers rates cannot be trusted blindly, the transaction will not be broadcasted in this case, the returned transaction JSON including the fees calculated should be reviewed by the user. The bundler rate used will be returned in the bundlerRateNQTPerFXT JSON field, -1 if no bundlers are known for the chain." The way the Ardor wallet works is that it first asks the user to calculate the fee then provides the best available fee from all bundlers as default fee which can be changed manually.
Alternatively, you can develop your own bundling filter which will only bundle your withdrawal transactions. You will need to implement an interface with a single Java method or reuse the available PersonalBundler.java example.
public interface Filter {
boolean ok(Bundler bundler, ChildTransaction childTransaction);
}
You then register this bundling filter using the following property in your nxt.properties file:
- When running Bundlers, only bundle transactions that satisfy this filter.
- Filter class must implement the nxt.Bundler.Filter interface, see
- nxt.addons.PersonalBundler for an example.
- nxt.bundlingFilter=nxt.addons.PersonalBundler
Our recommendation is that if you know that all your withdrawal transactions will be issued from a single account, also perform the bundling using this account and use the provided PersonalBundler filter to bundle only your own withdrawal transactions and ignore other transactions. This will provide you full control over the bundling process for your Ignis withdrawals, you will pay the fee in ARDR and receive back the IGNIS you charge from your customers as transaction fees. You can regularly adjust the bundling rate based on the IGNIS per ARDR rate on your exchange. Bundler is started or updated using the startBundler API. Also see Bundling
Account Propertis Bundler
A more flexible approach is to use the Account Property Bundler in order to designate several accounts whose transactions your bundler will bundle.
Add the property nxt.bundlingFilter=nxt.addons.AccountPropertyBundler to your nxt.properties file
Decide which account will perform the bundling (probably your Ardor deposit account which is guaranteed to contain Ardor to pay the fees), from this account submit a setAccountProperty transaction (better do it from the wallet under the dashboard --> Account Properties menu) set the recipient account to the account which will submit the child chain withdrawal transactions and the name of the property to "bundling", the value of the property is unimportant at the moment. Submit these transactions from the IGNIS chain, chain "2".
Wait for the set account properties transactions to confirm, then start a bundler from the property setter account (Ardor account) and set the bundling parameters as usual. In most cases it makes sense to allow 0 fee transactions but it's not required.
As a result, only transactions submitted by accounts on which you set the "bundling" property will be bundled by your bundler.
Dealing with Assets and Currencies
In addition to sending the main transactional token Ignis between accounts, which is done using the sendMoney API (type:0, subType: 0) the Ardor blockchain supports two additional types of holding types: assets and currencies.
An asset typically represents a share in an entity. Each asset is identified by the unique 64 bit transaction id of the asset issuance transaction, has a name, which is not necessarily unique, has specific number of shares, which can be deleted and increased in the future. Asset quantities are divisible to between 0 to 8 decimal positions as specified by the asset issuance transaction. Use the getAsset API to retrieve the asset properties. Depositing and withdrawing assets is done using the transferAsset transaction, asset transfer transactions are identified by type=2 and subType=1.
A currency is a more general purpose token, identified uniquely using a currency code and also using the unique 64 bit transaction id of the asset issuance transaction. Use the getCurrency API to retrieve the currency properties. Depositing and withdrawing currencies is done using the transferCurrency transaction, currency transfer transactions are identified by type=5 and subType=3. When dealing with currencies, make sure the currency is issued, i.e. the blcokchain passed the issuance height of the currency and that it is not deleted. See more information in the monetary system documentation https://bitbucket.org/JeanLucPicard/nxt/issues/205/monetary-system-documentation
Other considerations regarding deposit and withdrawal of asset shares and currency units, for example the treating of phased transactions and message attachments, should work the same way as Ignis deposits and withdrawals. Quantity and fee calculations for assets and currencies are explained above.
Migration of tokens from NXT to Ardor
When Ardor is launched on mainnet it will be composed of two main tokens ARDR represents the parent chain token, IGNIS represents the first child chain token. There will be other child chain tokens that exchanges may want to support as well. For exchanges which already support NXT, support for the Ardor tokens can be easily implemented by porting the existing NXT integration using the migration instructions
Ardor Initial Distribution
The initial balances of the ARDR token are based on the balances of the ARDR asset id 12422608354438203866 on the NXT blockchain. Each existing NXT account which holds the ARDR asset will receive the same amount of ARDR parent chain token using the same passphrase. During the snapshot period exchanges should freeze deposits and withdrawals of the ARDR token until migration from NXT asset based trading to Ardor parent chain trading is complete.
Ignis Initial Distribution
The initial balances of the IGNIS token are based on the balances of the JLRDA currency id 823491988455668070 on the NXT blockchain. Each existing NXT account which holds the JLRDA currency will receive the same amount of IGNIS child chain token using the same passphrase. The JLRDA token is not tradable so there is no need to freeze trading. In addition each holder of the NXT token, will receive 0.5 IGNIS during the snapshot. Exchanges will receive these IGNIS automatically and need to make sure to credit these IGNIS tokens to their users according to their internal NXT balances at the time of the snapshot.
Snapshot Process
During the snapshot period there is no need to freeze trading of NXT itself. Deposits and withdrawals of the ARDR asset has to be freeze 12 hours before the snapshot and resumed only after the Ardor network is running stable in production. Internal trading of the ARDR token inside the exchange may resume.