怎么用Node.js和NoSQL开发加密货币应用程序
这篇文章主要介绍"怎么用Node.js和NoSQL开发加密货币应用程序",在日常操作中,相信很多人在怎么用Node.js和NoSQL开发加密货币应用程序问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"怎么用Node.js和NoSQL开发加密货币应用程序"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
我们要在这里改变一下。到目前为止,我们已经在NoSQL数据库中完成了面向帐户的操作。另一个重要方面是交易。例如,也许用户X为BTC存入一些美元货币,而用户Y进行提款。我们需要存储和查询该交易信息。
API端点函数将保存交易数据,但我们仍然可以查询它。
getAccountBalance(account) { var statement = "SELECT SUM(tx.satoshis) AS balance FROM " + this.bucket._name + " AS tx WHERE tx.type = 'transaction' AND tx.account = $account"; var query = Couchbase.N1qlQuery.fromString(statement); return new Promise((resolve, reject) => { this.bucket.query(query, { "account": account }, (error, result) => { if(error) { reject({ "code": error.code, "message": error.message }); } resolve({ "balance": result[0].balance }); }); });}
给定一个帐户,我们希望获得特定用户的帐户余额。
等一下,让我们退后一步,因为我们不是已经创建了一些帐户余额功能吗?从技术上讲,我们做了,但这些功能用于检查钱包余额,而不是帐户余额。
这是我的一些经验变成灰色区域的地方。每次发送比特币时,都会收取费用,有时费用相当昂贵。当你存款时,将钱转入你的钱包并不符合成本效益,因为这将收取矿工费。然后你将被收取撤回甚至转账的费用。那时你已经失去了大部分的比特币。
相反,我认为交易所有一个类似于证券交易所货币市场账户的持有账户。你的帐户中应该有资金的记录,但从技术上讲,它不在钱包中。如果你想要转账,则需要从应用程序地址而不是你的用户地址进行转账。当你退出时,它只是被减去。
再说一次,我不知道这是否真的如何运作,但这就是我为了避免各处收费而采取的方式。
回到我们的getAccountBalance
函数。我们正在处理每笔交易的总和。存款具有正值,而转账和取款具有负值。将这些信息汇总在一起可以为你提供准确的数字,不包括你的钱包余额。稍后我们将获得一个钱包余额帐户。
鉴于我们对帐户余额知之甚少,我们可以尝试从钱包中创建一个交易:
createTransactionFromAccount(account, source, destination, amount) { return new Promise((resolve, reject) => { this.getAddressBalance(source).then(sourceAddress => { if(sourceAddress.balanceSat < amount) { return reject({ "message": "Not enough funds in account." }); } this.getPrivateKeyFromAddress(account, source).then(keypair => { this.getAddressUtxo(source).then(utxo => { var transaction = new Bitcore.Transaction(); for(var i = 0; i < utxo.length; i++) { transaction.from(utxo[i]); } transaction.to(destination, amount); this.addAddress(account).then(change => { transaction.change(change.address); transaction.sign(keypair.secret); resolve(transaction); }, error => reject(error)); }, error => reject(error)); }, error => reject(error)); }, error => reject(error)); });}
如果提供了源地址,目的地地址和金额,我们可以创建并签署一个交易,以便稍后在比特币网络上广播。
首先,我们得到有问题的源地址的余额。我们需要确保它有足够的UTXO来满足发送量预期。请注意,在此示例中,我们正在执行单个地址交易。如果你想变得复杂,可以在单个交易中从多个地址发送。我们不会在这里这样做。如果我们的单个地址有足够的资金,我们会获得它的私钥和UTXO数据。使用UTXO数据,我们可以创建比特币交易,应用目的地地址和更改地址,然后使用我们的私钥对交易进行签名。可以广播响应。
同样地,假设我们想从我们的持有账户转账比特币:
createTransactionFromMaster(account, destination, amount) { return new Promise((resolve, reject) => { this.getAccountBalance(account).then(accountBalance => { if(accountBalance.balance < amount) { reject({ "message": "Not enough funds in account." }); } var mKeyPairs = this.getMasterKeyPairs(); var masterAddresses = mKeyPairs.map(a => a.address); this.getMasterAddressWithMinimum(masterAddresses, amount).then(funds => { this.getAddressUtxo(funds.address).then(utxo => { var transaction = new Bitcore.Transaction(); for(var i = 0; i < utxo.length; i++) { transaction.from(utxo[i]); } transaction.to(destination, amount); var change = helper.getMasterChangeAddress(); transaction.change(change.address); for(var j = 0; j < mKeyPairs.length; j ++) { if(mKeyPairs[j].address == funds.address) { transaction.sign(mKeyPairs[j].secret); } } var tx = { account: account, satoshis: (amount * -1), timestamp: (new Date()).getTime(), status: "transfer", type: "transaction" }; this.insert(tx).then(result => { resolve(transaction); }, error => reject(error)); }, error => reject(error)); }, error => reject(error)); }, error => reject(error)); });}
我们假设我们的交换地址装满了疯狂的比特币以满足需求。
第一步是确保我们的持有账户中有资金。我们可以执行总结每个交易的查询以获得有效数字。如果我们有足够的,我们可以获得所有10个主密钥对和地址。我们需要检查哪个地址有足够的资金发送。请记住,这里的单一地址交易可能会有更多。
如果地址有足够的资金,我们会获得UTXO数据并开始进行交易。这次代替我们的钱包作为源地址,我们使用交换的钱包。在我们获得签名交易之后,我们想在数据库中创建一个交易来减去我们正在传输的值。
在我们进入API端点之前,我想重新尝试一些事情:
我假设热门的交易所有一个持有账户,以避免对钱包地址征收费用。
我们在此示例中使用单地址交易,而不是聚合我们拥有的内容。
我应该是在加密帐户文档中的关键数据。
我没有广播任何交易,只创建它们。
现在让我们关注我们的API端点,这是一个简单的部分。
使用Express Framework设计RESTful API端点
请记住,正如我们在开始时配置的那样,我们的端点将分为三个文件,这些文件充当分组。我们将从最小和最简单的端点组开始,这些端点比其他任何端点都更实用。
打开项目的routes/utility.js
文件并包含以下内容:
const Bitcore = require("bitcore-lib");const Mnemonic = require("bitcore-mnemonic");module.exports = (app) => { app.get("/mnemonic", (request, response) => { response.send({ "mnemonic": (new Mnemonic(Mnemonic.Words.ENGLISH)).toString() }); }); app.get("/balance/value", (request, response) => { Request("https://api.coinmarketcap.com/v1/ticker/bitcoin/").then(market => { response.send({ "value": "$" + (JSON.parse(market)[0].price_usd * request.query.balance).toFixed(2) }); }, error => { response.status(500).send(error); }); });}
这里我们有两个端点,一个用于生成助记符种子,另一个用于获取比特币余额的法定值。这两者都不是真正必要的,但是在第一次启动时,生成种子值以便稍后保存在我们的配置文件中可能会很好。
现在打开项目的routes/account.js
文件,以便我们处理帐户信息:
const Request = require("request-promise");const Joi = require("joi");const helper = require("../app").helper;module.exports = (app) => { app.post("/account", (request, response) => { }); app.put("/account/address/:id", (request, response) => { }); app.get("/account/addresses/:id", (request, response) => { }); app.get("/addresses", (request, response) => { }); app.get("/account/balance/:id", (request, response) => { }); app.get("/address/balance/:id", (request, response) => { });}
请注意,我们正在从尚未启动的app.js
文件中提取helper程序类。现在就跟它一起使用它以后会有意义,虽然它没什么特别的。
在创建帐户时,我们有以下内容:
app.post("/account", (request, response) => { var model = Joi.object().keys({ firstname: Joi.string().required(), lastname: Joi.string().required(), type: Joi.string().forbidden().default("account") }); Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => { if(error) { return response.status(500).send(error); } helper.createAccount(value).then(result => { response.send(value); }, error => { response.status(500).send(error); }); });});
使用Joi
我们可以验证请求正文并在错误时抛出错误。假设请求正文是正确的,我们可以调用createAccount
函数在数据库中保存一个新帐户。
创建帐户后,我们可以添加一些地址:
app.put("/account/address/:id", (request, response) => { helper.addAddress(request.params.id).then(result => { response.send(result); }, error => { return response.status(500).send(error); });});
使用发送的帐户ID
,我们可以调用我们的addAddress
函数来对我们的文档使用子文档操作。
还不错吧?
要获取特定帐户的所有地址,我们可能会有以下内容:
app.get("/account/addresses/:id", (request, response) => { helper.getAddresses(request.params.id).then(result => { response.send(result); }, error => { response.status(500).send(error); });});
或者,如果我们不提供id
,我们可以使用以下端点函数从所有帐户获取所有地址:
app.get("/addresses", (request, response) => { helper.getAddresses().then(result => { response.send(result); }, error => { response.status(500).send(error); });});
现在可能是最棘手的端点功能。假设我们希望获得帐户余额,其中包括持有帐户以及每个钱包地址。我们可以做到以下几点:
app.get("/account/balance/:id", (request, response) => { helper.getAddresses(request.params.id).then(addresses => helper.getWalletBalance(addresses)).then(balance => { helper.getAccountBalance(request.params.id).then(result => { response.send({ "balance": balance.balance + result.balance }); }, error => { response.status(500).send({ "code": error.code, "message": error.message }); }); }, error => { response.status(500).send({ "code": error.code, "message": error.message }); });});
以上将调用我们的两个函数来获得余额,并将结果加在一起以获得一个巨大的余额。
帐户端点不是特别有趣。创建交易更令人兴奋。
打开项目的routes/transaction.js
文件并包含以下内容:
const Request = require("request-promise");const Joi = require("joi");const Bitcore = require("bitcore-lib");const helper = require("../app").helper;module.exports = (app) => { app.post("/withdraw", (request, response) => { }); app.post("/deposit", (request, response) => { }); app.post("/transfer", (request, response) => { });}
我们有三种不同类型的交易。我们可以为比特币存入法定货币,为法定货币提取比特币,并将比特币转账到新的钱包地址。
我们来看看存款端点:
app.post("/deposit", (request, response) => { var model = Joi.object().keys({ usd: Joi.number().required(), id: Joi.string().required() }); Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => { if(error) { return response.status(500).send(error); } Request("https://api.coinmarketcap.com/v1/ticker/bitcoin/").then(market => { var btc = value.usd / JSON.parse(market)[0].price_usd; var transaction = { account: value.id, usd: value.usd, satoshis: Bitcore.Unit.fromBTC(btc).toSatoshis(), timestamp: (new Date()).getTime(), status: "deposit", type: "transaction" }; helper.insert(transaction).then(result => { response.send(result); }, error => { response.status(500).send(error); }); }, error => { response.status(500).send(error); }); });});
在我们验证输入后,我们使用CoinMarketCap
检查美元比特币的当前值。使用响应中的数据,我们可以根据存入的美元金额计算出应该获得多少比特币。
创建数据库交易后,我们可以保存它,因为它是一个正数,它将在查询时返回正余额。
现在让我们说我们想从比特币中提取资金:
app.post("/withdraw", (request, response) => { var model = Joi.object().keys({ satoshis: Joi.number().required(), id: Joi.string().required() }); Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => { if(error) { return response.status(500).send(error); } helper.getAccountBalance(value.id).then(result => { if(result.balance == null || (result.balance - value.satoshis) < 0) { return response.status(500).send({ "message": "There are not `" + value.satoshis + "` satoshis available for withdrawal" }); } Request("https://api.coinmarketcap.com/v1/ticker/bitcoin/").then(market => { var usd = (Bitcore.Unit.fromSatoshis(value.satoshis).toBTC() * JSON.parse(market)[0].price_usd).toFixed(2); var transaction = { account: value.id, satoshis: (value.satoshis * -1), usd: parseFloat(usd), timestamp: (new Date()).getTime(), status: "withdrawal", type: "transaction" }; helper.insert(transaction).then(result => { response.send(result); }, error => { response.status(500).send(error); }); }, error => { response.status(500).send(error); }); }, error => { return response.status(500).send(error); }); });});
类似的事件正在这里发生。在验证请求主体后,我们获得帐户余额并确保我们提取的金额小于或等于我们的余额。如果是,我们可以根据CoinMarketCap
的当前价格进行另一次交易。我们将使用负值创建一个交易并将其保存到数据库中。
在这两种情况下,我们都依赖于CoinMarketCap
,它在过去一直存在负面争议。你可能希望为交易选择不同的资源。
最后,我们有转账:
app.post("/transfer", (request, response) => { var model = Joi.object().keys({ amount: Joi.number().required(), sourceaddress: Joi.string().optional(), destinationaddress: Joi.string().required(), id: Joi.string().required() }); Joi.validate(request.body, model, { stripUnknown: true }, (error, value) => { if(error) { return response.status(500).send(error); } if(value.sourceaddress) { helper.createTransactionFromAccount(value.id, value.sourceaddress, value.destinationaddress, value.amount).then(result => { response.send(result); }, error => { response.status(500).send(error); }); } else { helper.createTransactionFromMaster(value.id, value.destinationaddress, value.amount).then(result => { response.send(result); }, error => { response.status(500).send(error); }); } });});
如果请求包含源地址,我们将从我们自己的钱包转账,否则我们将从交换管理的钱包转账。
所有这些都基于我们之前创建的功能。
通过端点,我们可以专注于引导我们的应用程序并得出结论。
引导Express Framework应用程序
现在我们有两个文件保持不受示例的影响。我们还没有添加配置或驱动逻辑来引导我们的端点。
打开项目的config.json
文件,并包含以下内容:
{ "mnemonic": "manage inspire agent october potato thought hospital trim shoulder round tired kangaroo", "host": "localhost", "bucket": "bitbase", "username": "bitbase", "password": "123456"}
记住这个文件非常敏感。考虑将其锁定或甚至使用不同的方法。如果种子被暴露,则可以毫不费力地获得所有用户帐户和交换帐户的每个私钥。
现在打开项目的app.js
文件并包含以下内容:
const Express = require("express");const BodyParser = require("body-parser");const Bitcore = require("bitcore-lib");const Mnemonic = require("bitcore-mnemonic");const Config = require("./config");const Helper = require("./classes/helper");var app = Express();app.use(BodyParser.json());app.use(BodyParser.urlencoded({ extended: true }));var mnemonic = new Mnemonic(Config.mnemonic);var master = new Bitcore.HDPrivateKey(mnemonic.toHDPrivateKey());module.exports.helper = new Helper(Config.host, Config.bucket, Config.username, Config.password, master);require("./routes/account.js")(app);require("./routes/transaction.js")(app);require("./routes/utility.js")(app);var server = app.listen(3000, () => { console.log("Listening at :" + server.address().port + "...");});
我们正在做的是初始化Express
,加载配置信息以及链接我们的路由。module.exports.helper
变量是我们的单例,将在每个其他JavaScript文件中使用。
到此,关于"怎么用Node.js和NoSQL开发加密货币应用程序"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!