diff --git a/.DS_Store b/.DS_Store index ae76aa7..557f989 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/README.md b/README.md index 41cf2ee..dcedb2b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,24 @@ # Bitcoin Query -A javascript Bitcoin library for `node.js`. Written in `TypeScript`. Connect to `Mysql` database for saving data +A javascript Bitcoin library for `node.js`. Written in `TypeScript`. # Use can trust the source This source doesn't have any address wallet or connect string. you can trust the source code `100%` ## Installing - the source uses Nodejs, Typescript for the server, and [Sequlize](https://sequelize.org/) for query data from [Mysql database](https://dev.mysql.com/downloads/installer/). Btw the source also has used [pnpm](https://pnpm.io/installation) for saving disk and highest speed. + Before installing, you need to make sure that [Bitcoin core](https://bitcoin.org/en/download) was installed and runned ``` - pnpm i - pnpm dev + pnpm i bitcoin-query + ``` + + ## Example + + ``` + import RPCServices from "bitcoin-query" + + const rpcServices = new RPCServices(url) // url is a bitcoin core connecting string + + rpcServices.getBlockCount() // get height number of latest block + ``` \ No newline at end of file diff --git a/package.json b/package.json index ec2ac03..21c251e 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "dev": "nodemon src/index.ts", "build": "npx tsc", "start": "node build/index.js", + "rollup": "rollup --config rollup.config.js --configPlugin typescript", "format": "prettier --write \"./src/**/*.{js,ts}\"", "lint": "eslint . --ext .ts", "precommit": "lint-staged", @@ -10,14 +11,14 @@ }, "dependencies": { "axios": "1.6.2", - "dotenv": "16.3.1", "express": "4.18.2", - "mysql2": "^3.6.5", - "sequelize": "^6.35.2" + "mysql2": "3.6.5", + "sequelize": "6.35.2" }, "devDependencies": { "@types/express": "4.17.21", "@types/node": "20.10.5", + "dotenv": "16.3.1", "husky": "8.0.3", "lint-staged": "15.2.0", "nodemon": "3.0.2", @@ -27,5 +28,25 @@ }, "lint-staged": { "*.{js,ts}": "pnpm format" - } + }, + "name": "bitcoin-query", + "description": "A javascript Bitcoin library for `node.js`. Written in `TypeScript`. Connect to `Mysql` database for saving data", + "version": "0.0.3", + "main": "./package/rpc.js", + "author": "dangbt", + "license": "ISC", + "types": "./package/rpc.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/dangbt/bitcoin-query.git" + }, + "keywords": [ + "bitcoin", + "nodejs", + "typescript" + ], + "bugs": { + "url": "https://github.com/dangbt/bitcoin-query/issues" + }, + "homepage": "https://github.com/dangbt/bitcoin-query#readme" } \ No newline at end of file diff --git a/package/rpc.d.ts b/package/rpc.d.ts new file mode 100644 index 0000000..5577f0c --- /dev/null +++ b/package/rpc.d.ts @@ -0,0 +1,17 @@ +import type { Block, BlockStats, ResponseRPC } from "../types" +import { TransactionRaw } from "../types/transacton" +export declare class RPCService { + url: string + constructor(url: string) + getBlockCount: () => Promise + getBlock: (hash: string) => Promise> + getBlockStats: (height: number) => Promise> + getRawTransaction: ({ + transactionHash, + blockHash, + }: { + transactionHash: string + blockHash: string + }) => Promise> +} +export default RPCService diff --git a/package/rpc.js b/package/rpc.js new file mode 100644 index 0000000..d0d03f9 --- /dev/null +++ b/package/rpc.js @@ -0,0 +1,96 @@ +"use strict" +var __awaiter = + (this && this.__awaiter) || + function (thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P + ? value + : new P(function (resolve) { + resolve(value) + }) + } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)) + } catch (e) { + reject(e) + } + } + function rejected(value) { + try { + step(generator["throw"](value)) + } catch (e) { + reject(e) + } + } + function step(result) { + result.done + ? resolve(result.value) + : adopt(result.value).then(fulfilled, rejected) + } + step((generator = generator.apply(thisArg, _arguments || [])).next()) + }) + } +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod } + } +Object.defineProperty(exports, "__esModule", { value: true }) +exports.RPCService = void 0 +const axios_1 = __importDefault(require("../axios/axios")) +class RPCService { + constructor(url) { + this.getBlockCount = () => + __awaiter(this, void 0, void 0, function* () { + try { + const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getblockcount","params":[]}` + const res = yield axios_1.default.post("/", dataString) + return res.data + } catch (error) { + throw Error(error) + } + }) + this.getBlock = (hash) => + __awaiter(this, void 0, void 0, function* () { + try { + const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getblock","params":["${hash}"]}` + const res = yield axios_1.default.post("/", dataString) + return res.data + } catch (error) { + throw Error(error) + } + }) + this.getBlockStats = (height) => + __awaiter(this, void 0, void 0, function* () { + try { + const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getblockstats","params":[${height}]}` + const res = yield axios_1.default.post("/", dataString) + return res.data + } catch (error) { + throw Error(error) + } + }) + this.getRawTransaction = ({ transactionHash, blockHash }) => + __awaiter(this, void 0, void 0, function* () { + try { + const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getrawtransaction","params":["${transactionHash}", 2, "${blockHash}"]}` + const res = yield axios_1.default.post("/", dataString) + return res.data + } catch (error) { + throw Error(error) + } + }) + if (!url) { + throw Error( + "URL required, it is look like http://${USER}:${PASS}@127.0.0.1:8332/", + ) + } + this.url = url + axios_1.default.defaults.baseURL = url + axios_1.default.defaults.headers.common["Content-Type"] = "text/plain" + } +} +exports.RPCService = RPCService +exports.default = RPCService diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05ad48e..465f175 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,17 +8,14 @@ dependencies: axios: specifier: 1.6.2 version: 1.6.2 - dotenv: - specifier: 16.3.1 - version: 16.3.1 express: specifier: 4.18.2 version: 4.18.2 mysql2: - specifier: ^3.6.5 + specifier: 3.6.5 version: 3.6.5 sequelize: - specifier: ^6.35.2 + specifier: 6.35.2 version: 6.35.2(mysql2@3.6.5) devDependencies: @@ -28,6 +25,9 @@ devDependencies: '@types/node': specifier: 20.10.5 version: 20.10.5 + dotenv: + specifier: 16.3.1 + version: 16.3.1 husky: specifier: 8.0.3 version: 8.0.3 @@ -448,7 +448,7 @@ packages: /dotenv@16.3.1: resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} engines: {node: '>=12'} - dev: false + dev: true /dottie@2.0.6: resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} diff --git a/src/controller/rpc.ts b/src/controller/rpc.ts index 2b686f5..48deac2 100644 --- a/src/controller/rpc.ts +++ b/src/controller/rpc.ts @@ -1,9 +1,15 @@ import type { Request, Response } from "express" -import rpcService from "../services/rpc" +import RPCService from "../services/rpc" import symbolService from "../services/symbol" -import type { RPCService } from "../services/rpc" import type { SymbolService } from "../services/symbol" import handler, { StatusCode } from "../utils/handler" +import dotenv from "dotenv" + +dotenv.config() + +const USER = process.env.RPC_USER +const PASS = process.env.RPC_PASSWORD +const rpcService = new RPCService(`http://${USER}:${PASS}@127.0.0.1:8332/`) class RPCController { rpcService diff --git a/src/cron-job/index.ts b/src/cron-job/index.ts index c6ea4df..ff1d45c 100644 --- a/src/cron-job/index.ts +++ b/src/cron-job/index.ts @@ -28,16 +28,16 @@ export default class CronJob { start = async (height: number) => { try { const res = await this.rpcService.getBlockStats(height) - const hash = res.blockhash + const hash = res.result.blockhash const block = await this.rpcService.getBlock(hash) - const tx = block.tx + const tx = block.result.tx if (tx.length) { tx.map(async (t, i) => { const transaction = await this.rpcService.getRawTransaction({ transactionHash: t, blockHash: hash, }) - const vout = transaction.vout + const vout = transaction.result.vout // this transaction is for miner, who is an owner block, dont have from address if (i === 0) { vout.forEach(async (out) => { @@ -46,7 +46,7 @@ export default class CronJob { from: "0", to: out.scriptPubKey.address, value: out.value, - time: transaction.time, + time: transaction.result.time, symbol_id: this.symbol.id, } const trans = @@ -56,10 +56,10 @@ export default class CronJob { } else { vout.map(async (out) => { const data: TransactionCreationAttributes = { - from: transaction.vin[0].prevout.scriptPubKey.address, + from: transaction.result.vin[0].prevout.scriptPubKey.address, to: out.scriptPubKey.address, value: out.value, - time: transaction.time, + time: transaction.result.time, symbol_id: this.symbol.id, } const trans = diff --git a/src/database/index.ts b/src/database/index.ts index 08e8ec6..2936664 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -1,10 +1,18 @@ import { Sequelize } from "sequelize" import { syncDatabase } from "./models/model" import CronJob from "../cron-job" -import rpcService from "../services/rpc" +import RPCService from "../services/rpc" import symbolService from "../services/symbol" import transactionService from "../services/transaction" +import dotenv from "dotenv" + +dotenv.config() + +const USER = process.env.RPC_USER +const PASS = process.env.RPC_PASSWORD +const rpcService = new RPCService(`http://${USER}:${PASS}@127.0.0.1:8332/`) + class Database { sequelize: Sequelize connect = (connectString: string) => { diff --git a/src/services/rpc.ts b/src/services/rpc.ts index c354660..3f42adf 100644 --- a/src/services/rpc.ts +++ b/src/services/rpc.ts @@ -1,16 +1,16 @@ import instance from "../axios/axios" -import type { Block, BlockStats } from "../types" -import dotenv from "dotenv" +import type { Block, BlockStats, ResponseRPC } from "../types" import { TransactionRaw } from "../types/transacton" -dotenv.config() - -const USER = process.env.RPC_USER -const PASS = process.env.RPC_PASSWORD - export class RPCService { - url + url: string constructor(url: string) { + if (!url) { + throw Error( + "URL required, it is look like http://${USER}:${PASS}@127.0.0.1:8332/", + ) + } + this.url = url instance.defaults.baseURL = url instance.defaults.headers.common["Content-Type"] = "text/plain" @@ -26,19 +26,19 @@ export class RPCService { } } - getBlock = async (hash: string): Promise => { + getBlock = async (hash: string): Promise> => { try { const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getblock","params":["${hash}"]}` - const res = await instance.post("/", dataString) + const res = await instance.post>("/", dataString) return res.data } catch (error) { throw Error(error as string) } } - getBlockStats = async (height: number): Promise => { + getBlockStats = async (height: number): Promise> => { try { const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getblockstats","params":[${height}]}` - const res = await instance.post("/", dataString) + const res = await instance.post>("/", dataString) return res.data } catch (error) { throw Error(error as string) @@ -51,15 +51,19 @@ export class RPCService { }: { transactionHash: string blockHash: string - }): Promise => { + }): Promise> => { try { const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getrawtransaction","params":["${transactionHash}", 2, "${blockHash}"]}` - const res = await instance.post("/", dataString) + const res = await instance.post>( + "/", + dataString, + ) return res.data } catch (error) { throw Error(error as string) } } + // getBestBlockHash = () => {} // getConnectionCount = () => {} // getDifficulty = () => {} @@ -69,5 +73,4 @@ export class RPCService { // getRawMemPool = () => {} } -const rpcService = new RPCService(`http://${USER}:${PASS}@127.0.0.1:8332/`) -export default rpcService +export default RPCService diff --git a/src/types/block.ts b/src/types/block.ts index c05c0df..06fd948 100644 --- a/src/types/block.ts +++ b/src/types/block.ts @@ -62,3 +62,7 @@ export interface BlockStats { utxo_increase_actual: number // (numeric, optional) The increase/decrease in the number of unspent outputs, not counting unspendables utxo_size_inc_actual: number // (numeric, optional) The increase/decrease in size for the utxo index, not counting unspendables } // + +export interface ResponseRPC { + result: T +} diff --git a/tsconfig.json b/tsconfig.json index 127090a..fc3c7e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,9 +26,9 @@ /* Modules */ "module": "commonjs" /* Specify what module code is generated. */, - "rootDir": "src/" /* Specify the root folder within your source files. */, + "rootDir": "src" /* Specify the root folder within your source files. */, // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */, // "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'. */ @@ -49,9 +49,9 @@ // "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. */ + "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. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "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. */