Exchange Integration Ardor
Introduction
The following page explains how to integrate Ardor into a currency exchange platform
Prerequisites
Ardor node running 24/7 with fully synchronized blockchain.
It is also recommended 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.
Ardor also supports numeric account ids from which the ARDOR account ids are derived. These numeric account ids have 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 stored 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 since creating these addresses need to pay a higher fee.
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:
1. Message-Based Deposits - Ardor deposits are sent to a single address, the user is identified by an attached message.
2. Address per User - Ardor is deposited to a new deposit address for each user.
When using both methods, always use a strong passphrase for the deposit account.
Message-Based Deposits
Each payment transaction in Ardor can have an attached message, either in plain text or encrypted. This allows you to identify the customer using an identifier provided by the exchange that the user will enter when making a deposit.
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
The API call takes the following parameters:
- chain - set to 1 to identify the Ardor chain
- account - deposit account id
- timestamp - if specified, transactions should be newer than this block timestamp
- type - the type of transaction. For Ardor payments this should be -2
- subtype - the transaction subtype. For payments, this should be 0 as well
- numberOfConfirmations - only load transactions with this number of confirmations
- firstIndex - for pagination purposes
- lastIndex - for pagination purposes
To monitor a specific account for payment transactions use the following URL:
http://localhost:26876/nxt?
requestType=getBlockchainTransactions&
chain=1&
account=ARDOR-XK4R-7VJU-6EQG-7R335&
type=-2&
subtype=0&
numberOfConfirmations=10
This is a sample JSON response (irrelevant fields omitted):
{
"transactions": [
{
"signature": "acbe6720b1a7e13e6af7cdc2dc42e448b5aafb1fa3a481bac08cf150ab9db90842a9d231abb41c280c489f2cca1a122a186048aa6f41dca66663644b6f4e4588",
"transactionIndex": 0,
"type": -2,
"phased": false,
"ecBlockId": "1318911886063902233",
"signatureHash": "a85066e35731f1ee34be7326a16e92cbf703c650d0487b88a947a3c83fe1719e",
"attachment": {
"version.FxtPayment": 0,
"version.PrunablePlainMessage": 1,
"messageIsText": true,
"messageHash": "f8ed492c51010998ab9a8b8909d36cf0e792565e61048cd040fe406c0d519a75",
"message": "{\"chain\":2,\"fullHash\":\"2bf7716b0867c743e3bf630360b852b98dc6559d33a22ede437d690db51fc5a4\"}"
},
"senderRS": "ARDOR-EVHD-5FLM-3NMQ-G46NR",
"subtype": 0,
"amountNQT": "10000000000",
"recipientRS": "ARDOR-XK4R-7VJU-6EQG-7R335",
"block": "4144059555451144178",
"blockTimestamp": 8018267,
"deadline": 1440,
"timestamp": 8018209,
"height": 135271,
"senderPublicKey": "0b4e505972149e7ceb51309edc76729795cabe1f2cc42d87688138d0966db436",
"chain": 1,
"feeNQT": "100000000",
"confirmations": 55607,
"fullHash": "e33ccc4648786cba42bbb052796fb5be45c48e25c3c9b4908b773ff2295272f3",
"version": 1,
"sender": "16992224448242675179",
"recipient": "5873880488492319831",
"ecBlockHeight": 0,
"transaction": "13433244040360115427"
},
{
"signature": "393fc5bf120734468bd2f1d1dfcab6483c32167d6c6a008e4fc386c27985ce02f1e51499900c3c6b3a16e38e21ecd81062e734532b62754b73b2e7e2909dcaa7",
"transactionIndex": 0,
"type": -2,
"phased": false,
"ecBlockId": "7136116332013816990",
"signatureHash": "a1d7c6d14e66572b7a0c2e5567a6ae60420654c8155694d37fbf1e9afa287662",
"attachment": {
"version.FxtPayment": 0
},
"senderRS": "ARDOR-XK4R-7VJU-6EQG-7R335",
"subtype": 0,
"amountNQT": "1000000000000",
"recipientRS": "ARDOR-ZQW8-EKSR-UX8K-CJ6DD",
"block": "17515962222690505699",
"blockTimestamp": 7530374,
"deadline": 15,
"timestamp": 7530360,
"height": 127214,
"senderPublicKey": "112e0c5748b5ea610a44a09b1ad0d2bddc945a6ef5edc7551b80576249ba585b",
"chain": 1,
"feeNQT": "100000000",
"confirmations": 63664,
"fullHash": "2888abfe72b97a3599933de52337578cdb4d77f18fc02c1ceffcc152eb7ad4dc",
"version": 1,
"sender": "5873880488492319831",
"recipient": "11938075473327676294",
"ecBlockHeight": 17000,
"transaction": "3853596334718945320"
}
]
}
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:
- chain - should always equal 1 to identify Ardor
- senderRS - the sender’s account id
- 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. For very large amounts you may want to wait up to 720 which is the maximum possible fork height.
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 sent by testnet transaction 12700027308938063138 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",
}
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 transactions that 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&
chain=1&
account=ARDOR-XK4R-7VJU-6EQG-7R335&
type=-2&
subtype=0&
timestamp=8018267
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.
Preventing users from accidentally sending ARDR without message. nrs_recipient_ui_options properties functionality
Refer to guide Account Setup for Message-based Deposits to set up the property nrs_recipient_ui_options
that prevents users to send funds without a message.
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",
"requestProcessingTime": 2,
"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
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 getBlock API to get the block at the height of 10 blocks ago (10 confirmations).
Pass it the numberOfBlocks parameter from the getBlockchainStatus API response after subtracting 11 from this value, since blocks start at height 0
- height - height of the block, zero-based.
- includeTransactions - set to true to return the array of transactions included in the block
http://localhost:26876/nxt?
requestType=getBlock&
height=135271&
includeTransactions=true
Loop over the "transactions" array
For each transaction, 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, for message-based deposits, 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 ARDOR account id he wants to withdraw to.
Sending Ardor is done via the sendMoney API call. The relevant parameters are:
- chain - set to 1 to identify the Ardor chain
- recipient - recipient’s account address
- amountNQT - amount of Ardor (in NQT (also called FQT in Ardor) i.e. 1^(10-8))
- feeNQT - transaction fee for the transaction in NQT. The minimum value is 100000000 FQT (1 Ardor) but if you are withdrawing to a new account you need to pay 2 Ardor (use the getAccount API to check if the account already exists)
- secretPhrase - sender’s account passphrase
- deadline - deadline for the transaction in minutes. Should be set to the maximum value of 1440
The sendMoney request has to use 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 fullHash field which represents the unique transaction identifier.
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 the account to which funds are withdrawn is configured for message-based deposits, you should prevent the user from sending funds without a message, or with incorrectly formatted message. See the Account Setup for Message-based Deposits guide.
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 that 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
Ardor Account Format
The Ardor 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 Ardor account ID’s. Therefore always use the RS address format in your user interface.
ARDR and FQT Amounts
All Ardor amounts should be converted to FQT format (same as NQT) to be used in API calls. FQT is the name given to 0.00000001 ARDR (or 10^(-8) in mathematical shorthand). The FQT to ARDR ratio is equivalent to the Satoshi to Bitcoin ratio. Simply put, 1 ARDR is 100000000 FQT, therefore to convert ARDR to FQT, simply multiply by 100000000.
Minimum Fee
All outgoing transactions require a fee of at least 1 ARDR. In API calls, this must be represented as 100000000 FQT.
Time Calculations
Genesis time for the Ardor blockchain is 01 JAN, 2018 00:00:00 UTC.