@atomicfinance/bitcoin-rpc-provider 3.0.0
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/CHANGELOG.md +17 -0
- package/LICENSE.md +21 -0
- package/dist/BitcoinRpcProvider.d.ts +45 -0
- package/dist/BitcoinRpcProvider.js +206 -0
- package/dist/BitcoinRpcProvider.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/lib/BitcoinRpcProvider.ts +346 -0
- package/lib/index.ts +3 -0
- package/package.json +45 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @atomicfinance/bitcoin-rpc-provider
|
|
2
|
+
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 2edf70f: Port over bitcoin RPC related providers
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [2edf70f]
|
|
12
|
+
- Updated dependencies [a06082e]
|
|
13
|
+
- @atomicfinance/jsonrpc-provider@3.0.0
|
|
14
|
+
- @atomicfinance/types@3.0.0
|
|
15
|
+
- @atomicfinance/bitcoin-utils@3.0.0
|
|
16
|
+
- @atomicfinance/errors@3.0.0
|
|
17
|
+
- @atomicfinance/utils@3.0.0
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Liquality
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { JsonRpcProvider } from '@atomicfinance/jsonrpc-provider';
|
|
2
|
+
import { Address, BigNumber, bitcoin, BitcoinJsonRpcTypes, Block, ChainProvider, SendOptions, Transaction } from '@atomicfinance/types';
|
|
3
|
+
import { BitcoinNetwork } from 'bitcoin-networks';
|
|
4
|
+
interface ProviderOptions {
|
|
5
|
+
uri: string;
|
|
6
|
+
username?: string;
|
|
7
|
+
password?: string;
|
|
8
|
+
network: BitcoinNetwork;
|
|
9
|
+
feeBlockConfirmations?: number;
|
|
10
|
+
defaultFeePerByte?: number;
|
|
11
|
+
}
|
|
12
|
+
export default class BitcoinRpcProvider extends JsonRpcProvider implements Partial<ChainProvider> {
|
|
13
|
+
_feeBlockConfirmations: number;
|
|
14
|
+
_defaultFeePerByte: number;
|
|
15
|
+
_network: BitcoinNetwork;
|
|
16
|
+
_usedAddressCache: {
|
|
17
|
+
[key: string]: boolean;
|
|
18
|
+
};
|
|
19
|
+
constructor(options: ProviderOptions);
|
|
20
|
+
decodeRawTransaction(rawTransaction: string): Promise<bitcoin.Transaction>;
|
|
21
|
+
getFeePerByte(numberOfBlocks?: number): Promise<number>;
|
|
22
|
+
getMinRelayFee(): Promise<number>;
|
|
23
|
+
getBalance(_addresses: (string | Address)[]): Promise<BigNumber>;
|
|
24
|
+
getUnspentTransactions(_addresses: (Address | string)[]): Promise<bitcoin.UTXO[]>;
|
|
25
|
+
getAddressTransactionCounts(_addresses: (Address | string)[]): Promise<bitcoin.AddressTxCounts>;
|
|
26
|
+
getReceivedByAddress(address: string): Promise<number>;
|
|
27
|
+
importAddresses(addresses: string[]): Promise<any>;
|
|
28
|
+
getTransactionHex(transactionHash: string): Promise<string>;
|
|
29
|
+
generateBlock(numberOfBlocks: number): Promise<any>;
|
|
30
|
+
getBlockByHash(blockHash: string, includeTx?: boolean): Promise<Block>;
|
|
31
|
+
getBlockByNumber(blockNumber: number, includeTx?: boolean): Promise<Block<any>>;
|
|
32
|
+
getBlockHeight(): Promise<any>;
|
|
33
|
+
getTransactionByHash(transactionHash: string): Promise<Transaction<bitcoin.Transaction>>;
|
|
34
|
+
getTransactionFee(tx: bitcoin.Transaction): Promise<number>;
|
|
35
|
+
getParsedTransactionByHash(transactionHash: string, addFees?: boolean): Promise<Transaction<bitcoin.Transaction>>;
|
|
36
|
+
getRawTransactionByHash(transactionHash: string): Promise<string>;
|
|
37
|
+
sendRawTransaction(rawTransaction: string): Promise<string>;
|
|
38
|
+
sendBatchTransaction(transactions: SendOptions[]): Promise<Transaction<bitcoin.Transaction>>;
|
|
39
|
+
signRawTransaction(hexstring: string): Promise<any>;
|
|
40
|
+
createRawTransaction(transactions: [], outputs: {
|
|
41
|
+
[index: string]: number;
|
|
42
|
+
}): Promise<string>;
|
|
43
|
+
fundRawTransaction(hexstring: string): Promise<BitcoinJsonRpcTypes.FundRawResponse>;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
@@ -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 = 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 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[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 bitcoin_utils_1.normalizeTransactionObject(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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BitcoinRpcProvider.js","sourceRoot":"","sources":["../lib/BitcoinRpcProvider.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;AAAA,8EAAsD;AAE7C,6BAFF,4BAAkB,CAEE"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import {
|
|
2
|
+
decodeRawTransaction,
|
|
3
|
+
normalizeTransactionObject,
|
|
4
|
+
} from '@atomicfinance/bitcoin-utils';
|
|
5
|
+
import { BlockNotFoundError, TxNotFoundError } from '@atomicfinance/errors';
|
|
6
|
+
import { JsonRpcProvider } from '@atomicfinance/jsonrpc-provider';
|
|
7
|
+
import {
|
|
8
|
+
Address,
|
|
9
|
+
BigNumber,
|
|
10
|
+
bitcoin,
|
|
11
|
+
BitcoinJsonRpcTypes,
|
|
12
|
+
Block,
|
|
13
|
+
ChainProvider,
|
|
14
|
+
SendOptions,
|
|
15
|
+
Transaction,
|
|
16
|
+
} from '@atomicfinance/types';
|
|
17
|
+
import { addressToString } from '@atomicfinance/utils';
|
|
18
|
+
import { BitcoinNetwork } from 'bitcoin-networks';
|
|
19
|
+
import { flatten } from 'lodash';
|
|
20
|
+
|
|
21
|
+
interface ProviderOptions {
|
|
22
|
+
// RPC URI
|
|
23
|
+
uri: string;
|
|
24
|
+
// Authentication username
|
|
25
|
+
username?: string;
|
|
26
|
+
// Authentication password
|
|
27
|
+
password?: string;
|
|
28
|
+
// Bitcoin network
|
|
29
|
+
network: BitcoinNetwork;
|
|
30
|
+
// Number of block confirmations to target for fee. Defaul: 1
|
|
31
|
+
feeBlockConfirmations?: number;
|
|
32
|
+
// Default fee per byte for transactions. Default: 3
|
|
33
|
+
defaultFeePerByte?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default class BitcoinRpcProvider
|
|
37
|
+
extends JsonRpcProvider
|
|
38
|
+
implements Partial<ChainProvider> {
|
|
39
|
+
_feeBlockConfirmations: number;
|
|
40
|
+
_defaultFeePerByte: number;
|
|
41
|
+
_network: BitcoinNetwork;
|
|
42
|
+
_usedAddressCache: { [key: string]: boolean };
|
|
43
|
+
|
|
44
|
+
constructor(options: ProviderOptions) {
|
|
45
|
+
const {
|
|
46
|
+
uri,
|
|
47
|
+
username,
|
|
48
|
+
password,
|
|
49
|
+
network,
|
|
50
|
+
feeBlockConfirmations = 1,
|
|
51
|
+
defaultFeePerByte = 3,
|
|
52
|
+
} = options;
|
|
53
|
+
super(uri, username, password);
|
|
54
|
+
this._network = network;
|
|
55
|
+
this._feeBlockConfirmations = feeBlockConfirmations;
|
|
56
|
+
this._defaultFeePerByte = defaultFeePerByte;
|
|
57
|
+
this._usedAddressCache = {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async decodeRawTransaction(
|
|
61
|
+
rawTransaction: string,
|
|
62
|
+
): Promise<bitcoin.Transaction> {
|
|
63
|
+
return this.jsonrpc('decoderawtransaction', rawTransaction);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getFeePerByte(numberOfBlocks = this._feeBlockConfirmations) {
|
|
67
|
+
try {
|
|
68
|
+
const { feerate } = await this.jsonrpc(
|
|
69
|
+
'estimatesmartfee',
|
|
70
|
+
numberOfBlocks,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (feerate && feerate > 0) {
|
|
74
|
+
return Math.ceil((feerate * 1e8) / 1000);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error('Invalid estimated fee');
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return this._defaultFeePerByte;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async getMinRelayFee() {
|
|
84
|
+
const { relayfee } = await this.jsonrpc('getnetworkinfo');
|
|
85
|
+
return (relayfee * 1e8) / 1000;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getBalance(_addresses: (string | Address)[]) {
|
|
89
|
+
const addresses = _addresses.map(addressToString);
|
|
90
|
+
const _utxos = await this.getUnspentTransactions(addresses);
|
|
91
|
+
const utxos = flatten(_utxos);
|
|
92
|
+
|
|
93
|
+
return utxos.reduce((acc, utxo) => acc.plus(utxo.value), new BigNumber(0));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getUnspentTransactions(
|
|
97
|
+
_addresses: (Address | string)[],
|
|
98
|
+
): Promise<bitcoin.UTXO[]> {
|
|
99
|
+
const addresses = _addresses.map(addressToString);
|
|
100
|
+
const utxos: BitcoinJsonRpcTypes.UTXO[] = await this.jsonrpc(
|
|
101
|
+
'listunspent',
|
|
102
|
+
0,
|
|
103
|
+
9999999,
|
|
104
|
+
addresses,
|
|
105
|
+
);
|
|
106
|
+
return utxos.map((utxo) => ({
|
|
107
|
+
...utxo,
|
|
108
|
+
value: new BigNumber(utxo.amount).times(1e8).toNumber(),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getAddressTransactionCounts(_addresses: (Address | string)[]) {
|
|
113
|
+
const addresses = _addresses.map(addressToString);
|
|
114
|
+
const receivedAddresses: BitcoinJsonRpcTypes.ReceivedByAddress[] = await this.jsonrpc(
|
|
115
|
+
'listreceivedbyaddress',
|
|
116
|
+
0,
|
|
117
|
+
false,
|
|
118
|
+
true,
|
|
119
|
+
);
|
|
120
|
+
return addresses.reduce((acc: bitcoin.AddressTxCounts, addr) => {
|
|
121
|
+
const receivedAddress = receivedAddresses.find(
|
|
122
|
+
(receivedAddress) => receivedAddress.address === addr,
|
|
123
|
+
);
|
|
124
|
+
const transactionCount = receivedAddress
|
|
125
|
+
? receivedAddress.txids.length
|
|
126
|
+
: 0;
|
|
127
|
+
acc[addr] = transactionCount;
|
|
128
|
+
return acc;
|
|
129
|
+
}, {});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getReceivedByAddress(address: string): Promise<number> {
|
|
133
|
+
return this.jsonrpc('getreceivedbyaddress', address);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async importAddresses(addresses: string[]) {
|
|
137
|
+
const request = addresses.map((address) => ({
|
|
138
|
+
scriptPubKey: { address },
|
|
139
|
+
timestamp: 0,
|
|
140
|
+
}));
|
|
141
|
+
return this.jsonrpc('importmulti', request);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getTransactionHex(transactionHash: string): Promise<string> {
|
|
145
|
+
return this.jsonrpc('getrawtransaction', transactionHash);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async generateBlock(numberOfBlocks: number) {
|
|
149
|
+
const miningAddressLabel = 'miningAddress';
|
|
150
|
+
let address;
|
|
151
|
+
try {
|
|
152
|
+
// Avoid creating 100s of addresses for mining
|
|
153
|
+
const labelAddresses = await this.jsonrpc(
|
|
154
|
+
'getaddressesbylabel',
|
|
155
|
+
miningAddressLabel,
|
|
156
|
+
);
|
|
157
|
+
address = Object.keys(labelAddresses)[0];
|
|
158
|
+
} catch (e) {
|
|
159
|
+
// Label does not exist
|
|
160
|
+
address = await this.jsonrpc('getnewaddress', miningAddressLabel);
|
|
161
|
+
}
|
|
162
|
+
return this.jsonrpc('generatetoaddress', numberOfBlocks, address);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async getBlockByHash(blockHash: string, includeTx = false): Promise<Block> {
|
|
166
|
+
let data: BitcoinJsonRpcTypes.Block;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
data = await this.jsonrpc('getblock', blockHash); // TODO: This doesn't fit the interface?: https://chainquery.com/bitcoin-cli/getblock
|
|
170
|
+
} catch (e) {
|
|
171
|
+
if (e.name === 'NodeError' && e.message.includes('Block not found')) {
|
|
172
|
+
const { name, message, ...attrs } = e;
|
|
173
|
+
throw new BlockNotFoundError(`Block not found: ${blockHash}`, attrs);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
throw e;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const {
|
|
180
|
+
hash,
|
|
181
|
+
height: number,
|
|
182
|
+
mediantime: timestamp,
|
|
183
|
+
difficulty,
|
|
184
|
+
size,
|
|
185
|
+
previousblockhash: parentHash,
|
|
186
|
+
nonce,
|
|
187
|
+
tx: transactionHashes,
|
|
188
|
+
} = data;
|
|
189
|
+
|
|
190
|
+
let transactions: any[] = transactionHashes;
|
|
191
|
+
// TODO: Why transactions need to be retrieved individually? getblock has verbose 2 https://chainquery.com/bitcoin-cli/getblock
|
|
192
|
+
if (includeTx) {
|
|
193
|
+
const txs = transactionHashes.map((hash) =>
|
|
194
|
+
this.getTransactionByHash(hash),
|
|
195
|
+
);
|
|
196
|
+
transactions = await Promise.all(txs);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
hash,
|
|
201
|
+
number,
|
|
202
|
+
timestamp,
|
|
203
|
+
difficulty: parseFloat(new BigNumber(difficulty).toFixed()),
|
|
204
|
+
size,
|
|
205
|
+
parentHash,
|
|
206
|
+
nonce,
|
|
207
|
+
transactions,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async getBlockByNumber(blockNumber: number, includeTx = false) {
|
|
212
|
+
let blockHash;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
blockHash = await this.jsonrpc('getblockhash', blockNumber);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
if (
|
|
218
|
+
e.name === 'NodeError' &&
|
|
219
|
+
e.message.includes('Block height out of range')
|
|
220
|
+
) {
|
|
221
|
+
const { name, message, ...attrs } = e;
|
|
222
|
+
throw new BlockNotFoundError(`Block not found: ${blockNumber}`, attrs);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
throw e;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return this.getBlockByHash(blockHash, includeTx);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getBlockHeight() {
|
|
232
|
+
return this.jsonrpc('getblockcount');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async getTransactionByHash(transactionHash: string) {
|
|
236
|
+
try {
|
|
237
|
+
const tx = await this.getParsedTransactionByHash(transactionHash, true);
|
|
238
|
+
return tx;
|
|
239
|
+
} catch (e) {
|
|
240
|
+
if (
|
|
241
|
+
e.name === 'NodeError' &&
|
|
242
|
+
e.message.includes('No such mempool transaction')
|
|
243
|
+
) {
|
|
244
|
+
const { name, message, ...attrs } = e;
|
|
245
|
+
throw new TxNotFoundError(
|
|
246
|
+
`Transaction not found: ${transactionHash}`,
|
|
247
|
+
attrs,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
throw e;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async getTransactionFee(tx: bitcoin.Transaction) {
|
|
256
|
+
const isCoinbaseTx = tx.vin.find((vin) => vin.coinbase);
|
|
257
|
+
if (isCoinbaseTx) return; // Coinbase transactions do not have a fee
|
|
258
|
+
|
|
259
|
+
const inputs = tx.vin.map((vin) => ({ txid: vin.txid, vout: vin.vout }));
|
|
260
|
+
const inputTransactions = await Promise.all(
|
|
261
|
+
inputs.map((input) => this.jsonrpc('getrawtransaction', input.txid, 1)),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const inputValues = inputTransactions.map((inputTx, index) => {
|
|
265
|
+
const vout = inputs[index].vout;
|
|
266
|
+
const output = inputTx.vout[vout];
|
|
267
|
+
return output.value * 1e8;
|
|
268
|
+
});
|
|
269
|
+
const inputValue = inputValues.reduce(
|
|
270
|
+
(a, b) => a.plus(new BigNumber(b)),
|
|
271
|
+
new BigNumber(0),
|
|
272
|
+
);
|
|
273
|
+
const outputValue = tx.vout.reduce(
|
|
274
|
+
(a, b) => a.plus(new BigNumber(b.value).times(new BigNumber(1e8))),
|
|
275
|
+
new BigNumber(0),
|
|
276
|
+
);
|
|
277
|
+
const feeValue = inputValue.minus(outputValue);
|
|
278
|
+
return feeValue.toNumber();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async getParsedTransactionByHash(
|
|
282
|
+
transactionHash: string,
|
|
283
|
+
addFees = false,
|
|
284
|
+
): Promise<Transaction<bitcoin.Transaction>> {
|
|
285
|
+
const tx: BitcoinJsonRpcTypes.MinedTransaction = await this.jsonrpc(
|
|
286
|
+
'getrawtransaction',
|
|
287
|
+
transactionHash,
|
|
288
|
+
1,
|
|
289
|
+
);
|
|
290
|
+
return normalizeTransactionObject(
|
|
291
|
+
tx,
|
|
292
|
+
addFees ? await this.getTransactionFee(tx) : undefined,
|
|
293
|
+
tx.confirmations > 0
|
|
294
|
+
? await this.getBlockByHash(tx.blockhash)
|
|
295
|
+
: undefined,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async getRawTransactionByHash(transactionHash: string) {
|
|
300
|
+
const tx: string = await this.jsonrpc(
|
|
301
|
+
'getrawtransaction',
|
|
302
|
+
transactionHash,
|
|
303
|
+
0,
|
|
304
|
+
);
|
|
305
|
+
return tx;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async sendRawTransaction(rawTransaction: string): Promise<string> {
|
|
309
|
+
return this.jsonrpc('sendrawtransaction', rawTransaction);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async sendBatchTransaction(transactions: SendOptions[]) {
|
|
313
|
+
const outputs: { [index: string]: number } = {};
|
|
314
|
+
for (const tx of transactions) {
|
|
315
|
+
outputs[addressToString(tx.to)] = new BigNumber(tx.value)
|
|
316
|
+
.dividedBy(1e8)
|
|
317
|
+
.toNumber();
|
|
318
|
+
}
|
|
319
|
+
const rawTxOutputs = await this.createRawTransaction([], outputs);
|
|
320
|
+
const rawTxFunded = await this.fundRawTransaction(rawTxOutputs);
|
|
321
|
+
const rawTxSigned = await this.signRawTransaction(rawTxFunded.hex);
|
|
322
|
+
const fee = new BigNumber(rawTxFunded.fee).times(1e8).toNumber();
|
|
323
|
+
await this.sendRawTransaction(rawTxSigned.hex);
|
|
324
|
+
return normalizeTransactionObject(
|
|
325
|
+
decodeRawTransaction(rawTxSigned.hex, this._network),
|
|
326
|
+
fee,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async signRawTransaction(hexstring: string) {
|
|
331
|
+
return this.jsonrpc('signrawtransactionwithwallet', hexstring);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async createRawTransaction(
|
|
335
|
+
transactions: [],
|
|
336
|
+
outputs: { [index: string]: number },
|
|
337
|
+
): Promise<string> {
|
|
338
|
+
return this.jsonrpc('createrawtransaction', transactions, outputs);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async fundRawTransaction(
|
|
342
|
+
hexstring: string,
|
|
343
|
+
): Promise<BitcoinJsonRpcTypes.FundRawResponse> {
|
|
344
|
+
return this.jsonrpc('fundrawtransaction', hexstring);
|
|
345
|
+
}
|
|
346
|
+
}
|
package/lib/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atomicfinance/bitcoin-rpc-provider",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"module": "dist/index.js",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/lib/index.d.ts",
|
|
8
|
+
"directories": {
|
|
9
|
+
"dist": "dist",
|
|
10
|
+
"src": "lib"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"lib"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "../../node_modules/.bin/tsc --project tsconfig.json",
|
|
18
|
+
"lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .",
|
|
19
|
+
"lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .",
|
|
20
|
+
"test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --recursive \"tests/**/*.test.*\""
|
|
21
|
+
},
|
|
22
|
+
"author": "Atomic Finance <info@atomic.finance>",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=14"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@atomicfinance/bitcoin-utils": "^3.0.0",
|
|
29
|
+
"@atomicfinance/errors": "^3.0.0",
|
|
30
|
+
"@atomicfinance/jsonrpc-provider": "^3.0.0",
|
|
31
|
+
"@atomicfinance/types": "^3.0.0",
|
|
32
|
+
"@atomicfinance/utils": "^3.0.0",
|
|
33
|
+
"@babel/runtime": "^7.12.1",
|
|
34
|
+
"bignumber.js": "^9.0.0",
|
|
35
|
+
"bitcoin-networks": "^1.0.0",
|
|
36
|
+
"lodash": "^4.17.20"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/lodash": "^4.14.168"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false
|
|
45
|
+
}
|