Node.js和NoSQL開發比特幣加密貨幣應用程序(下)

使用Node.js和NoSQL開發比特幣加密貨幣應用程序(上)中,咱們建立了HD錢包,它能夠爲給定的種子生成無限量的密鑰,每一個密鑰表明一個用戶錢包。咱們將根據主種子建立每一個包含錢包的用戶賬戶。下面咱們接着來看如何進行交易、查詢餘額等重要功能如何實現。php

咱們要在這裏改變一下。到目前爲止,咱們已經在NoSQL數據庫中完成了面向賬戶的操做。另外一個重要方面是交易。例如,也許用戶X爲BTC存入一些美圓貨幣,而用戶Y進行提款。咱們須要存儲和查詢該交易信息。java

API端點函數將保存交易數據,但咱們仍然能夠查詢它。node

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 });
        });
    });
}
複製代碼

給定一個賬戶,咱們但願得到特定用戶的賬戶餘額。python

等一下,讓咱們退後一步,由於咱們不是已經建立了一些賬戶餘額功能嗎?從技術上講,咱們作了,但這些功能用於檢查錢包餘額,而不是賬戶餘額。android

這是個人一些經驗變成灰色區域的地方。每次發送比特幣時,都會收取費用,有時費用至關昂貴。當你存款時,將錢轉入你的錢包並不符合成本效益,由於這將收取礦工費。而後你將被收取撤回甚至轉帳的費用。那時你已經失去了大部分的比特幣。git

相反,我認爲交易全部一個相似於證券交易所貨幣市場帳戶的持有帳戶。你的賬戶中應該有資金的記錄,但從技術上講,它不在錢包中。若是你想要轉帳,則須要從應用程序地址而不是你的用戶地址進行轉帳。當你退出時,它只是被減去。程序員

再說一次,我不知道這是否真的如何運做,但這就是我爲了不各處收費而採起的方式。github

回到咱們的getAccountBalance函數。咱們正在處理每筆交易的總和。存款具備正值,而轉帳和取款具備負值。將這些信息彙總在一塊兒能夠爲你提供準確的數字,不包括你的錢包餘額。稍後咱們將得到一個錢包餘額賬戶。golang

鑑於咱們對賬戶餘額知之甚少,咱們能夠嘗試從錢包中建立一個交易:web

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開發比特幣加密貨幣應用程序

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息