Application example

This article uses a simple development example of the exchange withdrawal function, including the preparation of the development environment and the deployment of BFChain, so that developers can easily master how to develop their own DApp on the BFChain network.

Development preparation

The development examples in this article are developed using TypeScript. In order to help developers get started quickly, some necessary tools need to be prepared before development.

1.Nodejs v12+

  • Select the version from Nodejs official website (https://nodejs.org/) to install
  • iew Nodejs version node -v v12.16.2

2.TypeScript

  • Install Typescript npm install -g typescript

  • View Typescript version tsc -v

3.Vscode

Node installation and deployment

Developers can download the node installation program through the BFChain official website (http://www.bfchain.com), and start the local node after the installation is complete.

Specific example

Statement of needs

In mainstream exchanges, user deposit and withdrawal is a very common function. It includes the following functions:

  • Import SDK
  • Check account balance
  • User withdrawal (transfer)

The functions are shown in the figure:
Exchange Withdrawal

The DB structure of the account

Step1: Initialize SDK

In order to ensure the security of the data and the private key not to leak, the exchange must start the BFChain node locally, and generate transaction data through the local node to perform user withdrawal (transfer) functions.

  • The node address defaults to the local node (127.0.0.1)
  • Port: the official environment is 9003, the test environment is 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
        );
    }
    /**
    * Get the latest block of the local node
    */
    async getLastBlock() {
        return this.__bfchainSdk.api.basic.getLastBlock();
    }
   /**
     * Get account balance
     * @param:address: account address
     * @param:assetType: asset type
     */
    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: Initialize the data structure

In order to simplify the data model, in this example, we only use the two data models of Account and Transaction. Take MongoDB as an example.

  • Account data model (Account)
import {Schema} from "mongoose"
const accountSchema = new Schema({
    accountId: {type: String, require: true }, // account ID
    address: {type: String, require: true }, // account address
    coin: {type: String, require: true }, // currency
    balance: {type: String, require: true} // balance
});
export const account = mongoose.model("account", accountSchema)
  • Transaction data model (Transaction)
const transactionSchema = new Schema({
    tranId: {type: String, require: true }, // transaction ID
    amount: {type: Number, require: true }, // transaction amount
    tranType: {type: String, require: true }, // transaction type
    senderId: {type: String, require: true }, // sender ID
    recipientId: {type: String, require: true }, // recipient ID
    signature: {type: String, require: true} // transaction signature
});
export const transaction = mongoose.model("tansaction", transactionSchema)

Step3: Generate withdrawal transaction class

export class Withdraw {
    private _withDrawAmount: number; // Withdraw cash amount
    private _address: string; // account address
    private _withDrawAddress: string; // Withdrawal address
    private _secret: string; // Withdraw private key
    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"
        });
        // The account balance is greater than the amount of cash to be withdrawn
        if (accountInfos.balance <this._withDrawAmount) {
            throw new Error("Insufficient account balance")
        }
        const chainbalance = this.bfchainSdk.getAddressBalance(this._address);
        // The balance on the chain is greater than the amount of cash to be withdrawn
        if (chainbalance <this._withDrawAmount) {
            throw new Error("The balance of the address on the chain is insufficient")
        }
        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
        }
        // Call the transfer interface
        const trTranferAsset = await this.bfchainSdk.api.transaction.sendTransferAsset(data);
        // The transfer is successful, record the data in the transaction table
        if (trTranferAsset && trTranferAsset.success === true) {
            // Generate withdrawal transaction table data
            transaction.insertOne({
                tranId: uuid.v4(),
                amount: this._withDrawAmount, // Withdraw cash amount
                tranType: "withdraw", // Withdraw cash
                senderId: this._address, // sender address of withdrawal
                recipientId: this._withDrawAddress, // Withdrawal address
                signature: trTranferAsset.result.signature // transaction signature
            })
        }
    }
}

Step4: Make a withdrawal transaction

(async () => {
    const withdraw = new Withdraw(20000,"Account Address","Withdrawal Address","Account Address Private Key");
    await withdraw.exec();
})().catch(err => {
    console.error(err);
});