NEM Developer Kit for Node.js and the browser
This SDK is just a "draft" for now, lot of things will change as development and feedback goes, but it is enough to start developing great applications.
Nano Wallet will integrate this library instead of everything being merged in the same project. So we have a real separation between core and client.
- Easy integration
- Organised in namespaces
- Create wallets compatible with Nano Wallet client
- Simple transactions
- Mosaic transactions
- Encrypted, unencrypted and hex messaging
- Create and audit Apostilles
- Create and verify signatures
- Helpers and formatting functions
- 22 NIS API requests with promises
- Websockets for real time blockchain data
- Commented code and examples
- Browser examples ready to use out of the box
Download the library source, open the dist/
folder and put nem-sdk.js
into your project.
Library include the require()
function so you can require()
the module directly
<script src="nem-sdk.js"></script>
<script>
// Include the library
var nem = require("nem-sdk").default;
console.log(nem)
</script>
npm install nem-sdk
// Use require
var nem = require("nem-sdk").default;
// ES6
import nem from 'nem-sdk';
// Use the build/ folder
var nem = require("path/to/build/index.js").default;
npm install
npm run build
npm run browserify
The SDK is organised in namespaces and sub-namespaces.
There is 4 main namespaces:
requests
: Requests to NIS and the outside worldwebsockets
: Connection, subscription and requests to NIS websockets
keyPair
: Functions to create keypair from hex and sign data with ithelpers
: Miscellaneous cryptographic functions, like encrypt private key, decrypt a wallet, derive a password...nacl
: External cryptographic library modified for NEMjs
: Access to the crypto-js library
address
: Functions regarding NEM addresses like base32 encoding / decoding, verify, convert public key to address...objects
: Contains usesul objects modelsfees
: Contains all the transaction fees and calculation functionsnetwork
: Contains networks types and functions relatednodes
: Contains array of nodes for different networks, default nodes, search by hash nodes...sinks
: Contains the sink addresses for namespaces and mosaics by networktransactions
: Contains functions to prepare and send transaction objectstransactionTypes
: Contains all the NEM transactions typeswallet
: Contains functions to create wallets
convert
: Contains convertion functionshelpers
: Contains miscellaneous helper functionsformat
: Contains miscellaneous formatting functionsnty
: Contains functions to build nty dataSerialization
: Contains functions to serialize transactions
Consult the code directly for details, almost all functions are commented, with parameters, return values and types.
Namespace: nem.model.objects
This namespace allow to easily get
or create
objects to use in the SDK. Each object is accessible via a keyword.
Public methods:
get
create
Keywords:
common
: An object to hold password and private keyendpoint
: An object containing info about a remote nodemosaicAttachment
: An object containing mosaic data to join in a transfer transactionmosaicDefinitionMetaDataPair
: An object of objects containing mosaics propertiesinvoice
: An invoice object working on NEM mobile clientstransferTransaction
: An un-prepared transfer transaction objectsignatureTransaction
: An un-prepared signature transaction object
Return an empty object
// Get an empty object
var object = nem.model.objects.get("keyword");
// Get an empty object
var transferTransaction = nem.model.objects.get("transferTransaction");
{
"amount": "",
"recipient": "",
"recipientPublicKey": "",
"isMultisig": false,
"multisigAccount" : "",
"message": "",
"isEncrypted" : false,
"mosaics": []
}
Return an object with parameters.
Using the create
method takes different parameters depending of the object.
Name | Type | Description |
---|---|---|
password | string | A password |
privateKey | string | A private key |
Name | Type | Description |
---|---|---|
host | string | An NIS uri |
port | string | An NIS port |
Name | Type | Description |
---|---|---|
namespaceId | string | A namespace name |
mosaicName | string | A mosaic name |
quantity | long number | A quantity in micro-units |
Name | Type | Description |
---|---|---|
recipient | string | A recipient address |
amount | number | An amount |
message | string | A message to join |
// Create an object with parameters
var object = nem.model.objects.create("keyword")(param1, param2, ...);
// Create an object with parameters
var transferTransaction = nem.model.objects.create("transferTransaction")("TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S", 10, "Hello");
{
"amount": 10,
"recipient": "TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S",
"recipientPublicKey": "",
"isMultisig": false,
"multisigAccount" : "",
"message": "Hello",
"isEncrypted" : false,
"mosaics": []
}
Consult src/model/objects.js
for details about objects and creation parameters
Namespace: nem.model.transactions
Public methods:
prepare
send
prepareMessage
Keywords:
transferTransaction
mosaicTransferTransaction
signatureTransaction
This namespace is used to prepare and send transactions.
For now only preparation of simple and mosaics transactions with encrypted, unencrypted and hex messages are implemented.
In part 2 you can see in the examples how to build a transfer transaction object, with or without data.
Transaction objects you will create via nem.model.objects
are un-prepared transaction objects. They only contain raw / incomplete data and need to be arranged before being signed and sent.
Using the prepare
method takes different parameters depending of the transaction object.
Name | Type | Description |
---|---|---|
common | object | A common object |
tx | object | A transferTransaction object |
network | number | A network id |
Name | Type | Description |
---|---|---|
common | object | A common object |
tx | object | A transferTransaction object |
mosaicDefinitionMetaDataPair | object | A mosaicDefinitionMetaDataPair object (see 3.4) |
network | number | A network id |
// Prepare a transaction object
var preparedTransaction = nem.model.transactions.prepare("keyword")(param1, param2, ...);
// Create an object with parameters
var transferTransaction = nem.model.objects.create("transferTransaction")("TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S", 10, "Hello");
// Prepare the above object
var transactionEntity = nem.model.transactions.prepare("transferTransaction")(common, transferTransaction, nem.model.network.data.testnet.id)
{
type: 257,
version: -1744830463,
signer: '0257b05f601ff829fdff84956fb5e3c65470a62375a1cc285779edd5ca3b42f6',
timeStamp: 62995509,
deadline: 62999109,
recipient: 'TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S',
amount: 10000000,
fee: 2000000,
message: { type: 1, payload: '48656c6c6f' },
mosaics: null
}
You can easily see the difference between an un-prepared transaction object (2.2) and above prepared object.
Amounts are in the smallest unit possible in a prepared transaction object:
1000000 = 1 XEM
// Create an object with parameters (multisig account address and inner transaction hash)
var signatureTransaction = nem.model.objects.create("signatureTransaction")("TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S", "161d7f74ab9d332acd46f96650e74371d65b6e1a0f47b076bdd7ccea37903175");
// Prepare the above object
var transactionEntity = nem.model.transactions.prepare("signatureTransaction")(common, signatureTransaction, nem.model.network.data.testnet.id)
{
type: 4098,
version: -1744830463,
signer: '0257b05f601ff829fdff84956fb5e3c65470a62375a1cc285779edd5ca3b42f6',
timeStamp: 62995509,
deadline: 62999109,
otherHash: {
data: '161d7f74ab9d332acd46f96650e74371d65b6e1a0f47b076bdd7ccea37903175'
},
otherAccount: 'TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S',
fee: 6000000
}
Once your transaction is prepared simply use the send
method of the namespace.
Name | Type | Description |
---|---|---|
common | object | A common object |
entity | object | A prepared transaction object |
endpoint | object | An endpoint object |
// Serialize transfer transaction and announce
nem.model.transactions.send(common, transactionEntity, endpoint).then(function(res) {....});
A NemAnnounceResult
object (http://bob.nem.ninja/docs/#nemAnnounceResult)
The two provided example speaks for themselves:
- See
examples/node/transfer.js
for node - See
examples/browser/transfer
for browser
The node version contains only the strict necessary while browser example needs to handle form and update fees.
- See
examples/node/mosaicTransfer.js
for node - See
examples/browser/mosaicTransfer
for browser
Similar to transfer transaction, it use the same un-prepared transferTransaction
object, but needs an array of mosaicAttachment
objects.
Keyword of the preparation function is mosaicTransferTransaction
.
Preparation of mosaic transfer transactions requires a mosaicDefinitionMetaDataPair
object containing mosaic definitions of the mosaics you are joining to the transaction.
Definitions are needed to know informations about the included mosaic(s) and calculate quantity and fee accordingly.
-
You can take it from NIS API using http://bob.nem.ninja/docs/#retrieving-mosaic-definitions and put the definition into
model/objects.js
, in themosaicDefinitionMetaDataPair
object (like shown by the comments). If mosaics used in your application are fixed, it is the way to go. -
Query the network using the embedded API requests (
nem.com.requests.namespace.mosaicDefinitions
) as shown in the examples. If mosaics used in your application are not fixed, it is the way to go.
To communicate with an NIS you need an endpoint
object. The object contains the node host and port so it is easier to handle.
// Custom endpoint
var endpoint = nem.model.objects.create("endpoint")("http://myNode", 7890);
// Using sdk data
var endpoint = nem.model.objects.create("endpoint")(nem.model.nodes.defaultTestnet, nem.model.nodes.defaultPort);
Namespace: nem.com.requests
22 NIS API calls and a few other external requests are implemented and organised in namespaces:
data
: Gets account dataforwarded
: Gets the account data of the account for which the given account is the delegate accountharvesting.blocks
: Gets harvested blocksharvesting.stop
: Stop delegated harvestingharvesting.start
: Start delegated harvestingnamespaces.owned
: Gets namespaces that an account ownsmosaics.owned
: Gets mosaics that an account ownsmosaics.allDefinitions
: Gets all mosaic definitions that an account ownsmosaics.definitions
: Gets mosaic definitions that an account has createdtransactions.incoming
: Gets incoming transactionstransactions.unconfirmed
: Gets unconfirmed transactionstransactions.all
: Gets all transactionstransactions.outgoing
: Gets outgoing transactionsunlockInfo
: Gets information about the maximum number of allowed harvesters and how many harvesters are already using the node
audit
: Audit an apostille
height
: Gets the chain heightlastBlock
: Gets the last blocktime
: Get network time
heartbeat
: Gets the node status
xem
: Gets XEM price in BTCbtc
: Gets BTC price in $
roots
: Gets root namespacesinfo
: Gets the namespace with given idmosaicDefinitions
: Gets mosaic definitions of a namespace
all
: Gets all supernodes info
byHash
: Gets a transaction by hashannounce
: Announce a transaction to the network
Requests are wrapped in Promises
which allow to use then()
for callbacks
// Gets chain height
nem.com.requests.chain.height(endpoint).then(function(res) {
console.log(res)
}, function(err) {
console.error(err)
})
// Gets account data
nem.com.requests.account.data(endpoint, "TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S").then(...);
Consult src/com/requests
for details about requests parameters.
- See
examples/browser/monitor
for browser demonstration - See
examples/node/requests
for all requests in node
Namespace: nem.com.websockets
Note: For now webSockets use two versions of SockJS to work in Node (v1.1.4) and the browser (v0.3.4). Using only latest SockJS v1.1.4, gives an error when used in browser:
XMLHttpRequest cannot load http://bob.nem.ninja:7778/w/messages/info?t=1429552020306. A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin 'null' is therefore not allowed access.
If anyone has a solution to that, it is welcome.
create
: Create a connector objectclose
: Close the websocket connection
errors
: Subscribes to error channel
data
: Subscribes to account data channeltransactions.recent
: Subscribes to recent transactions channeltransactions.confirmed
: Subscribes to confirmed transactions channeltransactions.unconfirmed
: Subscribes to unconfirmed transactions channel
height
: Subscribes to new chain height channelblocks
: Subscribes to new blocks channel
data
: Requests account data from channeltransactions.recent
: Requests recent transactions from channel
You first need to create a connector object pointing to the right endpoint then use this connector to open the connection.
If connection is a success, the connector.connect
function will resolve a promise in a .then()
function, in which you can request and subscribe to channels.
Subscription takes a connector and resolve in a simple callback function (.then()
not supported), where your data will be received from the channel. It acts exactly like a .on('something')
.
Name | Type | Description |
---|---|---|
endpoint | object | An endpoint object (using websocket port) |
address | string | A NEM account address |
Name | Type | Description |
---|---|---|
connector | object | An open connector object |
callback | function | A callback function where data will be received |
address | string | A NEM account address (optional, for custom account subscription) |
Name | Type | Description |
---|---|---|
connector | object | An open connector object |
address | string | A NEM account address (optional, for custom account request) |
// Create an endpoint object
var endpoint = nem.model.objects.create("endpoint")(nem.model.nodes.defaultTestnet, nem.model.nodes.websocketPort);
// Address to subscribe
var address = "TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S";
// Create a connector object
var connector = nem.com.websockets.connector.create(endpoint, address);
// Connect using connector
connector.connect().then(function() {
// If we are here we are connected
console.log("Connected");
// Subscribe to new blocks channel
nem.com.websockets.subscribe.chain.blocks(connector, function(res) {
console.log(res);
});
// Subscribe to account data channel
nem.com.websockets.subscribe.account.data(connector, function(res) {
console.log(res);
});
// Request account data
nem.com.websockets.requests.account.data(connector);
}, function (err) {
// If we are here connection failed 10 times (1/s).
console.log(err);
});
Consult src/com/websockets
for details.
- See
examples/browser/websockets
for browser demonstration - See
examples/nodejs/websockets.js
for Node demonstration
Namespace: nem.utils.helpers
Public methods:
needsSignature
haveTx
getTransactionIndex
haveCosig
createNEMTimeStamp
fixPrivateKey
isPrivateKeyValid
isPublicKeyValid
checkAndFormatUrl
createTimeStamp
getTimestampShort
convertDateToString
extendObj
isHexadecimal
searchMosaicDefinitionArray
grep
isTextAmountValid
cleanTextAmount
formatEndpoint
Namespace: nem.utils.format
Public methods:
address
hexMessage
hexToUtf8
importanceTransferMode
levyFee
nemDate
nemImportanceScore
nemValue
pubToAddress
splitHex
supply
supplyRaw
mosaicIdToName
txTypeToName
Add hyphens to unformatted address.
Name | Type | Description |
---|---|---|
address | string | An unformatted NEM address |
var address = "TBCI2A67UQZAKCR6NS4JWAEICEIGEIM72G3MVW5S";
// Add hyphens to NEM address
var fmtAddress = nem.utils.format.address(address); //TBCI2A-67UQZA-KCR6NS-4JWAEI-CEIGEI-M72G3M-VW5S
Change a NEM quantity into an array of values.
Quantity means the smallest unit (1.000000 XEM = 1'000'000)
Name | Type | Description |
---|---|---|
data | number | A quantity (smallest unit) |
var xemQuantity = 10003002; // Smallest unit for XEM
// Format quantity
var fmt = nem.utils.format.nemValue(xemQuantity)
var fmtAmount = fmt[0] + "." + fmt[1]; // 10.003002
Format hexadecimal payload contained in message objects.
Message objects also contains type:
Type 1: Plain message. Type 2: Encrypted message.
Name | Type | Description |
---|---|---|
msg | object | A message object |
var msg = {
"type": 1,
"payload": "4e454d20697320617765736f6d652021"
}
// Format msg
var fmt = nem.utils.format.hexMessage(msg); // NEM is awesome !
A private key is a 64 or 66 characters hex string, looking like this:
// 64 characters hexadecimal private key
712cb1b773066cf572b6f271cb10be49b3e71ed24dd7b6a2ac876af9f3ad84e7
// 66 characters hexadecimal private key (always start with 00 in that case)
00d32b7c09e8747908b1ed9dbc893ff33987b2275bb3401cd5199f45b1bbbc7d75
To obtain a private key, 4 choices are possible:
-
You can type yourself a random 64 hexadecimal string
-
Use the included PRNG:
// Create random bytes from PRNG
var rBytes = nem.crypto.nacl.randomBytes(32);
// Convert the random bytes to hex
var privateKey = nem.utils.convert.ua2hex(rBytes);
- Create a private key from a passphrase:
// Derive a passphrase to get a private key
var privateKey = nem.crypto.helpers.derivePassSha(passphrase, 6000).priv;
- Use a private key from another source.
Key pairs are objects representing accounts keys (private, secret and public) and are used to sign data or transactions.
Name | Type | Description |
---|---|---|
hexData | string | 64 or 66 hexadecimal characters |
// A funny but valid private key
var privateKey = "aaaaaaaaaaeeeeeeeeeebbbbbbbbbb5555555555dddddddddd1111111111aaee";
// Create a key pair
var keyPair = nem.crypto.keyPair.create(privateKey);
To sign a transaction or any other data simply use the above keyPair
object
var signature = keyPair.sign(data);
You can extract the public key from the keyPair
object very easily
var publicKey = keyPair.publicKey.toString();
To verify a signature you need the signer public key, the data that have been signed and the signature.
Name | Type | Description |
---|---|---|
publicKey | string | The signer public key |
data | string | The data that were signed |
signature | string | The signature of the data |
var signer = "0257b05f601ff829fdff84956fb5e3c65470a62375a1cc285779edd5ca3b42f6"
var signature = "392511e5b1d78e0991d4cb2a10037cc8be775e56d76b8157a4da726ccb44042e9b419084c09128ffe2a78fe78e2a19beb0e2f57e14b66c962187e61457bd9e09"
var data = "NEM is awesome !";
// Verify
var result = nem.crypto.verifySignature(signer, data, signature);
- See
examples/nodejs/verifySignature.js
for node demonstration
Namespace: nem.model.address
Public methods:
b32encode
b32decode
toAddress
isFromNetwork
isValid
clean
Addresses are base32 string used to receive XEM. They look like this:
NAMOAV-HFVPJ6-FP32YP-2GCM64-WSRMKX-A5KKYW-WHPY NAMOAVHFVPJ6FP32YP2GCM64WSRMKXA5KKYWWHPY
The version without hyphens ("-") is the one we'll use in our queries and lower level processing. The formatted version is only for visual purposes.
-
Mainnet (104): N
-
Testnet (-104): T
-
Mijin (96): M
var address = nem.model.address.toAddress(publicKey, networkId)
var isValid = nem.model.address.isValid(address);
var isFromNetwork = nem.model.address.isFromNetwork(address, networkId);
Consult src/model/address.js
for more details
Namespace: nem.crypto.helpers
Public methods:
toMobileKey
derivePassSha
passwordToPrivatekey
checkAddress
randomKey
decrypt
encrypt
encodePrivKey
encode
decode
Namespace: nem.model.wallet
Public methods:
createPRNG
createBrain
importPrivateKey
The SDK allow to create wallets 100% compatible with the Nano Wallet client (as BIP32 not implemented yet the client will ask for an upgrade).
Wallet can contain multiple accounts in an object of objects. The first account is the primary account and is labelled like this by default.
Every accounts objects but primary of brain wallets contains an encrypted private key. Brain wallets primary do not contains an encrypted private key because it is retrieved by the password / passphrase.
Each wallet has an algo
property, it is needed to know how to decrypt the accounts.
Wallet files (.wlt) are just storing a wallet object as base 64 strings.
nem.model.wallet.createPRNG
create a wallet object with the primary account's private key generated from a PRNG
// Set a wallet name
var walletName = "QuantumMechanicsPRNG";
// Set a password
var password = "Something";
// Create PRNG wallet
var wallet = nem.model.wallet.createPRNG(walletName, password, nem.model.network.data.testnet.id);
nem.model.wallet.createBrain
create a wallet object with primary account's private key derived from a password/passphrase
// Set a wallet name
var walletName = "QuantumMechanicsBrain";
// Set a password/passphrase
var password = "Something another thing and something else";
// Create Brain wallet
var wallet = nem.model.wallet.createBrain(walletName, password, nem.model.network.data.testnet.id);
nem.model.wallet.importPrivateKey
create a wallet object with primary account's private key imported
// Set a wallet name
var walletName = "QuantumMechanicsImported";
// Set a password
var password = "Something";
// Set private key
var privateKey = "Private key to import";
// Create a private key wallet
var wallet = nem.model.wallet.importPrivateKey(walletName, password, privateKey, nem.model.network.data.testnet.id);
Create an empty file, name it walletName.wlt
and put the base 64 string given by below code
// Convert stringified wallet object to word array
var wordArray = nem.crypto.js.enc.Utf8.parse(JSON.stringify(wallet));
// Word array to base64
var base64 = nem.crypto.js.enc.Base64.stringify(wordArray);
nem.crypto.helpers.passwordToPrivatekey
is a function to decrypt an account into a wallet and return it's private key into the common
object
// Create a common object
var common = nem.model.objects.create("common")("walletPassword/passphrase", "");
// Get the wallet account to decrypt
var walletAccount = wallet.accounts[index];
// Decrypt account private key
nem.crypto.helpers.passwordToPrivatekey(common, walletAccount, wallet.algo);
// The common object now has a private key
console.log(common)
Namespace: nem.model.apostille
Public methods:
create
generateAccount
hashing
verify
This namespace is used to create and verify Apostilles. For detailled informations about Apostille: https://www.nem.io/ApostilleWhitePaper.pdf
nem.model.apostille.create
create an apostille object containing information about the apostille, and the transaction ready to be sent via nem.model.transactions.send
.
// Create a common object holding key
var common = nem.model.objects.create("common")("", "privateKey");
// Simulate the file content
var fileContent = nem.crypto.js.enc.Utf8.parse('Apostille is awesome !');
// Create the Apostille
var apostille = nem.model.apostille.create(common, "Test.txt", fileContent, "Test Apostille", nem.model.apostille.hashing["SHA256"], false, {}, true, nem.model.network.data.testnet.id);
// Serialize transfer transaction and announce
nem.model.transactions.send(common, apostille.transaction, endpoint).then(...)
- See
examples/node/apostille/create
for creation example in node
nem.model.apostille.verify
verify an apostille from a file content (as Word Array) and an apostille transaction object.
// Create an NIS endpoint object
var endpoint = nem.model.objects.create("endpoint")(nem.model.nodes.defaultTestnet, nem.model.nodes.defaultPort);
// Simulate the file content
var fileContent = nem.crypto.js.enc.Utf8.parse('Apostille is awesome !');
// Transaction hash of the Apostille
var txHash = "9b2dc096fb55e610c97a870b1d385458ca3d60b6f656428a981069ab8edd9a28";
// Get the Apostille transaction from the chain
nem.com.requests.transaction.byHash(endpoint, txHash).then(function(res) {
// Verify
if (nem.model.apostille.verify(fileContent, res.transaction)) {
console.log("Apostille is valid");
} else {
console.log("Apostille is invalid");
}
}, function(err) {
console.log("Apostille is invalid");
console.log(err);
});
- See
examples/node/apostille/audit
for verification example in node
Consult src/model/apostille.js
for more details