diff --git a/README.md b/README.md index ba625af..7260103 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Backend service is configured using environment variables that need to be set be The command for running the service is: ```shell -node src/server.js +node dist/server.js ``` ### Frontend Application diff --git a/service/package.json b/service/package.json index 50dd1f1..7ea73b0 100644 --- a/service/package.json +++ b/service/package.json @@ -2,11 +2,13 @@ "name": "bgl-wbgl-bridge-service", "version": "0.3.1", "description": "BGL-WBGL(ETH) bridge", - "main": "src/server.js", + "main": "dist/server.js", "license": "MIT", "type": "module", "scripts": { - "dev": "nodemon -L --inspect=0.0.0.0:9229 src/server.js", + "dev": "nodemon -L --inspect=0.0.0.0:9229 dist/server.js", + "build": "tsc -p tsconfig.buid.json", + "prepare": "npm run build", "test": "mocha src/tests/**/*.js -r dotenv/config --timeout 10000", "lint:check": "eslint .", "lint:fix": "eslint --fix", @@ -25,6 +27,9 @@ "web3": "^1.3.6" }, "devDependencies": { + "@types/cors": "^2.8.12", + "@types/express": "^4.17.13", + "@types/node": "^18.7.15", "eslint": "^8.12.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.0.0", @@ -32,7 +37,9 @@ "nodemon": "^2.0.7", "pre-commit": "^1.2.2", "prettier": "^2.6.2", - "should": "^13.2.3" + "should": "^13.2.3", + "ts-node": "^10.9.1", + "typescript": "^4.8.2" }, "options": { "mocha": "--timeout 20000 --recursive --require should" diff --git a/service/src/app.js b/service/src/app.ts similarity index 82% rename from service/src/app.js rename to service/src/app.ts index fbde467..b77eef2 100644 --- a/service/src/app.js +++ b/service/src/app.ts @@ -1,18 +1,18 @@ -import express from "express"; +import express, {Request, Response} from "express"; import cors from "cors"; -import { port } from "./utils/config.js"; +import { port } from "./utils/config"; import { BalanceController, IndexController, SubmitController, -} from "./controllers/index.js"; +} from "./controllers"; const app = express(); app.set("port", port); app.use(cors()); app.use(express.json()); -app.use(function(req, res, next) { +app.use(function(req: Request, res: Response, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization"); res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); diff --git a/service/src/controllers/BalanceController.js b/service/src/controllers/BalanceController.js deleted file mode 100644 index a3a5329..0000000 --- a/service/src/controllers/BalanceController.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Bsc, Eth, RPC } from "../modules/index.js"; - -export const bgl = async (_req, res) => - res.json(Math.floor(await RPC.getBalance())); -export const eth = async (_req, res) => - res.json(parseInt(await Eth.getWBGLBalance())); -export const bsc = async (_req, res) => - res.json(parseInt(await Bsc.getWBGLBalance())); diff --git a/service/src/controllers/BalanceController.ts b/service/src/controllers/BalanceController.ts new file mode 100644 index 0000000..c837ac4 --- /dev/null +++ b/service/src/controllers/BalanceController.ts @@ -0,0 +1,9 @@ +import {Request, Response} from 'express' +import { Bsc, Eth, RPC } from "../modules"; + +export const bgl = async (_req: Request, res: Response) => + res.json(Math.floor(await RPC.getBalance())); +export const eth = async (_req: Request, res: Response) => + res.json(parseInt(await Eth.getWBGLBalance())); +export const bsc = async (_req: Request, res: Response) => + res.json(parseInt(await Bsc.getWBGLBalance())); diff --git a/service/src/controllers/IndexController.js b/service/src/controllers/IndexController.ts similarity index 80% rename from service/src/controllers/IndexController.js rename to service/src/controllers/IndexController.ts index 786a648..f054854 100644 --- a/service/src/controllers/IndexController.js +++ b/service/src/controllers/IndexController.ts @@ -1,13 +1,14 @@ -import { Db, RPC, Eth, Bsc } from "../modules/index.js"; +import {Request, Response} from "express" +import { Db, RPC, Eth, Bsc } from "../modules/index"; -export const healthCheck = async (_req, res) => { +export const healthCheck = async (_req: Request, res: Response) => { try { await RPC.getBalance(); if (!Db.isConnected()) { - res.json(500, { + res.status(500).json({ status: "error", message: "Database connection not available", - }); + }) } res.json({ status: "ok", @@ -18,11 +19,11 @@ export const healthCheck = async (_req, res) => { } }; -export const state = async (_req, res) => { +export const state = async (_req: Request, res: Response) => { try { const blockchainInfo = await RPC.getBlockchainInfo(); if (!Db.isConnected()) { - res.json(500, { + res.status(500).json({ status: "error", message: "Database connection not available", }); diff --git a/service/src/controllers/SubmitController.js b/service/src/controllers/SubmitController.ts similarity index 91% rename from service/src/controllers/SubmitController.js rename to service/src/controllers/SubmitController.ts index a6b2242..4e6edbe 100644 --- a/service/src/controllers/SubmitController.js +++ b/service/src/controllers/SubmitController.ts @@ -1,9 +1,10 @@ -import Transfer from "../models/Transfer.js"; -import { RPC, Eth, Bsc } from "../modules/index.js"; -import { bsc, eth, feePercentage } from "../utils/config.js"; -import { isValidBglAddress, isValidEthAddress, sha3 } from "../utils/index.js"; +import {Request, Response} from "express"; +import Transfer from "../models/Transfer"; +import { RPC, Eth, Bsc } from "../modules"; +import { bsc, eth, feePercentage } from "../utils/config"; +import { isValidBglAddress, isValidEthAddress, sha3 } from "../utils/index"; -export const bglToWbgl = async (req, res) => { +export const bglToWbgl = async (req: Request, res: Response) => { const data = req.body; if (!data.hasOwnProperty("address") || !isValidEthAddress(data.address)) { res.status(400).json({ @@ -52,7 +53,7 @@ export const bglToWbgl = async (req, res) => { } }; -export const wbglToBgl = async (req, res) => { +export const wbglToBgl = async (req: Request, res: Response) => { const data = req.body; if ( !data.hasOwnProperty("ethAddress") || diff --git a/service/src/controllers/index.js b/service/src/controllers/index.js deleted file mode 100644 index dad8623..0000000 --- a/service/src/controllers/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as IndexController from "./IndexController.js"; -import * as BalanceController from "./BalanceController.js"; -import * as SubmitController from "./SubmitController.js"; - -export { IndexController, BalanceController, SubmitController }; diff --git a/service/src/controllers/index.ts b/service/src/controllers/index.ts new file mode 100644 index 0000000..fd08caa --- /dev/null +++ b/service/src/controllers/index.ts @@ -0,0 +1,5 @@ +import * as IndexController from "./IndexController"; +import * as BalanceController from "./BalanceController"; +import * as SubmitController from "./SubmitController"; + +export { IndexController, BalanceController, SubmitController }; diff --git a/service/src/models/Conversion.js b/service/src/models/Conversion.ts similarity index 58% rename from service/src/models/Conversion.js rename to service/src/models/Conversion.ts index 7e7909f..639ec51 100644 --- a/service/src/models/Conversion.js +++ b/service/src/models/Conversion.ts @@ -1,4 +1,23 @@ -import mongoose from "mongoose"; +import mongoose, { Document } from "mongoose"; +import {Chain} from "../types" + +interface IConversion { + type: string, + chain: Chain, + transfer: string, + transaction: string, + address: string, + amount: string, + sendAmount: number, + txid: string, + nonce: number, + receipt: Object, + returnTxid: string, + status: string, + txChecks: number, +} + +export type ConversionModelType = IConversion & Document const schema = new mongoose.Schema( { @@ -27,4 +46,4 @@ const schema = new mongoose.Schema( { timestamps: true }, ); -export default mongoose.model("Conversion", schema); +export default mongoose.model("Conversion", schema); diff --git a/service/src/models/Data.js b/service/src/models/Data.ts similarity index 100% rename from service/src/models/Data.js rename to service/src/models/Data.ts diff --git a/service/src/models/Transaction.js b/service/src/models/Transaction.ts similarity index 58% rename from service/src/models/Transaction.js rename to service/src/models/Transaction.ts index 6869ac3..5e62eba 100644 --- a/service/src/models/Transaction.js +++ b/service/src/models/Transaction.ts @@ -1,4 +1,22 @@ import mongoose from "mongoose"; +import { Chain } from "../types"; + +export interface ITransaction { + type: string, + chain: Chain, + id: string, + transfer: string, + address: string, + amount: number, + blockHash: string, + time: number +} + +export interface Tx extends ITransaction { + confirmations: number + txid: string, + category: string +} const schema = new mongoose.Schema( { @@ -18,4 +36,4 @@ const schema = new mongoose.Schema( { timestamps: true }, ); -export default mongoose.model("Transaction", schema); +export default mongoose.model("Transaction", schema); diff --git a/service/src/models/Transfer.js b/service/src/models/Transfer.ts similarity index 55% rename from service/src/models/Transfer.js rename to service/src/models/Transfer.ts index 4234bd9..03ce945 100644 --- a/service/src/models/Transfer.js +++ b/service/src/models/Transfer.ts @@ -1,4 +1,20 @@ import mongoose from "mongoose"; +import { Chain } from "../types"; + +enum ChainTypes { + bgl = "bgl", + wbgl = "wbgl", +} + +type ChainType = string | ChainTypes + +interface ITransfer { + id: string, + type: ChainType, + chain: Chain, + from: string, + to: string +} const schema = new mongoose.Schema( { @@ -11,4 +27,4 @@ const schema = new mongoose.Schema( { timestamps: true }, ); -export default mongoose.model("Transfer", schema); +export default mongoose.model("Transfer", schema); diff --git a/service/src/modules/bsc.js b/service/src/modules/bsc.ts similarity index 59% rename from service/src/modules/bsc.js rename to service/src/modules/bsc.ts index 36f1a7d..14c3bc0 100644 --- a/service/src/modules/bsc.js +++ b/service/src/modules/bsc.ts @@ -3,6 +3,21 @@ import { bsc, confirmations } from "../utils/config.js"; import Web3Base from "./web3.js"; class Bsc extends Web3Base { + private networkId!: number + private chainId!: number + + constructor( + endpoint: string, + id: string, + contractAddress: string, + custodialAccountAddress: string, + custodialAccountKey: Buffer, + nonceDataName: string, + confirmations: number, + ) { + super(endpoint, id, contractAddress, custodialAccountAddress,custodialAccountKey, nonceDataName, confirmations) + } + async getNetworkId() { if (!this.networkId) { this.networkId = await this.web3.eth.net.getId(); @@ -17,13 +32,14 @@ class Bsc extends Web3Base { return this.chainId; } + //@ts-ignore async transactionOpts() { const params = { name: "bnb", networkId: await this.getNetworkId(), chainId: await this.getChainId(), }; - const common = Common.default.forCustomChain( + const common = Common.forCustomChain( "mainnet", params, "petersburg", @@ -33,10 +49,10 @@ class Bsc extends Web3Base { } export default new Bsc( - bsc.endpoint, + bsc.endpoint as string, "bsc", - bsc.contract, - bsc.account, + bsc.contract as string, + bsc.account as string, bsc.key, "bscNonce", confirmations.bsc, diff --git a/service/src/modules/chores.js b/service/src/modules/chores.ts similarity index 86% rename from service/src/modules/chores.js rename to service/src/modules/chores.ts index 30de7d9..f9870b4 100644 --- a/service/src/modules/chores.js +++ b/service/src/modules/chores.ts @@ -1,23 +1,26 @@ -import { confirmations, feePercentage, bsc, nonces } from "../utils/config.js"; -import { Data, RPC, Eth, Bsc } from "./index.js"; -import Transaction from "../models/Transaction.js"; -import Transfer from "../models/Transfer.js"; -import Conversion from "../models/Conversion.js"; +import { confirmations, feePercentage, bsc, nonces } from "../utils/config"; +import { Data, RPC, Eth, Bsc } from "./index"; +import Transaction from "../models/Transaction"; +import Transfer from "../models/Transfer"; +import Conversion, { ConversionModelType } from "../models/Conversion"; +import { Tx } from "../models/Transaction"; let ethNonce = 0; let bscNonce = 0; -function setupNonce(){ +async function setupNonce(){ if ((ethNonce == 0) && (nonces.eth == 0)) { - ethNonce = Eth.getTransactionCount(); + const Nonce = await Eth.getTransactionCount(); + ethNonce = Nonce; } else if ((nonces.eth > 0) && (nonces.eth > ethNonce)) { ethNonce = nonces.eth; } if ((bscNonce == 0) && (nonces.bsc == 0)) { - bscNonce = Bsc.getTransactionCount(); + bscNonce = await Bsc.getTransactionCount(); } else if ((nonces.bsc > 0) && (nonces.bsc > bscNonce)) { bscNonce = nonces.bsc; } + return { ethNonce, bscNonce } } const expireDate = () => { @@ -26,10 +29,10 @@ const expireDate = () => { return expireDate.toISOString(); }; -const deductFee = (amount) => +const deductFee = (amount: number) => parseFloat((((100 - feePercentage) * amount) / 100).toFixed(3)); -async function returnBGL(conversion, address) { +async function returnBGL(conversion: ConversionModelType, address: string) { try { conversion.status = "returned"; await conversion.save(); @@ -45,13 +48,16 @@ async function returnBGL(conversion, address) { } } -async function returnWBGL(Chain, conversion, address) { + +async function returnWBGL(Chain: typeof Eth | typeof Bsc, conversion: ConversionModelType, address: string) { try { conversion.status = "returned"; await conversion.save(); + // @ts-ignore conversion.returnTxid = await Chain.sendWBGL( address, conversion.amount.toString(), + 0 ); await conversion.save(); } catch (e) { @@ -71,20 +77,19 @@ async function checkBglTransactions() { blockHash || undefined, confirmations.bgl, ); - setupNonce(); result.transactions .filter( - (tx) => + (tx: Tx) => tx.confirmations >= confirmations.bgl && tx.category === "receive", ) - .forEach((tx) => { + .forEach((tx: Tx) => { Transfer.findOne({ type: "bgl", from: tx.address, updatedAt: { $gte: expireDate() }, }) .exec() - .then(async (transfer) => { + .then(async (transfer: any) => { if ( transfer && !(await Transaction.findOne({ id: tx["txid"] }).exec()) @@ -99,7 +104,7 @@ async function checkBglTransactions() { transfer: transfer._id, address: fromAddress, amount: tx["amount"], - blockHash: tx["blockhash"], + blockHash: tx["blockHash"], time: new Date(tx["time"] * 1000), }); const amount = deductFee(tx["amount"]); @@ -113,6 +118,7 @@ async function checkBglTransactions() { sendAmount: amount, }); + // @ts-ignore if (amount > (await Chain.getWBGLBalance())) { console.log( `Insufficient WBGL balance, returning ${tx["amount"]} BGL to ${fromAddress}`, @@ -125,6 +131,7 @@ async function checkBglTransactions() { const AssignedNonce = transfer.chain === "bsc" ? Bsc : Eth; if (transfer.chain === "bsc") { bscNonce += 1; + // @ts-ignore conversion.txid = await Chain.sendWBGL( transfer.to, amount.toString(), @@ -132,6 +139,7 @@ async function checkBglTransactions() { ); } else { ethNonce += 1; + // @ts-ignore conversion.txid = await Chain.sendWBGL( transfer.to, amount.toString(), @@ -183,21 +191,23 @@ export async function checkWbglTransfers(Chain = Eth, prefix = "Eth") { console.log("fromQuery:", fromQuery); Transfer.findOne({ type: "wbgl", + // @ts-ignore chain: Chain.id, from: fromQuery, updatedAt: { $gte: expireDate() }, }) .exec() - .then(async (transfer) => { + .then(async (transfer: any) => { if ( transfer && !(await Transaction.findOne({ + // @ts-ignore chain: Chain.id, id: event.transactionHash, }).exec()) ) { const amount = Chain.convertWGBLBalance(event.returnValues.value); - const sendAmount = deductFee(amount); + const sendAmount = deductFee(parseFloat(amount)); const transaction = await Transaction.create({ type: "wbgl", chain: Chain.id, @@ -228,7 +238,7 @@ export async function checkWbglTransfers(Chain = Eth, prefix = "Eth") { } try { - conversion.txid = await RPC.send(transfer.to, sendAmount); + conversion.txid = await RPC.send(transfer.to, (sendAmount).toString()); conversion.status = "sent"; await conversion.save(); } catch (e) { @@ -253,13 +263,17 @@ export async function checkWbglTransfers(Chain = Eth, prefix = "Eth") { setTimeout(() => checkWbglTransfers(Chain, prefix), 60000); } -async function checkPendingConversions(Chain) { +// @ts-ignore +async function checkPendingConversions(Chain: typeof Eth | typeof Bsc) { + + // @ts-ignore const conversions = await Conversion.find({ chain: Chain.id, type: "wbgl", status: "pending", txid: { $exists: true }, }).exec(); + let blockNumber; console.log("checkPendingConversions:", conversions); try { @@ -288,6 +302,7 @@ async function checkPendingConversions(Chain) { export const init = async () => { await checkWbglTransfers(Eth, "Eth"); + // @ts-ignore: Inheritance vs subtyping await checkWbglTransfers(Bsc, "Bsc"); await checkBglTransactions(); diff --git a/service/src/modules/data.js b/service/src/modules/data.ts similarity index 61% rename from service/src/modules/data.js rename to service/src/modules/data.ts index e5a8a38..ddf10bc 100644 --- a/service/src/modules/data.js +++ b/service/src/modules/data.ts @@ -1,6 +1,6 @@ -import Data from "../models/Data.js"; +import Data from "../models/Data"; -export async function get(name, defaultValue = null) { +export async function get(name: string, defaultValue?: U) { const record = await Data.findOne({ name }).exec(); return record ? record["value"] @@ -9,6 +9,6 @@ export async function get(name, defaultValue = null) { : defaultValue; } -export async function set(name, value) { +export async function set(name: string, value: T) { await Data.updateOne({ name }, { value }, { upsert: true }); } diff --git a/service/src/modules/db.js b/service/src/modules/db.ts similarity index 86% rename from service/src/modules/db.js rename to service/src/modules/db.ts index fed4eb7..60f01a6 100644 --- a/service/src/modules/db.js +++ b/service/src/modules/db.ts @@ -1,11 +1,11 @@ import mongoose from "mongoose"; -import { mongo } from "../utils/config.js"; +import { mongo } from "../utils/config"; let dbConnected = false; export const init = async () => { try { - await mongoose.connect(mongo.url, { + await mongoose.connect(mongo.url as string, { dbName: mongo.database, useNewUrlParser: true, useUnifiedTopology: true, diff --git a/service/src/modules/eth.js b/service/src/modules/eth.js deleted file mode 100644 index 2a50dfb..0000000 --- a/service/src/modules/eth.js +++ /dev/null @@ -1,12 +0,0 @@ -import { confirmations, eth } from "../utils/config.js"; -import Web3Base from "./web3.js"; - -export default new Web3Base( - eth.endpoint, - "eth", - eth.contract, - eth.account, - eth.key, - "nonce", - confirmations.eth, -); diff --git a/service/src/modules/eth.ts b/service/src/modules/eth.ts new file mode 100644 index 0000000..b54201f --- /dev/null +++ b/service/src/modules/eth.ts @@ -0,0 +1,12 @@ +import { confirmations, eth } from "../utils/config"; +import Web3Base from "./web3"; + +export default new Web3Base( + eth.endpoint as string, + "eth", + eth.contract as string, + eth.account as string, + eth.key, + "nonce", + confirmations.eth, +); diff --git a/service/src/modules/index.js b/service/src/modules/index.js deleted file mode 100644 index 34744a7..0000000 --- a/service/src/modules/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import eth from "./eth.js"; -import bsc from "./bsc.js"; - -export * as RPC from "./rpc.js"; -export * as Db from "./db.js"; -export * as Data from "./data.js"; -export * as Chores from "./chores.js"; - -export const Eth = eth; -export const Bsc = bsc; diff --git a/service/src/modules/index.ts b/service/src/modules/index.ts new file mode 100644 index 0000000..a0442be --- /dev/null +++ b/service/src/modules/index.ts @@ -0,0 +1,10 @@ +import eth from "./eth"; +import bsc from "./bsc"; + +export * as RPC from "./rpc"; +export * as Db from "./db"; +export * as Data from "./data"; +export * as Chores from "./chores"; + +export const Eth = eth; +export const Bsc = bsc; diff --git a/service/src/modules/rpc.js b/service/src/modules/rpc.ts similarity index 78% rename from service/src/modules/rpc.js rename to service/src/modules/rpc.ts index 99d14f0..f00cdae 100644 --- a/service/src/modules/rpc.js +++ b/service/src/modules/rpc.ts @@ -1,9 +1,9 @@ import Client from "bitcoin-core"; import { rpc, confirmations } from "../utils/config.js"; -let client; export const getClient = () => { + let client; if (!client) { client = new Client(rpc); } @@ -18,17 +18,17 @@ export const getBlockCount = async () => export const getBalance = async () => await getClient().command("getbalance"); -export const validateAddress = async (address) => +export const validateAddress = async (address: string): Promise => (await getClient().command("validateaddress", address)).isvalid; export const createAddress = async () => await getClient().command("getnewaddress"); export const tips = async () => await getClient().getChainTips(); -export const generate = async (tip) => await getClient().generate(tip); +export const generate = async (tip: string) => await getClient().generate(tip); export const listSinceBlock = async ( - blockHash, + blockHash: string, confirmation = confirmations.bgl, ) => { return await getClient().command( @@ -38,12 +38,12 @@ export const listSinceBlock = async ( ); }; -export const getTransactionFromAddress = async (txid) => { +export const getTransactionFromAddress = async (txid: string) => { const rawTx = await getClient().command("getrawtransaction", txid, true); const vin = rawTx["vin"][0]; const txIn = await getClient().command("getrawtransaction", vin.txid, true); return txIn["vout"][vin["vout"]]["scriptPubKey"]["address"]; }; -export const send = async (address, amount) => +export const send = async (address: string, amount: string) => await getClient().command("sendtoaddress", address, amount); diff --git a/service/src/modules/web3.js b/service/src/modules/web3.ts similarity index 78% rename from service/src/modules/web3.js rename to service/src/modules/web3.ts index f91bd8a..fd33ef1 100644 --- a/service/src/modules/web3.js +++ b/service/src/modules/web3.ts @@ -1,25 +1,36 @@ import Web3 from "web3"; +import {Contract} from "web3-eth-contract" //import {Transaction} from 'ethereumjs-tx' -import pkg from "ethereumjs-tx"; -const { Transaction } = pkg; import fs from "fs"; -import { Data } from "../modules/index.js"; + import { toBaseUnit } from "../utils/index.js"; +import pkg from "ethereumjs-tx"; +const { Transaction } = pkg; const bn = Web3.utils.toBN; -const createProvider = (endpoint) => new Web3.providers.HttpProvider(endpoint); +const createProvider = (endpoint: string) => new Web3.providers.HttpProvider(endpoint); class Web3Base { decimals = 18; - + private contractAddress: string; + private custodialAccountKey: Buffer; + private nonceDataName: string; + private chain!: string + + public confirmations: number; + public web3: Web3; + public WBGL: Contract; + public custodialAccountAddress: string + public id: string; + constructor( - endpoint, - id, - contractAddress, - custodialAccountAddress, - custodialAccountKey, - nonceDataName, - confirmations, + endpoint: string, + id: string, + contractAddress: string, + custodialAccountAddress: string, + custodialAccountKey: Buffer, + nonceDataName: string, + confirmations: number, ) { this.id = id; this.contractAddress = contractAddress; @@ -35,7 +46,7 @@ class Web3Base { ); this.WBGL.methods["decimals"]() .call() - .then((decimals) => (this.decimals = decimals)); + .then((decimals: number) => (this.decimals = decimals)); this.init() .then(() => {}) @@ -56,7 +67,7 @@ class Web3Base { return this.web3.utils.fromWei(gasPrice, "Gwei"); } - async getEstimateGas(amount) { + async getEstimateGas(amount: string) { return await this.WBGL.methods["transfer"]( this.custodialAccountAddress, toBaseUnit(amount, this.decimals), @@ -69,13 +80,13 @@ class Web3Base { ); } - async getWBGLBalance1(address) { + async getWBGLBalance1(address: string) { return this.convertWGBLBalance( await this.WBGL.methods["balanceOf"](address).call(), ); } - convertWGBLBalance(number, resultDecimals = this.decimals) { + convertWGBLBalance(number: number, resultDecimals = this.decimals) { const balance = bn(number); const divisor = bn(10).pow(bn(this.decimals)); const beforeDec = balance.div(divisor).toString(); @@ -94,11 +105,11 @@ class Web3Base { ); } - async getTransactionReceipt(txid) { + async getTransactionReceipt(txid: string) { return await this.web3.eth.getTransactionReceipt(txid); } - sendWBGL(address, amount, nonce) { + sendWBGL(address: string, amount: string, nonce: number) { return new Promise(async (resolve, reject) => { const data = this.WBGL.methods["transfer"]( address, diff --git a/service/src/server.js b/service/src/server.ts similarity index 56% rename from service/src/server.js rename to service/src/server.ts index 16e12bc..ea14f06 100644 --- a/service/src/server.js +++ b/service/src/server.ts @@ -1,7 +1,7 @@ import http from "http"; -import app from "./app.js"; -import { port } from "./utils/config.js"; -import { Db, Chores } from "./modules/index.js"; +import app from "./app"; +import { port } from "./utils/config"; +import { Db, Chores } from "./modules/index"; const server = http.createServer(app); server.listen(parseInt(port), () => { @@ -14,5 +14,10 @@ process.on("uncaughtException", async (error) => { process.exit(1); }); -await Db.init(); -await Chores.init(); +const init = async() => { + await Db.init(); + await Chores.init(); +} + +init() + .catch(err => {process.exit(1)}) \ No newline at end of file diff --git a/service/src/types/Chains.ts b/service/src/types/Chains.ts new file mode 100644 index 0000000..ee284f5 --- /dev/null +++ b/service/src/types/Chains.ts @@ -0,0 +1,6 @@ +enum Chains { + eth = "eth", + bsc = "bsc", + } + +export type Chain = "eth" | "bsc" | Chains \ No newline at end of file diff --git a/service/src/types/environment.d.ts b/service/src/types/environment.d.ts new file mode 100644 index 0000000..7682fc8 --- /dev/null +++ b/service/src/types/environment.d.ts @@ -0,0 +1,25 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + RPC_HOST: string, + RPC_PORT: string, + RPC_USER: string, + RPC_PASSWORD: string, + RPC_WALLET: string, + ETH_ENDPOINT: string, + ETH_ACCOUNT: string, + ETH_PRIVKEY: string, + ETH_CONTRACT_ADDRESS: string, + BSC_ENDPOINT: string, + BSC_PRIVKEY: string, + BSC_ACCOUNT: string, + BSC_CONTRACT_ADDRESS: string, + DB_CONNECTION: string, + DB_DATABASE: string | "wbgl_bridge", + BGL_MIN_CONFIRMATIONS: string, + ETH_MIN_CONFIRMATIONS: string, + BSC_MIN_CONFIRMATIONS: string + + } + } +} \ No newline at end of file diff --git a/service/src/types/external.d.ts b/service/src/types/external.d.ts new file mode 100644 index 0000000..8ace4fc --- /dev/null +++ b/service/src/types/external.d.ts @@ -0,0 +1 @@ +declare module 'bitcoin-core' \ No newline at end of file diff --git a/service/src/types/index.ts b/service/src/types/index.ts new file mode 100644 index 0000000..13e2abb --- /dev/null +++ b/service/src/types/index.ts @@ -0,0 +1 @@ +export * from './Chains' \ No newline at end of file diff --git a/service/src/utils/config.js b/service/src/utils/config.ts similarity index 62% rename from service/src/utils/config.js rename to service/src/utils/config.ts index 33f8f94..0dc772b 100644 --- a/service/src/utils/config.js +++ b/service/src/utils/config.ts @@ -5,10 +5,19 @@ export const env = process.env.NODE_ENV || "development"; export const port = process.env.PORT || "8080"; -const rpcConfig = { +type RPCConfig = { + host: string, + port: string, + username?: string, + password?: string, + wallet?: string +} + +const rpcConfig: RPCConfig = { host: process.env.RPC_HOST || "localhost", port: process.env.RPC_PORT || "8332", }; + if (process.env.hasOwnProperty("RPC_USER") && process.env.RPC_USER) { rpcConfig.username = process.env.RPC_USER; } @@ -18,19 +27,20 @@ if (process.env.hasOwnProperty("RPC_PASSWORD") && process.env.RPC_PASSWORD) { if (process.env.hasOwnProperty("RPC_WALLET") && process.env.RPC_WALLET) { rpcConfig.wallet = process.env.RPC_WALLET; } + export const rpc = rpcConfig; export const eth = { endpoint: process.env.ETH_ENDPOINT, account: process.env.ETH_ACCOUNT, - key: Buffer.from(process.env.ETH_PRIVKEY, "hex"), + key: Buffer.from(process.env.ETH_PRIVKEY as string, "hex"), contract: process.env.ETH_CONTRACT_ADDRESS, }; export const bsc = { endpoint: process.env.BSC_ENDPOINT, account: process.env.BSC_ACCOUNT, - key: Buffer.from(process.env.BSC_PRIVKEY, "hex"), + key: Buffer.from(process.env.BSC_PRIVKEY as string, "hex"), contract: process.env.BSC_CONTRACT_ADDRESS, }; @@ -40,14 +50,14 @@ export const mongo = { }; export const confirmations = { - bgl: parseInt(process.env.BGL_MIN_CONFIRMATIONS) || 3, - eth: parseInt(process.env.ETH_MIN_CONFIRMATIONS) || 3, - bsc: parseInt(process.env.BSC_MIN_CONFIRMATIONS) || 3, + bgl: parseInt(process.env.BGL_MIN_CONFIRMATIONS as string) || 3, + eth: parseInt(process.env.ETH_MIN_CONFIRMATIONS as string) || 3, + bsc: parseInt(process.env.BSC_MIN_CONFIRMATIONS as string) || 3, }; -export const feePercentage = process.env.FEE_PERCENTAGE || 1; +export const feePercentage = parseInt(process.env.FEE_PERCENTAGE as string) || 1; export const nonces = { - bsc: parseInt(process.env.BSC_NONCE) || 0, - eth: parseInt(process.env.ETH__NONCE) || 0, + bsc: parseInt(process.env.BSC_NONCE as string) || 0, + eth: parseInt(process.env.ETH__NONCE as string) || 0, }; diff --git a/service/src/utils/index.js b/service/src/utils/index.ts similarity index 72% rename from service/src/utils/index.js rename to service/src/utils/index.ts index 2b6a180..c90985a 100644 --- a/service/src/utils/index.js +++ b/service/src/utils/index.ts @@ -1,21 +1,25 @@ import Web3 from "web3"; -import { RPC } from "../modules/index.js"; +import BN from 'bn.js' -export const isValidEthAddress = (address) => +import { RPC } from "../modules/index"; + +type Address = string + +export const isValidEthAddress = (address: Address) => /^0x[a-fA-F0-9]{40}$/i.test(address); -export const isValidBglAddress = async (address) => +export const isValidBglAddress = async (address: Address) => RPC.validateAddress(address); -export const sha3 = (value) => Web3.utils.sha3(value).substring(2); +export const sha3 = (value: string | BN): string => Web3.utils.sha3(value).substring(2); const bn = Web3.utils.toBN; -export function isString(s) { +export function isString(s: any) { return typeof s === "string" || s instanceof String; } -export function toBaseUnit(value, decimals) { +export function toBaseUnit(value: string, decimals: number): BN { if (!isString(value)) { throw new Error("Pass strings to prevent floating point precision issues."); } @@ -41,8 +45,8 @@ export function toBaseUnit(value, decimals) { throw new Error("Too many decimal points"); } - let whole = comps[0], - fraction = comps[1]; + let whole: string | BN = comps[0], + fraction: string | BN = comps[1]; if (!whole) { whole = "0"; diff --git a/service/tsconfig.build.json b/service/tsconfig.build.json new file mode 100644 index 0000000..bcfff24 --- /dev/null +++ b/service/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig", + "exclude": ["src/tests"] +} \ No newline at end of file diff --git a/service/tsconfig.json b/service/tsconfig.json new file mode 100644 index 0000000..880669d --- /dev/null +++ b/service/tsconfig.json @@ -0,0 +1,103 @@ +{ + "include": [ + "./src/**/*" + ], + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}