应用示例

本文通过一个简单的交易所提现功能的开发示例,包括开发环境的准备和BFChain的部署,使开发者能够轻松掌握如何在BFChain网络上开发自己的DApp

开发准备

本文的开发示例使用TypeScript进行开发,为了帮助开发者迅速上手,开发之前需要准备一些必备工具。

1.Nodejs v12+

2.TypeScript

  • 安装Typescript npm install -g typescript

  • 查看Typescript版本 tsc -v

3.Vscode

节点的安装和部署

开发人员可以通过BFChain官网(http://www.bfchain.com)下载节点的安装程序,进行安装完成之后启动本地节点。

具体示例

需求说明

在主流的交易所中,用户充值提现是一个很常见的功能。 它包括以下几个功能:

  • 导入SDK
  • 查询账户余额
  • 用户提现(转账)

功能展示如图:
交易所提现

账户的DB结构

Step1:初始化SDK

为了保证数据的安全性和私钥不泄漏,交易所必须在本地启动BFChain节点,通过本地节点生成交易数据进行用户提现(转账)功能。

  • 节点地址默认为本地节点(127.0.0.1)
  • 端口:正式环境为9003,测试环境为19003
import { Sdk } from "@bfchain/pc-sdk-core";
export class BFChainPcSdk {
    private __bfchainSdk: Sdk;
    constructor() {
        this.__bfchainSdk = new Sdk({
            ip: "127.0.0.1",
            port: "9003",
            requestTimeOut: 10000,
            requestProtocol: "websocket"
        }, 9999
        );
    }
    /**
    *   获取本地节点当前最新区块
    */
    async getLastBlock() {
        return this.__bfchainSdk.api.basic.getLastBlock();
    }
    /**
    *   获得账户余额
    *   @param:address:账户地址
    *   @param:assetType:资产类型
    */
    async getAddressBalance(address: string, assetType?: string) {
        if (!assetType) assetType = "BFT";
        const accountAsset = await this.__bfchainSdk.api.basic.getAccountLastTransaction({
            address: address,
            assetType: assetType
        })
        if (accountAsset && accountAsset.success === true) {
            return this.getAssetBalance(address, accountAsset.result.transactionInBlock)
        } else {
            return "0";
        }
    }
    async getAssetBalance(address: string, tr) {
        const assetChange = tr.transactionAssetChanges;
        if (assetChange && assetChange.length > 0) {
            const isSender = address === tr.transaction.senderId;
            const isRecipient = address === tr.transaction.recipientId;
            let assetType = "";
            if(tr.transaction && tr.transaction.storageKey === "assetType" && tr.transaction.storageKey){
                assetType = tr.transaction.storageKey
            };
            for(const change of assetChange){
                if((isSender && change.accountType ===0) || (isRecipient && change.accountType === 1)){
                    if(!assetType){
                        return change.assetBalance;
                    }
                    if(assetType !== "BFT" && change.assetTypes === 0){
                        return change.assetBalance;
                    }
                }
            }
        }
        return "0"
    }
    get bfchainSdk() {
        return this.__bfchainSdk;
    }
}

Step2: 初始化数据结构

为了简化数据模型,在这个实例中,我们只使用Account和Transaction这2个数据模型。以MongoDB为例。

  • 账户数据模型(Account)
import { Schema } from "mongoose"
const accountSchema = new Schema({
    accountId: { type: String, require: true },     // 账户ID
    address: { type: String, require: true },       // 账户地址
    coin: { type: String, require: true },          // 币种
    balance: { type: String, require: true }        // 余额
});
export const account = mongoose.model("account", accountSchema)
  • 交易数据模型(Transaction)
const transactionSchema = new Schema({
    tranId: { type: String, require: true },         //  交易ID
    amount: { type: Number, require: true },         //  交易金额
    tranType: { type: String, require: true },       //  交易类型
    senderId: { type: String, require: true },       //  发送者ID
    recipientId: { type: String, require: true },    //  接受者ID
    signature: { type: String, require: true }       //  交易签名
});
export const transaction = mongoose.model("tansaction", transactionSchema)

Step3: 生成提现交易类

export class Withdraw {
    private _withDrawAmount: number;    //  提现金额
    private _address: string;           //  账户地址
    private _withDrawAddress: string;   //  提现地址
    private _secret: string;            //  提现私钥
    private bfchainSdk: Sdk = new BFChainPcSdk().bfchainSdk;
    constructor(amount: number, address:string, withDrawAddress: string, secret: string) {
        this._withDrawAmount = amount;
        this._address = address;
        this._withDrawAddress = withDrawAddress;
        this._secret = secret;
    }
    async exec() {
        const accountInfos = account.find({
            address: this._address,
            coin: "BFT"
        });
        // 账户余额大于要提现金额
        if (accountInfos.balance < this._withDrawAmount) {
            throw new Error("账户余额不足")
        }
        const chainbalance = this.bfchainSdk.getAddressBalance(this._address);
        // 链上余额大于要提现金额
        if (chainbalance < this._withDrawAmount) {
            throw new Error("链上地址余额不足")
        }
        const lastBlock = this.bfchainSdk.getLastBlock();
        const height = lastBlock.result.height ? lastBlock.result.height : 1;
        const data = {
            secret: this._secret,
            amount: this._withDrawAmount.toString(),
            fee: 100,
            assetType: "BFT",
            recipientId: this._withDrawAddress,
            height: height
        }
        // 调用转账接口
        const trTranferAsset = await this.bfchainSdk.api.transaction.sendTransferAsset(data);
        // 转账成功,在交易表中记录数据
        if (trTranferAsset && trTranferAsset.success === true) {
            // 生成提现交易表数据
            transaction.insertOne({
                tranId: uuid.v4(),
                amount: this._withDrawAmount,       // 提现金额
                tranType: "withdraw",               // 提现
                senderId: this._address,            // 提现的发送者地址
                recipientId: this._withDrawAddress, // 提现的地址
                signature: trTranferAsset.result.signature  // 交易签名
            })
        }
    }
}

Step4: 进行提现交易

(async () => {
    const withdraw = new Withdraw(20000,"账户地址","提现地址","账户地址私钥");
    await withdraw.exec();
})().catch(err => {
    console.error(err);
});