@atomicfinance/bitcoin-rpc-provider 3.5.0 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/BitcoinRpcProvider.js +206 -0
- package/lib/index.js +9 -0
- package/package.json +6 -7
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const bitcoin_utils_1 = require("@atomicfinance/bitcoin-utils");
|
|
4
|
+
const errors_1 = require("@atomicfinance/errors");
|
|
5
|
+
const jsonrpc_provider_1 = require("@atomicfinance/jsonrpc-provider");
|
|
6
|
+
const types_1 = require("@atomicfinance/types");
|
|
7
|
+
const utils_1 = require("@atomicfinance/utils");
|
|
8
|
+
const lodash_1 = require("lodash");
|
|
9
|
+
class BitcoinRpcProvider extends jsonrpc_provider_1.JsonRpcProvider {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
const { uri, username, password, network, feeBlockConfirmations = 1, defaultFeePerByte = 3, } = options;
|
|
12
|
+
super(uri, username, password);
|
|
13
|
+
this._network = network;
|
|
14
|
+
this._feeBlockConfirmations = feeBlockConfirmations;
|
|
15
|
+
this._defaultFeePerByte = defaultFeePerByte;
|
|
16
|
+
this._usedAddressCache = {};
|
|
17
|
+
}
|
|
18
|
+
async decodeRawTransaction(rawTransaction) {
|
|
19
|
+
return this.jsonrpc('decoderawtransaction', rawTransaction);
|
|
20
|
+
}
|
|
21
|
+
async getFeePerByte(numberOfBlocks = this._feeBlockConfirmations) {
|
|
22
|
+
try {
|
|
23
|
+
const { feerate } = await this.jsonrpc('estimatesmartfee', numberOfBlocks);
|
|
24
|
+
if (feerate && feerate > 0) {
|
|
25
|
+
return Math.ceil((feerate * 1e8) / 1000);
|
|
26
|
+
}
|
|
27
|
+
throw new Error('Invalid estimated fee');
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
return this._defaultFeePerByte;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async getMinRelayFee() {
|
|
34
|
+
const { relayfee } = await this.jsonrpc('getnetworkinfo');
|
|
35
|
+
return (relayfee * 1e8) / 1000;
|
|
36
|
+
}
|
|
37
|
+
async getBalance(_addresses) {
|
|
38
|
+
const addresses = _addresses.map(utils_1.addressToString);
|
|
39
|
+
const _utxos = await this.getUnspentTransactions(addresses);
|
|
40
|
+
const utxos = (0, lodash_1.flatten)(_utxos);
|
|
41
|
+
return utxos.reduce((acc, utxo) => acc.plus(utxo.value), new types_1.BigNumber(0));
|
|
42
|
+
}
|
|
43
|
+
async getUnspentTransactions(_addresses) {
|
|
44
|
+
const addresses = _addresses.map(utils_1.addressToString);
|
|
45
|
+
const utxos = await this.jsonrpc('listunspent', 0, 9999999, addresses);
|
|
46
|
+
return utxos.map((utxo) => ({
|
|
47
|
+
...utxo,
|
|
48
|
+
value: new types_1.BigNumber(utxo.amount).times(1e8).toNumber(),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
async getAddressTransactionCounts(_addresses) {
|
|
52
|
+
const addresses = _addresses.map(utils_1.addressToString);
|
|
53
|
+
const receivedAddresses = await this.jsonrpc('listreceivedbyaddress', 0, false, true);
|
|
54
|
+
return addresses.reduce((acc, addr) => {
|
|
55
|
+
const receivedAddress = receivedAddresses.find((receivedAddress) => receivedAddress.address === addr);
|
|
56
|
+
const transactionCount = receivedAddress
|
|
57
|
+
? receivedAddress.txids.length
|
|
58
|
+
: 0;
|
|
59
|
+
acc[addr] = transactionCount;
|
|
60
|
+
return acc;
|
|
61
|
+
}, {});
|
|
62
|
+
}
|
|
63
|
+
async getReceivedByAddress(address) {
|
|
64
|
+
return this.jsonrpc('getreceivedbyaddress', address);
|
|
65
|
+
}
|
|
66
|
+
async importAddresses(addresses) {
|
|
67
|
+
const request = addresses.map((address) => ({
|
|
68
|
+
scriptPubKey: { address },
|
|
69
|
+
timestamp: 0,
|
|
70
|
+
}));
|
|
71
|
+
return this.jsonrpc('importmulti', request);
|
|
72
|
+
}
|
|
73
|
+
async getTransactionHex(transactionHash) {
|
|
74
|
+
return this.jsonrpc('getrawtransaction', transactionHash);
|
|
75
|
+
}
|
|
76
|
+
async generateBlock(numberOfBlocks) {
|
|
77
|
+
const miningAddressLabel = 'miningAddress';
|
|
78
|
+
let address;
|
|
79
|
+
try {
|
|
80
|
+
// Avoid creating 100s of addresses for mining
|
|
81
|
+
const labelAddresses = await this.jsonrpc('getaddressesbylabel', miningAddressLabel);
|
|
82
|
+
address = Object.keys(labelAddresses)[0];
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
// Label does not exist
|
|
86
|
+
address = await this.jsonrpc('getnewaddress', miningAddressLabel);
|
|
87
|
+
}
|
|
88
|
+
return this.jsonrpc('generatetoaddress', numberOfBlocks, address);
|
|
89
|
+
}
|
|
90
|
+
async getBlockByHash(blockHash, includeTx = false) {
|
|
91
|
+
let data;
|
|
92
|
+
try {
|
|
93
|
+
data = await this.jsonrpc('getblock', blockHash); // TODO: This doesn't fit the interface?: https://chainquery.com/bitcoin-cli/getblock
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
if (e.name === 'NodeError' && e.message.includes('Block not found')) {
|
|
97
|
+
const { name, message, ...attrs } = e;
|
|
98
|
+
throw new errors_1.BlockNotFoundError(`Block not found: ${blockHash}`, attrs);
|
|
99
|
+
}
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
const { hash, height: number, mediantime: timestamp, difficulty, size, previousblockhash: parentHash, nonce, tx: transactionHashes, } = data;
|
|
103
|
+
let transactions = transactionHashes;
|
|
104
|
+
// TODO: Why transactions need to be retrieved individually? getblock has verbose 2 https://chainquery.com/bitcoin-cli/getblock
|
|
105
|
+
if (includeTx) {
|
|
106
|
+
const txs = transactionHashes.map((hash) => this.getTransactionByHash(hash));
|
|
107
|
+
transactions = await Promise.all(txs);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
hash,
|
|
111
|
+
number,
|
|
112
|
+
timestamp,
|
|
113
|
+
difficulty: parseFloat(new types_1.BigNumber(difficulty).toFixed()),
|
|
114
|
+
size,
|
|
115
|
+
parentHash,
|
|
116
|
+
nonce,
|
|
117
|
+
transactions,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async getBlockByNumber(blockNumber, includeTx = false) {
|
|
121
|
+
let blockHash;
|
|
122
|
+
try {
|
|
123
|
+
blockHash = await this.jsonrpc('getblockhash', blockNumber);
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
if (e.name === 'NodeError' &&
|
|
127
|
+
e.message.includes('Block height out of range')) {
|
|
128
|
+
const { name, message, ...attrs } = e;
|
|
129
|
+
throw new errors_1.BlockNotFoundError(`Block not found: ${blockNumber}`, attrs);
|
|
130
|
+
}
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
133
|
+
return this.getBlockByHash(blockHash, includeTx);
|
|
134
|
+
}
|
|
135
|
+
async getBlockHeight() {
|
|
136
|
+
return this.jsonrpc('getblockcount');
|
|
137
|
+
}
|
|
138
|
+
async getTransactionByHash(transactionHash) {
|
|
139
|
+
try {
|
|
140
|
+
const tx = await this.getParsedTransactionByHash(transactionHash, true);
|
|
141
|
+
return tx;
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
if (e.name === 'NodeError' &&
|
|
145
|
+
e.message.includes('No such mempool transaction')) {
|
|
146
|
+
const { name, message, ...attrs } = e;
|
|
147
|
+
throw new errors_1.TxNotFoundError(`Transaction not found: ${transactionHash}`, attrs);
|
|
148
|
+
}
|
|
149
|
+
throw e;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async getTransactionFee(tx) {
|
|
153
|
+
const isCoinbaseTx = tx.vin.find((vin) => vin.coinbase);
|
|
154
|
+
if (isCoinbaseTx)
|
|
155
|
+
return; // Coinbase transactions do not have a fee
|
|
156
|
+
const inputs = tx.vin.map((vin) => ({ txid: vin.txid, vout: vin.vout }));
|
|
157
|
+
const inputTransactions = await Promise.all(inputs.map((input) => this.jsonrpc('getrawtransaction', input.txid, 1)));
|
|
158
|
+
const inputValues = inputTransactions.map((inputTx, index) => {
|
|
159
|
+
const vout = inputs[index].vout;
|
|
160
|
+
const output = inputTx.vout[vout];
|
|
161
|
+
return output.value * 1e8;
|
|
162
|
+
});
|
|
163
|
+
const inputValue = inputValues.reduce((a, b) => a.plus(new types_1.BigNumber(b)), new types_1.BigNumber(0));
|
|
164
|
+
const outputValue = tx.vout.reduce((a, b) => a.plus(new types_1.BigNumber(b.value).times(new types_1.BigNumber(1e8))), new types_1.BigNumber(0));
|
|
165
|
+
const feeValue = inputValue.minus(outputValue);
|
|
166
|
+
return feeValue.toNumber();
|
|
167
|
+
}
|
|
168
|
+
async getParsedTransactionByHash(transactionHash, addFees = false) {
|
|
169
|
+
const tx = await this.jsonrpc('getrawtransaction', transactionHash, 1);
|
|
170
|
+
return (0, bitcoin_utils_1.normalizeTransactionObject)(tx, addFees ? await this.getTransactionFee(tx) : undefined, tx.confirmations > 0
|
|
171
|
+
? await this.getBlockByHash(tx.blockhash)
|
|
172
|
+
: undefined);
|
|
173
|
+
}
|
|
174
|
+
async getRawTransactionByHash(transactionHash) {
|
|
175
|
+
const tx = await this.jsonrpc('getrawtransaction', transactionHash, 0);
|
|
176
|
+
return tx;
|
|
177
|
+
}
|
|
178
|
+
async sendRawTransaction(rawTransaction) {
|
|
179
|
+
return this.jsonrpc('sendrawtransaction', rawTransaction);
|
|
180
|
+
}
|
|
181
|
+
async sendBatchTransaction(transactions) {
|
|
182
|
+
const outputs = {};
|
|
183
|
+
for (const tx of transactions) {
|
|
184
|
+
outputs[(0, utils_1.addressToString)(tx.to)] = new types_1.BigNumber(tx.value)
|
|
185
|
+
.dividedBy(1e8)
|
|
186
|
+
.toNumber();
|
|
187
|
+
}
|
|
188
|
+
const rawTxOutputs = await this.createRawTransaction([], outputs);
|
|
189
|
+
const rawTxFunded = await this.fundRawTransaction(rawTxOutputs);
|
|
190
|
+
const rawTxSigned = await this.signRawTransaction(rawTxFunded.hex);
|
|
191
|
+
const fee = new types_1.BigNumber(rawTxFunded.fee).times(1e8).toNumber();
|
|
192
|
+
await this.sendRawTransaction(rawTxSigned.hex);
|
|
193
|
+
return (0, bitcoin_utils_1.normalizeTransactionObject)((0, bitcoin_utils_1.decodeRawTransaction)(rawTxSigned.hex, this._network), fee);
|
|
194
|
+
}
|
|
195
|
+
async signRawTransaction(hexstring) {
|
|
196
|
+
return this.jsonrpc('signrawtransactionwithwallet', hexstring);
|
|
197
|
+
}
|
|
198
|
+
async createRawTransaction(transactions, outputs) {
|
|
199
|
+
return this.jsonrpc('createrawtransaction', transactions, outputs);
|
|
200
|
+
}
|
|
201
|
+
async fundRawTransaction(hexstring) {
|
|
202
|
+
return this.jsonrpc('fundrawtransaction', hexstring);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.default = BitcoinRpcProvider;
|
|
206
|
+
//# sourceMappingURL=BitcoinRpcProvider.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BitcoinRpcProvider = void 0;
|
|
7
|
+
const BitcoinRpcProvider_1 = __importDefault(require("./BitcoinRpcProvider"));
|
|
8
|
+
exports.BitcoinRpcProvider = BitcoinRpcProvider_1.default;
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atomicfinance/bitcoin-rpc-provider",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
|
-
"types": "dist/lib/index.d.ts",
|
|
8
7
|
"directories": {
|
|
9
8
|
"dist": "dist",
|
|
10
9
|
"src": "lib"
|
|
@@ -25,11 +24,11 @@
|
|
|
25
24
|
"node": ">=14"
|
|
26
25
|
},
|
|
27
26
|
"dependencies": {
|
|
28
|
-
"@atomicfinance/bitcoin-utils": "^3.5.
|
|
29
|
-
"@atomicfinance/errors": "^3.5.
|
|
30
|
-
"@atomicfinance/jsonrpc-provider": "^3.5.
|
|
31
|
-
"@atomicfinance/types": "^3.5.
|
|
32
|
-
"@atomicfinance/utils": "^3.5.
|
|
27
|
+
"@atomicfinance/bitcoin-utils": "^3.5.2",
|
|
28
|
+
"@atomicfinance/errors": "^3.5.2",
|
|
29
|
+
"@atomicfinance/jsonrpc-provider": "^3.5.2",
|
|
30
|
+
"@atomicfinance/types": "^3.5.2",
|
|
31
|
+
"@atomicfinance/utils": "^3.5.2",
|
|
33
32
|
"@babel/runtime": "^7.12.1",
|
|
34
33
|
"bignumber.js": "^9.0.0",
|
|
35
34
|
"bitcoin-networks": "^1.0.0",
|