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比较全面,相当良心:

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

测试网
http://bob.nem.ninja:8765

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 &amp;&amp; res.message == 'SUCCESS') callback(null, res)
          else callback(new Error(res.message), null)
      })
      .catch(error =&gt; {
          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 &amp;&amp; res.message == 'SUCCESS') callback(null, res.transactionHash.data)
        else callback(new Error(res.message), null)
    })
    .catch(error =&gt; {
        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 =&gt; {
        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 =&gt; {
          if (mosaic.mosaicId.namespaceId == namespaceId &amp;&amp; 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。缺点就是基础设施有点差,比如移动端的钱包,比较鸡肋。而且中文社区较少,中文的开发资料非常少。

Author

Ludis

Posted on

2018-09-06

Updated on

2018-09-06

Licensed under

Comments