在使用Node.js和NoSQL開發比特幣加密貨幣應用程式(上)中,我們建立了HD錢包,它可以為給定的種子生成無限量的金鑰,每個金鑰代表一個使用者錢包。我們將根據主種子建立每個包含錢包的使用者帳戶。下面我們接著來看如何進行交易、查詢餘額等重要功能如何實現。
我們要在這裡改變一下。到目前為止,我們已經在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和Couchbase作為NoSQL資料庫來構建自己的加密貨幣交易。我們涵蓋了很多,從生成HD錢包到建立具有複雜資料庫邏輯的端點。
我不能強調這一點。我是加密貨幣愛好者,在金融領域沒有真正的經驗。我分享的東西應該有效,但可以做得更好。不要忘記加密金鑰並確保種子安全。測試你的工作,知道自己正在做什麼。
如果你想下載此專案,請在GitHub上檢視。如果你想分享關於該主題的見解,經驗等,請在評論中分享。社群可以努力創造偉大的東西!
如果你是Golang的粉絲,我在之前的教程中建立了一個類似的專案。
======================================================================
分享一些以太坊、EOS、比特幣等區塊鏈相關的互動式線上程式設計實戰教程:
- java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
- EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- tendermint區塊鏈開發詳解,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI介面、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操程式碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裡是原文使用Node.js和NoSQL開發比特幣加密貨幣應用程式