Skip to content

Commit

Permalink
feat: improve rpc service and publish to npm
Browse files Browse the repository at this point in the history
  • Loading branch information
dangbt committed Dec 22, 2023
1 parent 7883ea0 commit cc54d93
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 44 deletions.
Binary file modified .DS_Store
Binary file not shown.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
```
29 changes: 25 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@
"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",
"prepare": "husky install"
},
"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",
Expand All @@ -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"
}
17 changes: 17 additions & 0 deletions package/rpc.d.ts
Original file line number Diff line number Diff line change
@@ -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<number>
getBlock: (hash: string) => Promise<ResponseRPC<Block>>
getBlockStats: (height: number) => Promise<ResponseRPC<BlockStats>>
getRawTransaction: ({
transactionHash,
blockHash,
}: {
transactionHash: string
blockHash: string
}) => Promise<ResponseRPC<TransactionRaw>>
}
export default RPCService
96 changes: 96 additions & 0 deletions package/rpc.js
Original file line number Diff line number Diff line change
@@ -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
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions src/controller/rpc.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/cron-job/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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 =
Expand All @@ -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 =
Expand Down
10 changes: 9 additions & 1 deletion src/database/index.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
35 changes: 19 additions & 16 deletions src/services/rpc.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -26,19 +26,19 @@ export class RPCService {
}
}

getBlock = async (hash: string): Promise<Block> => {
getBlock = async (hash: string): Promise<ResponseRPC<Block>> => {
try {
const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getblock","params":["${hash}"]}`
const res = await instance.post<Block>("/", dataString)
const res = await instance.post<ResponseRPC<Block>>("/", dataString)
return res.data
} catch (error) {
throw Error(error as string)
}
}
getBlockStats = async (height: number): Promise<BlockStats> => {
getBlockStats = async (height: number): Promise<ResponseRPC<BlockStats>> => {
try {
const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getblockstats","params":[${height}]}`
const res = await instance.post<BlockStats>("/", dataString)
const res = await instance.post<ResponseRPC<BlockStats>>("/", dataString)
return res.data
} catch (error) {
throw Error(error as string)
Expand All @@ -51,15 +51,19 @@ export class RPCService {
}: {
transactionHash: string
blockHash: string
}): Promise<TransactionRaw> => {
}): Promise<ResponseRPC<TransactionRaw>> => {
try {
const dataString = `{"jsonrpc":"1.0","id":"curltext","method":"getrawtransaction","params":["${transactionHash}", 2, "${blockHash}"]}`
const res = await instance.post<TransactionRaw>("/", dataString)
const res = await instance.post<ResponseRPC<TransactionRaw>>(
"/",
dataString,
)
return res.data
} catch (error) {
throw Error(error as string)
}
}

// getBestBlockHash = () => {}
// getConnectionCount = () => {}
// getDifficulty = () => {}
Expand All @@ -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
Loading

0 comments on commit cc54d93

Please sign in to comment.