NEM(新经币)公链对接
首先祝Chrome十周年生快,升级后的69耳目一新,继续加油。
前言
通常,当某个交易所要上新币的时候,都会提前下发通知。如果仔细点就会发现,如果是ERC20 Token的话,交易所基本是“秒上”,而对于其他非以太坊公链的Token,交易所一般会提前半月至一月发上币通知。原因很简单,对于ERC20 Token,由于都是运行在以太坊公链上,所以有着共通的运行机制,对于不同的Token,通过调用geth节点的API只需传入不同的合约地址,就能执行不同Token的转账、查余额等等一系列合约方法。也就是说代码可以完全复用,交易所上币只是添加了一个新币的合约地址,就能和其他ERC20 Token共用一套代码。
而对于非以太坊公链的Token,它们运行在其他完全不同的公链上。由于公链之间的开发语言、账户设计、Token设计、Token转账流程、共识机制等等都存在着天差地别,所以对这些公链上的Token进行操作,流程是不同于ERC20 Token的,也就是说代码不能与以太坊的复用。需要重新写一套适配该公链的程序,来完成对该公链上Token的发行、转账、查询等一些列操作。这就是为什么交易所在上非ERC20 Token时耗时较长的原因。
本文记录对于NEM(新经币)的对接过程。
新经币介绍
维基百科介绍:
新经币(New Economy Movement,缩写 NEM),是一种点对点虚拟货币。2015年初发布,其源代码由Java编写并100%属于原创。[1]NEM 广泛发布于人群中[2],其块链采用了全新发明的基于重要性证明POI的同步解决方案。NEM特征也包括:完整的点对点安全系统加密信息系统和基于Eigentrust++算法的声望系统。[3]
新经币NEM使用Java开发,且使用独创的POI(重要性证明机制),并且融合多重签名技术。单从这三点来看,NEM在技术和创新上,在各公链中属于上等马。
NEM新经币的原生Token为XEM。市值在60~100亿之间浮动,排名在10~20名之间。
新经币允许用户在其链上发型资产“mosaic”(翻译过来就是马赛克🌚),对标以太坊上发行的ERC20 Token。区别在于,发行的mosaic并不是以合约形式进行的,所以功能非常简单,不能像以太坊智能合约一样实现多样化的功能。它只具备Token的基础属性,即名称、发行量、精度。以及一些附加属性如发行量是否可修改、是否可转账、描述等。
mosaic代币在链上的唯一标识为mosaic id
,对标以太坊合约的合约地址。mosaic id
由两部分组成:namespace + mosaic name
。所以在创建一个新的mosaic
前,需要先创建一个namespace
,然后在该namespace
下创建mosaic name
。以pundix这个Token来说,它在NEM链上发行的mosaic的id为 pundix:npxs
, 其中 pundix
是它的namespace,npxs
是其mosaic name。创建一个namespace需要花费100XEM,且全网唯一,不可重复。创建完namespace后,就可以在该namespace下创建mosaic,创建一个mosaic花费10XEM。所以在NEM链上发行一个mosaic代币共需要110XEM+交易手续费(约0.3XEM)。且namespace不得与已存在的重复,该namespace下创建的mosaic的名称也不得出现重复。
NEM的账户地址分为测试网可主网。测试网地址以T开头,主网地址以N开头。(创建的时候可以挑NB开头的地址👍🏿)
开发文档
官网: https://nem.io
GitHub: https://github.com/NemProject
文档: http://docs.nem.io/en
NIS节点API文档: https://nemproject.github.io/
NEM官方提供的API SDK比较全面,相当良心:
- Python https://github.com/semolex/nis-python-client
- Java https://github.com/NEMPH/nem-apps-lib https://github.com/rosklyar/nem-library
- C# https://github.com/NemProject/csharp2nem
- Typescript/Javascript http://nemlibrary.com
- Javascript https://github.com/QuantumMechanics/NEM-sdk https://www.npmjs.com/package/nem-api
- NodeJS https://github.com/NemProject/nodejs2nem
- Ruby https://github.com/44uk/nis-ruby
- PHP https://github.com/namuyan/NEM-Api-Library https://github.com/tomotomo9696/NEMTools_PHP https://github.com/evias/nem-php
- Go https://github.com/nem-toolchain/nem-toolchain
NEM节点部署
如果想在本地启动节点并加入到NEM网络当中,过程非常简单。
在http://bob.nem.ninja/
下载nis
最新包之后,解压。nis目录下的config.properties
是一些节点信息配置。可以根据需要修改。然后直接运行nix.runNis.sh
即可启动节点。
✘ ludis@Mac ~/Downloads/package ./nix.runNis.sh
2018-09-06 06:26:38.598 信息 NEM logging has been bootstrapped! (org.nem.deploy.g a)
2018-09-06 06:26:38.617 信息 Acquiring exclusive lock to lock file: /Users/ludis/nem/nis.lock (org.nem.deploy.CommonStarter tryAcquireLock)
2018-09-06 06:26:38.623 警告 no certificate found for (file:/Users/ludis/Downloads/package/nis/nem-deploy-0.6.95-BETA.jar <no signer certificates>) (org.nem.core.metadata.CodeSourceFacade <init>)
2018-09-06 06:26:38.626 信息 Analyzing meta data in <nem-deploy-0.6.95-BETA.jar> (org.nem.core.metadata.JarFacade <init>)
2018-09-06 06:26:38.636 信息 Meta data title <NEM Deploy>, version <0.6.95-BETA> (org.nem.core.metadata.JarFacade <init>)
2018-09-06 06:26:38.639 信息 Starting embedded Jetty Server. (org.nem.deploy.CommonStarter main)
2018-09-06 06:26:39.148 信息 Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@56ef9176: startup date [Thu Sep 06 14:26:39 CST 2018]; root of context hierarchy (org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh)
2018-09-06 06:26:40.751 信息 Loaded JDBC driver: org.h2.Driver (org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName)
2018-09-06 06:26:41.394 信息 Database: jdbc:h2:/Users/ludis/nem/nis/data/nis5_mainnet (H2 1.3) (org.flywaydb.core.internal.dbsupport.DbSupportFactory info)
2018-09-06 06:26:41.553 信息 Current version of schema "PUBLIC": 1.0.7 (org.flywaydb.core.internal.command.DbMigrate info)
2018-09-06 06:26:41.555 信息 Schema "PUBLIC" is up to date. No migration necessary. (org.flywaydb.core.internal.command.DbMigrate info)
2018-09-06 06:26:41.834 INFO HCANN000001: Hibernate Commons Annotations {4.0.4.Final} (org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>)
2018-09-06 06:26:41.848 INFO HHH000412: Hibernate Core {4.3.0.Final} (org.hibernate.Version logVersion)
2018-09-06 06:26:41.853 INFO HHH000206: hibernate.properties not found (org.hibernate.cfg.Environment <clinit>)
2018-09-06 06:26:41.860 INFO HHH000021: Bytecode provider name : javassist (org.hibernate.cfg.Environment buildBytecodeProvider)
2018-09-06 06:26:42.398 INFO HHH000400: Using dialect: org.hibernate.dialect.H2Dialect (org.hibernate.dialect.Dialect <init>)
2018-09-06 06:26:42.782 INFO HHH000399: Using default transaction strategy (direct JDBC transactions) (org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService)
2018-09-06 06:26:42.791 INFO HHH000397: Using ASTQueryTranslatorFactory (org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>)
2018-09-06 06:26:42.898 INFO HV000001: Hibernate Validator 5.0.2.Final (org.hibernate.validator.internal.util.Version <clinit>)
2018-09-06 06:26:45.175 信息 Using DataSource [org.springframework.jdbc.datasource.DriverManagerDataSource@5792c08c] of Hibernate SessionFactory for HibernateTransactionManager (org.springframework.orm.hibernate4.HibernateTransactionManager afterPropertiesSet)
2018-09-06 06:26:45.239 警告 context ================== current: 108627620 (org.nem.nis.NisMain init)
……
……
2018-09-06 14:26:51.640:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@38c460e8{/,null,AVAILABLE}
2018-09-06 14:26:51.640:INFO:oejs.ServerConnector:main: Started ServerConnector@7a814310{HTTP/1.1}{0.0.0.0:7890}
2018-09-06 14:26:51.644:INFO:oejs.Server:main: Started @14689ms
2018-09-06 06:26:51.644 信息 NEM Deploy is ready to serve. URL is “http://192.168.128.10:7890/". (org.nem.deploy.CommonStarter a)
2018-09-06 06:26:51.651 信息 loadBlocks (from height 2802 to height 2901) needed 40ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:51.717 信息 loadBlocks (from height 2902 to height 3001) needed 28ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:51.762 信息 loadBlocks (from height 3002 to height 3101) needed 27ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:51.811 信息 loadBlocks (from height 3102 to height 3201) needed 25ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:51.855 信息 loadBlocks (from height 3202 to height 3301) needed 25ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:51.911 信息 loadBlocks (from height 3302 to height 3401) needed 28ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:51.958 信息 loadBlocks (from height 3402 to height 3501) needed 30ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:52.004 信息 loadBlocks (from height 3502 to height 3601) needed 27ms (org.nem.nis.dao.BlockDaoImpl d)
2018-09-06 06:26:52.063 信息 loadBlocks (from height 3602 to height 3701) needed 24ms (org.nem.nis.dao.BlockDaoImpl d)
……
这样本地的NIS节点就启动且连接到NEM网络了。可以对照文档进行各种功能测试。
区块链浏览器
主网
http://explorer.nemchina.com
http://chain.nem.ninja
nem faucet
测试时一些Token是必须的,但nem水龙头是真的不好找啊,能搜到的一些基本都停用了。。且用且珍惜。
https://xarleecm.com/en/nemfaucet
提示:使用这个水龙头申请Token时,需要先用该页面的插件挖矿几分钟后,才可以申请🤔,有点意思哈。没有免费的午餐么,大夏天的,我这air风扇转的那叫一个欢乐。风扇转完点提交,一般几个小时内就会把Token发到你的账户,可以去浏览器查询。
每小时最多申请100XEM,我申请了10个却意外地得到了500个,果然是个看脸的时代😏。因为创建mosaic需要110+的NEM,所以最好申请多点。
转账
这里使用node sdk,直接将XEM及mosaic转账及余额查询封装起来。需要的可以直接拿去用,转账的具体流程也写的比较清楚,可以参考注释和文档理解。
/**
- nem及mosaic发送交易封装
- /
const nem = require(“nem-sdk”).default;
const config = require(‘./nem_config’)
const logger = require(‘./logger’);
/**
- 创建 endpoint 对象(节点信息)
- host string An NIS uri
- port string An NIS port
- /
const endpoint = nem.model.objects.create(“endpoint”)(config.endpointHost, config.endpointPort)
logger.info(‘endpoint’, endpoint)
/**
- 创建 common 对象 (账户信息)
- password string A password
- privateKey string A private key
- /
const common = nem.model.objects.create(“common”)(config.password, config.privatekey)
// console.log(‘common’, common)
init()
/**
nem及mosaic转账入口
/
const doNemTransaction = (req, callback) => {
let {
name,
address: recipient,
value: amount
} = req.body
let option = {
name: name,
recipient: recipient,
amount: amount
}
logger.info(‘option’, option)
// mosaic Token转账时,transferTransaction amount代表:要执行后续定义的mosaicAttachment的次数!!!
// 而非代表nem的金额,此处与eth Token转账完全不同。为0时转账会成功,但mosaic不会到账~~
if (name == ‘NEM’) {
amount /= Math.pow(10, 6)
option.amount = amount
} else {
amount = 1
}
/**
- 创建 transferTransaction 对象(交易信息)
- recipient string A recipient address
- amount number An amount
- message string A message to join
- /
option.transferTransaction = nem.model.objects.create(“transferTransaction”)(recipient, amount, config.message);
logger.info(‘transferTransaction:’, option.transferTransaction)
if (name == ‘NEM’) transferNem(option, callback)
else transferMosaic(option, callback)
}
/**
转账nem
/
const transferNem = (option, callback) => {
/**
- 签名/打包交易信息
- common object A common object
- tx object A transferTransaction object
- network number A network id
- /
const transactionEntity = nem.model.transactions.prepare(“transferTransaction”)(common, option.transferTransaction, config.networkId)
logger.info(‘transactionEntity:’, transactionEntity)
/**
- 计算nem交易手续费
- 0.05 XEM per 10,000 XEM transferred, capped at 1.25 XEM
- Example: 0.20 XEM fee for a 45,000 XEM transfer, 1.25 XEM fee for a 500,000 XEM transfer.
- /
if (option.amount > 500000) return callback(new Error(‘转账nem不得超过500000’), null)
let nemFee = Math.floor(option.amount / 10000) * 0.05
nemFee = nemFee < 0.05 ? 0.05 : nemFee
nemFee = nemFee > 1.25 ? 1.25 : nemFee
/**
- 计算message fee
- message fee. 0.05 XEM per commenced 32 bytes
- If the message is empty, the fee will be 0
- @param {object} message - An message object
- @param {boolean} isHW - True if hardware wallet, false otherwise
- @return {number} - The message fee
- /
let messageFee = nem.model.fees.calculateMessage(transactionEntity.message, false)
let totalFee = nemFee + messageFee
totalFee = totalFee * Math.pow(10, 6)
logger.info(‘nemFee:’, nemFee, ‘messageFee’, messageFee, ‘totalFee’, totalFee)
transactionEntity.fee = totalFee
/**
- 发送交易(广播交易)
- 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) {
logger.info("交易详情:", res)
//callback(null, res)
if (res && res.message == 'SUCCESS') callback(null, res)
else callback(new Error(res.message), null)
})
.catch(error => {
logger.error('交易失败:', error)
callback(error, null)
});
}
/**
转账mosaic Token
/
const transferMosaic = async (option, callback) => {
const mosaicData = config.mosaicDefinitions[option.name]
if (!mosaicData) return callback(new Error(‘不支持该币种’), null)
const {
namespaceId,
name: mosaicName
} = mosaicData.mosaic.id
// mosaic需转换单位
let divisibility
mosaicData.mosaic.properties.forEach(item => {
if (item.name == 'divisibility') divisibility = item.value
})
if (!divisibility) return callback(new Error(‘divisibility not found’), null)
//blockchain.server 发送转账请求时已经将进制转换,不需要进行二次转换
//option.amount *= Math.pow(10, divisibility)
/**
- 创建 mosaicDefinitionMetaDataPair 对象
- Create variable to store our mosaic definitions, needed to calculate fees properly (already contains xem definition)
- doc: https://nemproject.github.io/#mosaicDefinitionMetaDataPair
- /
const mosaicDefinitionMetaDataPair = nem.model.objects.get(“mosaicDefinitionMetaDataPair”);
logger.info(‘mosaicDefinitionMetaDataPair’, mosaicDefinitionMetaDataPair)
/**
- 创建 mosaic 对象(mosaic是nem上的Token,类比Erc20 Token)
- namespaceId string A namespace name
- mosaicName string A mosaic name
- quantity long number A quantity in micro-units(根据divisibility转换为micro-units,npxsxem: 1000000 = 1 )
- doc: https://nemproject.github.io/#retrieving-mosaic-definitions
- /
var mosaicAttachment = nem.model.objects.create(“mosaicAttachment”)(namespaceId, mosaicName, option.amount);
logger.info(‘mosaicAttachment’, mosaicAttachment)
// Push attachment into transaction mosaics
option.transferTransaction.mosaics.push(mosaicAttachment);
logger.info(‘transferTransaction’, option.transferTransaction)
// 可通过接口实时获取mosaic属性 nem.com.requests.namespace.mosaicDefinitions(endpoint, mosaicAttachment.mosaicId.namespaceId)
// 当前mosaic较少,事先通过接口获取后写在配置文件
mosaicDefinitionMetaDataPair[mosaicData.fullMosaicName] = {};
mosaicDefinitionMetaDataPair[mosaicData.fullMosaicName].mosaicDefinition = mosaicData.mosaic;
// nem mosaic 转账bug: supply为空,导致calculateMosaics返回NaN
// https://github.com/QuantumMechanics/NEM-sdk/issues/36
// https://qiita.com/xiaca/items/9fa40061cd4977b13147
let res = await nem.com.requests.mosaic.supply(endpoint, mosaicData.fullMosaicName)
mosaicDefinitionMetaDataPair[mosaicData.fullMosaicName].supply = res.supply;
/**
* 签名/打包交易信息
* common object A common object
* tx object A transferTransaction object
* mosaicDefinitionMetaDataPair object A mosaicDefinitionMetaDataPair object
* network number A network id
*/
let transactionEntity = nem.model.transactions.prepare("mosaicTransferTransaction")(common, option.transferTransaction, mosaicDefinitionMetaDataPair, config.networkId);
logger.info('transactionEntity', transactionEntity)
/**
* 计算mosaic交易手续费
* https://nemproject.github.io/#transaction-fees
* @param {number} multiplier - A quantity multiplier
* @param {object} mosaics - A mosaicDefinitionMetaDataPair object
* @param {array} attachedMosaics - An array of mosaics to send
* @return {number} - The fee amount for the mosaics in the transaction
*/
let mosaicsFee = nem.model.fees.calculateMosaics(1000000, mosaicDefinitionMetaDataPair, option.transferTransaction.mosaics)
/**
* 计算message fee
* message fee. 0.05 XEM per commenced 32 bytes
* If the message is empty, the fee will be 0
* @param {object} message - An message object
* @param {boolean} isHW - True if hardware wallet, false otherwise
* @return {number} - The message fee
*/
let messageFee = nem.model.fees.calculateMessage(transactionEntity.message, false)
let totalFee = mosaicsFee + messageFee
totalFee = totalFee * Math.pow(10, 6)
logger.info('mosaicsFee:', mosaicsFee, 'messageFee', messageFee, 'totalFee', totalFee)
transactionEntity.fee = totalFee
// transactionEntity.fee = 1000000
logger.info('transactionEntity', transactionEntity)
// Serialize transfer transaction and announce
nem.model.transactions.send(common, transactionEntity, endpoint)
.then(function (res) {
logger.info("交易详情:", res)
//callback(null, res.transactionHash.data)
if (res && res.message == 'SUCCESS') callback(null, res.transactionHash.data)
else callback(new Error(res.message), null)
})
.catch(error => {
logger.error('交易失败:', error)
callback(error, null)
});
/*
// 通过接口实时获取mosaic信息,有levy属性的需要根据levy增加nem amount
nem.com.requests.namespace.mosaicDefinitions(endpoint, mosaicAttachment.mosaicId.namespaceId).then(function(res) {
// Look for the mosaic definition(s) we want in the request response
var neededDefinition = nem.utils.helpers.searchMosaicDefinitionArray(res.data, ["nem"]);
console.log('neededDefinition', neededDefinition)
// Get full name of mosaic to use as object key
var fullMosaicName = nem.utils.format.mosaicIdToName(mosaicAttachment.mosaicId);
// Check if the mosaic was found
if(undefined === neededDefinition[fullMosaicName]) return console.error("Mosaic not found !");
// Set eur mosaic definition into mosaicDefinitionMetaDataPair
mosaicDefinitionMetaDataPair[fullMosaicName] = {};
mosaicDefinitionMetaDataPair[fullMosaicName].mosaicDefinition = neededDefinition[fullMosaicName];
// Prepare the transfer transaction object
var transactionEntity = nem.model.transactions.prepare("mosaicTransferTransaction")(common, option.transferTransaction, mosaicDefinitionMetaDataPair, config.networkId);
transactionEntity.fee = 1000000
// Serialize transfer transaction and announce
nem.model.transactions.send(common, transactionEntity, endpoint)
.then(function (res) {
console.log("交易详情:", res)
callback(null, res)
})
.catch(error => {
console.log('交易失败:', error)
callback(error, null)
});
},
function(err) {
console.error(err);
});
*/
}
/**
- 获取账户余额
- /
const getNemBalance = (req, callback) => {
let { coin: name,
address
} = req.query
address = address ? address : config.adminAddress
let namespaceId, mosaicName
let balance = 0
if (name == ‘NEM’) { namespaceId = 'nem'
mosaicName = 'xem'
} else { if (!config.mosaicDefinitions[name]) return callback(new Error('不支持该mosaic'), null)
let mosaicData = config.mosaicDefinitions[name].mosaic.id
namespaceId = mosaicData.namespaceId
mosaicName = mosaicData.name
}
// http://192.3.61.243:7890/account/get?address=TCKUVV6PETWYVUBGTWRCLUROJOJVPY4JZXNRSDKT
// http://192.3.61.243:7890/account/mosaic/owned?address=TCKUVV6PETWYVUBGTWRCLUROJOJVPY4JZXNRSDKT
nem.com.requests.account.mosaics.owned(endpoint, address).then(result => { result.data.forEach(mosaic => {
if (mosaic.mosaicId.namespaceId == namespaceId && mosaic.mosaicId.name == mosaicName) balance = mosaic.quantity
})
logger.info('get mosaics owned', JSON.stringify(result))
callback(null, String(balance))
}).catch(error => { logger.error('get balance error', error)
callback(error, null)
})
}
module.exports = {
doNemTransaction: doNemTransaction,
getNemBalance: getNemBalance
}
进行测试
const nem = require('./nem')
// test nem transfer
const testNemTransfer = () => {
let req = {}
req.body = {
name: ‘NEM’,
address: ‘TCKUVV6PETWYVUBGTWRCLUROJOJVPY4JZXNRSDKT’,
value: 8729700
}
nem.doNemTransaction(req, (error, res) => {
console.log(‘doNemTransaction nem’, error, res)
})
}
// test mosaic transfer
const testMosaicTransfer = () => {
let req = {}
req.body = {
name: ‘NPXSXEM’,
address: ‘TCKUVV6PETWYVUBGTWRCLUROJOJVPY4JZXNRSDKT’,
value: 548250000
}
nem.doNemTransaction(req, (error, res) => {
console.log(‘doNemTransaction mosaic’, error, res)
})
}
// test get balance
const testGetBalance = () => {
let req = {}
req.query = {
/* coin: ‘NEM’,
address: ‘TCKUVV6PETWYVUBGTWRCLUROJOJVPY4JZXNRSDKT’ */
coin: ‘NPXSXEM’,
address: ‘TDV75PQWM2RYWVDM6JAFSPNXR44U63B3I4HQOR6A’
}
nem.getNemBalance(req, (error, res) => {
console.log(‘getNemBalance’, error, res)
})
}
testNemTransfer()
testMosaicTransfer()
testGetBalance()
其它
账户多签也是NEM的一个重要功能。直白说就是,一个钱包由多个人共同管理,只有超多一半的人签名同意后才可以转账,多签可以将多个账户分散存储,加强资产的安全性。多签的实现和转账流程与上述的略有差别,感兴趣的可以自行研究。
NEM的对开发者来说算是比较友好,完善的文档(细节可以更加完善),多语言的SDK。缺点就是基础设施有点差,比如移动端的钱包,比较鸡肋。而且中文社区较少,中文的开发资料非常少。
NEM(新经币)公链对接