@did-btcr2/bitcoin 0.3.3 → 0.4.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.
Files changed (104) hide show
  1. package/dist/cjs/bitcoin.js +82 -83
  2. package/dist/cjs/bitcoin.js.map +1 -1
  3. package/dist/cjs/client/http.js +9 -0
  4. package/dist/cjs/client/http.js.map +1 -0
  5. package/dist/cjs/client/rest/address.js +20 -28
  6. package/dist/cjs/client/rest/address.js.map +1 -1
  7. package/dist/cjs/client/rest/block.js +23 -22
  8. package/dist/cjs/client/rest/block.js.map +1 -1
  9. package/dist/cjs/client/rest/index.js +30 -83
  10. package/dist/cjs/client/rest/index.js.map +1 -1
  11. package/dist/cjs/client/rest/protocol.js +129 -0
  12. package/dist/cjs/client/rest/protocol.js.map +1 -0
  13. package/dist/cjs/client/rest/transaction.js +19 -21
  14. package/dist/cjs/client/rest/transaction.js.map +1 -1
  15. package/dist/cjs/client/rpc/index.js +77 -338
  16. package/dist/cjs/client/rpc/index.js.map +1 -1
  17. package/dist/cjs/client/rpc/interface.js +1 -6
  18. package/dist/cjs/client/rpc/interface.js.map +1 -1
  19. package/dist/cjs/client/rpc/json-rpc.js +40 -63
  20. package/dist/cjs/client/rpc/json-rpc.js.map +1 -1
  21. package/dist/cjs/client/rpc/protocol.js +134 -0
  22. package/dist/cjs/client/rpc/protocol.js.map +1 -0
  23. package/dist/cjs/constants.js +16 -5
  24. package/dist/cjs/constants.js.map +1 -1
  25. package/dist/cjs/errors.js.map +1 -1
  26. package/dist/cjs/index.js +21 -13
  27. package/dist/cjs/index.js.map +1 -1
  28. package/dist/cjs/types.js +5 -55
  29. package/dist/cjs/types.js.map +1 -1
  30. package/dist/esm/bitcoin.js +82 -83
  31. package/dist/esm/bitcoin.js.map +1 -1
  32. package/dist/esm/client/http.js +9 -0
  33. package/dist/esm/client/http.js.map +1 -0
  34. package/dist/esm/client/rest/address.js +20 -28
  35. package/dist/esm/client/rest/address.js.map +1 -1
  36. package/dist/esm/client/rest/block.js +23 -22
  37. package/dist/esm/client/rest/block.js.map +1 -1
  38. package/dist/esm/client/rest/index.js +30 -83
  39. package/dist/esm/client/rest/index.js.map +1 -1
  40. package/dist/esm/client/rest/protocol.js +129 -0
  41. package/dist/esm/client/rest/protocol.js.map +1 -0
  42. package/dist/esm/client/rest/transaction.js +19 -21
  43. package/dist/esm/client/rest/transaction.js.map +1 -1
  44. package/dist/esm/client/rpc/index.js +77 -338
  45. package/dist/esm/client/rpc/index.js.map +1 -1
  46. package/dist/esm/client/rpc/interface.js +1 -6
  47. package/dist/esm/client/rpc/interface.js.map +1 -1
  48. package/dist/esm/client/rpc/json-rpc.js +40 -63
  49. package/dist/esm/client/rpc/json-rpc.js.map +1 -1
  50. package/dist/esm/client/rpc/protocol.js +134 -0
  51. package/dist/esm/client/rpc/protocol.js.map +1 -0
  52. package/dist/esm/constants.js +16 -5
  53. package/dist/esm/constants.js.map +1 -1
  54. package/dist/esm/errors.js.map +1 -1
  55. package/dist/esm/index.js +21 -13
  56. package/dist/esm/index.js.map +1 -1
  57. package/dist/esm/types.js +5 -55
  58. package/dist/esm/types.js.map +1 -1
  59. package/dist/types/bitcoin.d.ts +66 -45
  60. package/dist/types/bitcoin.d.ts.map +1 -1
  61. package/dist/types/client/http.d.ts +21 -0
  62. package/dist/types/client/http.d.ts.map +1 -0
  63. package/dist/types/client/rest/address.d.ts +16 -19
  64. package/dist/types/client/rest/address.d.ts.map +1 -1
  65. package/dist/types/client/rest/block.d.ts +24 -16
  66. package/dist/types/client/rest/block.d.ts.map +1 -1
  67. package/dist/types/client/rest/index.d.ts +20 -51
  68. package/dist/types/client/rest/index.d.ts.map +1 -1
  69. package/dist/types/client/rest/protocol.d.ts +58 -0
  70. package/dist/types/client/rest/protocol.d.ts.map +1 -0
  71. package/dist/types/client/rest/transaction.d.ts +16 -17
  72. package/dist/types/client/rest/transaction.d.ts.map +1 -1
  73. package/dist/types/client/rpc/index.d.ts +105 -253
  74. package/dist/types/client/rpc/index.d.ts.map +1 -1
  75. package/dist/types/client/rpc/interface.d.ts +1 -4
  76. package/dist/types/client/rpc/interface.d.ts.map +1 -1
  77. package/dist/types/client/rpc/json-rpc.d.ts +20 -18
  78. package/dist/types/client/rpc/json-rpc.d.ts.map +1 -1
  79. package/dist/types/client/rpc/protocol.d.ts +82 -0
  80. package/dist/types/client/rpc/protocol.d.ts.map +1 -0
  81. package/dist/types/constants.d.ts +16 -5
  82. package/dist/types/constants.d.ts.map +1 -1
  83. package/dist/types/errors.d.ts +4 -3
  84. package/dist/types/errors.d.ts.map +1 -1
  85. package/dist/types/index.d.ts +20 -13
  86. package/dist/types/index.d.ts.map +1 -1
  87. package/dist/types/types.d.ts +98 -696
  88. package/dist/types/types.d.ts.map +1 -1
  89. package/package.json +3 -3
  90. package/src/bitcoin.ts +104 -117
  91. package/src/client/http.ts +28 -0
  92. package/src/client/rest/address.ts +24 -30
  93. package/src/client/rest/block.ts +29 -26
  94. package/src/client/rest/index.ts +37 -93
  95. package/src/client/rest/protocol.ts +153 -0
  96. package/src/client/rest/transaction.ts +23 -23
  97. package/src/client/rpc/index.ts +142 -380
  98. package/src/client/rpc/interface.ts +42 -51
  99. package/src/client/rpc/json-rpc.ts +53 -60
  100. package/src/client/rpc/protocol.ts +160 -0
  101. package/src/constants.ts +17 -5
  102. package/src/errors.ts +10 -3
  103. package/src/index.ts +81 -13
  104. package/src/types.ts +332 -973
@@ -1,8 +1,6 @@
1
- import { DEFAULT_BITCOIN_NETWORK_CONFIG } from '../../constants.js';
1
+ import { JSONUtils } from '@did-btcr2/common';
2
2
  import { BitcoinRpcError } from '../../errors.js';
3
3
  import {
4
- BatchOption,
5
- BitcoinRpcClientConfig,
6
4
  BlockResponse,
7
5
  BlockV0,
8
6
  BlockV1,
@@ -15,505 +13,269 @@ import {
15
13
  GetBlockParams,
16
14
  ListTransactionsParams,
17
15
  ListTransactionsResult,
18
- MethodNameInLowerCase,
19
16
  RawTransactionResponse,
20
17
  RawTransactionV0,
21
18
  RawTransactionV1,
22
19
  RawTransactionV2,
23
- RpcClientConfig,
20
+ RpcConfig,
24
21
  SignedRawTx,
25
22
  UnspentTxInfo,
26
23
  VerbosityLevel,
27
24
  WalletTransaction
28
25
  } from '../../types.js';
26
+ import { HttpExecutor } from '../http.js';
29
27
  import { BitcoinRpcClient } from './interface.js';
30
28
  import { JsonRpcTransport } from './json-rpc.js';
31
29
 
30
+ // ── Typed RPC method map ────────────────────────────────────────────
31
+
32
+ /**
33
+ * Maps Bitcoin Core JSON-RPC method names to their parameter and return types.
34
+ * Used by {@link BitcoinCoreRpcClient.executeRpc} for compile-time safety.
35
+ */
36
+ export interface RpcMethodMap {
37
+ getbestblockhash: { params: []; result: string };
38
+ getblock: { params: [string, number?]; result: BlockResponse };
39
+ getblockchaininfo: { params: []; result: ChainInfo };
40
+ getblockcount: { params: []; result: number };
41
+ getblockhash: { params: [number]; result: string };
42
+ getbalance: { params: []; result: number };
43
+ getnewaddress: { params: [string?, string?]; result: string };
44
+ gettransaction: { params: [string, boolean?]; result: WalletTransaction };
45
+ getrawtransaction: { params: [string, number?, string?]; result: RawTransactionResponse };
46
+ createrawtransaction: { params: [CreateRawTxInputs[], CreateRawTxOutputs[], number?, boolean?]; result: string };
47
+ signrawtransactionwithwallet: { params: [string]; result: SignedRawTx };
48
+ sendrawtransaction: { params: [string, (number | string)?, (number | string)?]; result: string };
49
+ listtransactions: { params: [string?, number?, number?, boolean?]; result: ListTransactionsResult };
50
+ listunspent: { params: [number?, number?, string[]?, boolean?]; result: UnspentTxInfo[] };
51
+ sendtoaddress: { params: [string, number]; result: string };
52
+ signmessage: { params: [string, string]; result: string };
53
+ verifymessage: { params: [string, string, string]; result: boolean };
54
+ deriveaddresses: { params: [string, number[]?]; result: DerivedAddresses[] };
55
+ }
56
+
57
+ /** Method names that have typed definitions in {@link RpcMethodMap}. */
58
+ export type TypedRpcMethod = keyof RpcMethodMap;
59
+
60
+ // ── Client ──────────────────────────────────────────────────────────
61
+
32
62
  /**
33
- * Class representing a Bitcoin Core RPC client.
34
- * @class BitcoinCoreRpcClient
35
- * @type {BitcoinCoreRpcClient}
63
+ * Bitcoin Core RPC client.
36
64
  * @implements {BitcoinRpcClient}
37
65
  */
38
66
  export class BitcoinCoreRpcClient implements BitcoinRpcClient {
39
- /**
40
- * The singleton instance of the BitcoinCoreRpcClient.
41
- * @type {BitcoinCoreRpcClient}
42
- * @private
43
- */
44
- static #instance: BitcoinCoreRpcClient;
45
-
46
- /**
47
- * The JSON-RPC transport layer.
48
- * @type {JsonRpcTransport}
49
- * @private
50
- */
51
67
  readonly #transport: JsonRpcTransport;
68
+ readonly #config: RpcConfig;
52
69
 
53
- /**
54
- * The configuration for the RPC client.
55
- * @type {RpcClientConfig}
56
- * @private
57
- */
58
- readonly #config: RpcClientConfig;
59
-
60
- /**
61
- * Constructs a new {@link BitcoinCoreRpcClient} instance from a new {@link RpcClient | RpcClient}.
62
- * @param {RpcClientConfig} config The bitcoin-core client instance.
63
- * @example
64
- * ```
65
- * import BitcoinRpcClient from '@did-btcr2/method';
66
- * const bob = BitcoinRpcClient.connect(); // To use default polar config, pass no args. Polar must run locally.
67
- * ```
68
- */
69
- constructor(config: RpcClientConfig) {
70
- this.#config = new BitcoinRpcClientConfig(config);
71
- this.#transport = new JsonRpcTransport(this.#config);
70
+ constructor(config: RpcConfig, executor?: HttpExecutor) {
71
+ this.#config = config;
72
+ this.#transport = new JsonRpcTransport(config, executor);
72
73
  }
73
74
 
74
- /**
75
- * Get the config for the current BitcoinRpcClient object.
76
- * @returns {BitcoinRpcClient} The encapsulated {@link BitcoinRpcClient} object.
77
- * @example
78
- * ```
79
- * import BitcoinRpcClient from '@did-btcr2/method';
80
- * const alice = BitcoinRpcClient.connect();
81
- * const config = alice.config;
82
- * ```
83
- */
84
- get config(): BitcoinRpcClientConfig {
85
- const config = this.#config;
86
- return config;
75
+ get config(): RpcConfig {
76
+ return this.#config;
87
77
  }
88
78
 
89
- /**
90
- * Get the client for the current BitcoinRpcClient object.
91
- * @returns {RpcClient} The encapsulated {@link RpcClient} object.
92
- * @example
93
- * ```
94
- * const alice = BitcoinRpcClient.connect();
95
- * const config = alice.client;
96
- * ```
97
- */
98
79
  get client(): JsonRpcTransport {
99
- const client = this.#transport;
100
- return client;
80
+ return this.#transport;
101
81
  }
102
82
 
103
83
  /**
104
- * Static method initializes a static instance of BitcoinCoreRpcClient with the given configuration.
105
- * The RpcClient returned by this method does not have any named methods.
106
- * Use this method to create and pass a new RpcClient instance to a BitcoinCoreRpcClient constructor.
107
- *
108
- * @param {RpcClientConfig} config The configuration object for the client (optional).
109
- * @returns {BitcoinCoreRpcClient} A new RpcClient instance.
110
- * @example
111
- * ```
112
- * const options: RpcClientConfig = {
113
- * host: 'http://localhost:18443',
114
- * username: 'alice',
115
- * password: 'alicepass',
116
- * version: '28.1.0',
117
- * }
118
- * const alice = BitcoinCoreRpcClient.initialize(options); // Client config required
119
- * ```
84
+ * Executes a typed JSON-RPC command on the bitcoind node.
120
85
  */
121
- public static initialize(config?: RpcClientConfig): BitcoinCoreRpcClient {
122
- const cfg = new BitcoinRpcClientConfig(config);
123
- BitcoinCoreRpcClient.#instance = new BitcoinCoreRpcClient(cfg);
124
- return BitcoinCoreRpcClient.#instance;
125
- }
126
-
127
- /**
128
- * Static method connects to a bitcoin node running the bitcoin core daemon (bitcoind).
129
- * To use default polar config, do not pass a config. See {@link DEFAULT_BITCOIN_NETWORK_CONFIG} for default config.
130
- * @required A locally running {@link https://github.com/jamaljsr/polar | Polar Lightning} regtest node.
131
- *
132
- * @param {?RpcClientConfig} config The configuration object for the client (optional).
133
- * @returns A new {@link BitcoinRpcClient} instance.
134
- * @example
135
- * ```
136
- * const alice = BitcoinRpcClient.connect();
137
- * ```
138
- */
139
- public static connect(config?: RpcClientConfig): BitcoinCoreRpcClient {
140
- return new BitcoinCoreRpcClient(config ?? DEFAULT_BITCOIN_NETWORK_CONFIG.regtest.rpc);
141
- }
142
-
143
- /**
144
- * Check if the given error is a JSON-RPC error.
145
- * @param {unknown} e The error to check.
146
- * @returns {boolean} True if the error is a JSON-RPC error, false otherwise.
147
- */
148
- public isJsonRpcError(e: unknown): e is Error & { name: 'RpcError'; code?: number } {
149
- return (
150
- e instanceof Error &&
151
- e.name === 'RpcError' &&
152
- typeof (e as any).code === 'number'
153
- );
154
- }
155
-
156
- /**
157
- * Executes a JSON-RPC command on the bitcoind node.
158
- * @param {MethodNameInLowerCase} method The name of the method to call.
159
- * @param {Array<any>} parameters The parameters to pass to the method.
160
- * @returns {Promise<T>} A promise resolving to the result of the command.
161
- */
162
- private async executeRpc<T>(method: MethodNameInLowerCase, parameters: Array<any> = []): Promise<T> {
86
+ private async executeRpc<M extends TypedRpcMethod>(
87
+ method: M,
88
+ parameters: RpcMethodMap[M]['params'] = [] as unknown as RpcMethodMap[M]['params']
89
+ ): Promise<RpcMethodMap[M]['result']> {
163
90
  try {
164
- const raw = await this.client.command([{ method, parameters }] as BatchOption[]);
165
- const normalized = JSON.unprototyped(raw) ? JSON.normalize(raw) : raw;
166
- const result = Array.isArray(normalized)
167
- ? normalized[normalized.length - 1]
168
- : normalized;
169
- return result as T;
91
+ const raw = await this.#transport.call(method, parameters as unknown[]);
92
+ const normalized = JSONUtils.isUnprototyped(raw) ? JSONUtils.normalize(raw) : raw;
93
+ return normalized as RpcMethodMap[M]['result'];
170
94
  } catch (err: unknown) {
171
- this.handleError(err, method, parameters);
172
- }
173
- }
174
-
175
- /**
176
- *
177
- * Map JSON-RPC error codes to HTTP status codes.
178
- * @param {number | undefined} code The JSON-RPC error code.
179
- * @returns {number} The corresponding HTTP status code.
180
- *
181
- * | Error type | HTTP code |
182
- * | -------------------------------| --------------------|
183
- * | Valid JSON-RPC failure | **400 / 404 / 422** |
184
- * | JSON-RPC authentication issues | **401 / 403** |
185
- * | Upstream bitcoind unreachable | **502** |
186
- * | Upstream timeout | **504** |
187
- * | Network transient errors | **503** |
188
- * | Unknown unexpected errors | **500** |
189
- */
190
- private mapRpcCodeToHttp(code?: number): number {
191
- switch (code) {
192
- case -32700:
193
- case -32600:
194
- case -32602:
195
- return 400;
196
-
197
- case -32601:
198
- return 404;
199
-
200
- default:
201
- return 422;
202
- }
203
- }
204
-
205
- /**
206
- * Handle errors that occur while executing commands.
207
- * @param methods An array of {@link BatchOption} objects.
208
- * @param error The error that was thrown.
209
- * @throws Throws a {@link BitcoinRpcError} with the error message.
210
- */
211
- private handleError(err: unknown, method: string, params: any[]): never {
212
- if (this.isJsonRpcError(err)) {
95
+ if (err instanceof BitcoinRpcError) throw err;
96
+ const cause = err instanceof Error ? err.message : String(err);
213
97
  throw new BitcoinRpcError(
214
- err.name.toUpperCase(),
215
- this.mapRpcCodeToHttp(err.code),
216
- `RPC ${method} failed: ${err.message}`,
217
- { method, params }
98
+ 'UNKNOWN_ERROR',
99
+ 500,
100
+ `Unknown failure in ${method}: ${cause}`,
101
+ { method }
218
102
  );
219
103
  }
220
-
221
- throw new BitcoinRpcError(
222
- 'UNKNOWN_ERROR',
223
- 500,
224
- `Unknown failure in ${method}`,
225
- { method, params, err }
226
- );
227
104
  }
228
105
 
229
106
  /**
230
107
  * Returns the block data associated with a `blockhash` of a valid block.
231
- * @param {GetBlockParams} params See {@link GetBlockParams} for details.
232
- * @param {?string} params.blockhash The blockhash of the block to query.
233
- * @param {?number} params.height The block height of the block to query.
234
- * @param {?VerbosityLevel} params.verbosity The verbosity level. See {@link VerbosityLevel}.
235
- * @returns {BlockResponse} A promise resolving to a {@link BlockResponse} formatted depending on `verbosity` level.
236
- * @throws {BitcoinRpcError} If neither `blockhash` nor `height` is provided.
237
108
  */
238
109
  public async getBlock({ blockhash, height, verbosity }: GetBlockParams): Promise<BlockResponse | undefined> {
239
- // Check if blockhash or height is provided, if neither throw an error
240
- if(!blockhash && height === undefined) {
110
+ if (!blockhash && height === undefined) {
241
111
  throw new BitcoinRpcError('INVALID_PARAMS_GET_BLOCK', 400, 'blockhash or height required', { blockhash, height });
242
112
  }
243
113
 
244
- // If height is provided, get the blockhash
245
114
  blockhash ??= await this.getBlockHash(height!);
246
- if(!blockhash || typeof blockhash !== 'string') {
115
+ if (!blockhash || typeof blockhash !== 'string') {
247
116
  return undefined;
248
117
  }
249
- // Get the block data
118
+
250
119
  const block = await this.executeRpc('getblock', [blockhash, verbosity ?? 3]);
251
120
 
252
- // Return the block data depending on verbosity level
253
- switch(verbosity) {
254
- case 0:
255
- return block as BlockV0;
256
- case 1:
257
- return block as BlockV1;
258
- case 2:
259
- return block as BlockV2;
260
- case 3:
261
- return block as BlockV3;
262
- default:
263
- return block as BlockV3;
121
+ switch (verbosity) {
122
+ case 0: return block as BlockV0;
123
+ case 1: return block as BlockV1;
124
+ case 2: return block as BlockV2;
125
+ case 3: return block as BlockV3;
126
+ default: return block as BlockV3;
264
127
  }
265
128
  }
266
129
 
267
- /**
268
- * Returns the blockheight of the most-work fully-validated chain. The genesis block has height 0.
269
- * @returns {Blockheight} The number of the blockheight with the most-work of the fully-validated chain.
270
- */
130
+ /** Returns the blockheight of the most-work fully-validated chain. */
271
131
  public async getBlockCount(): Promise<number> {
272
132
  return await this.executeRpc('getblockcount');
273
133
  }
274
134
 
275
- /**
276
- * Returns the blockhash of the block at the given height in the active chain.
277
- */
135
+ /** Returns the blockhash of the block at the given height. */
278
136
  public async getBlockHash(height: number): Promise<string> {
279
137
  return await this.executeRpc('getblockhash', [height]);
280
138
  }
281
139
 
282
- /**
283
- * Returns an object containing various blockchain state info.
284
- */
140
+ /** Returns various blockchain state info. */
285
141
  public async getBlockchainInfo(): Promise<ChainInfo> {
286
- return this.executeRpc('getblockchaininfo');
142
+ return await this.executeRpc('getblockchaininfo');
287
143
  }
288
144
 
289
- /**
290
- * Sign inputs for raw transaction (serialized, hex-encoded).
291
- * The second optional argument (may be null) is an array of previous transaction outputs that
292
- * this transaction depends on but may not yet be in the block chain.
293
- * Requires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.
294
- * @param {string} hexstring The hex-encoded transaction to send.
295
- */
145
+ /** Sign inputs for raw transaction (serialized, hex-encoded). */
296
146
  public async signRawTransaction(hexstring: string): Promise<SignedRawTx> {
297
- return await this.executeRpc<SignedRawTx>('signrawtransactionwithwallet', [hexstring]);
147
+ return await this.executeRpc('signrawtransactionwithwallet', [hexstring]);
298
148
  }
299
149
 
300
- /**
301
- * Submit a raw transaction (serialized, hex-encoded) to local node and network.
302
- *
303
- * The transaction will be sent unconditionally to all peers, so using sendrawtransaction
304
- * for manual rebroadcast may degrade privacy by leaking the transaction's origin, as
305
- * nodes will normally not rebroadcast non-wallet transactions already in their mempool.
306
- *
307
- * @param {string} hexstring The hex-encoded transaction to send.
308
- * @param {numbner} [maxfeerate] If not passed, default is 0.10.
309
- * @returns {Promise<string>} A promise resolving to the transaction hash in hex.
310
- */
150
+ /** Submit a raw transaction (serialized, hex-encoded) to local node and network. */
311
151
  public async sendRawTransaction(
312
152
  hexstring: string,
313
153
  maxfeerate?: number | string,
314
154
  maxBurnAmount?: number | string
315
155
  ): Promise<string> {
316
- return await this.executeRpc<string>('sendrawtransaction', [hexstring, maxfeerate ?? 0.10, maxBurnAmount ?? 0.00]);
156
+ return await this.executeRpc('sendrawtransaction', [hexstring, maxfeerate ?? 0.10, maxBurnAmount ?? 0.00]);
317
157
  }
318
158
 
319
- /**
320
- * Combines calls to `signRawTransaction` and `sendRawTransaction`.
321
- * @param {string} params.hexstring The hex-encoded transaction to send.
322
- * @returns {Promise<string>} A promise resolving to the transaction hash in hex.
323
- */
159
+ /** Signs and sends a raw transaction. */
324
160
  public async signAndSendRawTransaction(hexstring: string): Promise<string> {
325
- const signedRawTx = await this.signRawTransaction(hexstring,);
161
+ const signedRawTx = await this.signRawTransaction(hexstring);
162
+ if (!signedRawTx.complete) {
163
+ throw new BitcoinRpcError(
164
+ 'SIGNING_INCOMPLETE',
165
+ 400,
166
+ 'Transaction signing incomplete',
167
+ signedRawTx.errors
168
+ );
169
+ }
326
170
  return await this.sendRawTransaction(signedRawTx.hex);
327
171
  }
328
172
 
329
- /**
330
- * Combines calls to `createRawTransaction`, `signRawTransaction` and `sendRawTransaction`.
331
- * @param {CreateRawTxInputs[]} inputs The inputs to the transaction (required).
332
- * @param {CreateRawTxOutputs[]} outputs The outputs of the transaction (required).
333
- * @returns {Promise<string>} A promise resolving to the transaction hash in hex.
334
- */
173
+ /** Creates, signs, and sends a raw transaction. */
335
174
  public async createSignSendRawTransaction(inputs: CreateRawTxInputs[], outputs: CreateRawTxOutputs[]): Promise<string> {
336
175
  const rawTx = await this.createRawTransaction(inputs, outputs);
337
176
  const signedRawTx = await this.signRawTransaction(rawTx);
338
- const sentRawTx = await this.sendRawTransaction(signedRawTx.hex);
339
- return sentRawTx;
177
+ if (!signedRawTx.complete) {
178
+ throw new BitcoinRpcError(
179
+ 'SIGNING_INCOMPLETE',
180
+ 400,
181
+ 'Transaction signing incomplete',
182
+ signedRawTx.errors
183
+ );
184
+ }
185
+ return await this.sendRawTransaction(signedRawTx.hex);
340
186
  }
341
187
 
342
- /**
343
- * Returns up to 'count' most recent transactions skipping the first 'from' transactions for account 'label'.
344
- * @param {ListTransactionsParams} params The parameters for the listTransactions command.
345
- * @returns {Promise<ListTransactionsResult>} A promise resolving to a {@link ListTransactionsResult} object.
346
- */
188
+ /** Returns up to 'count' most recent transactions. */
347
189
  public async listTransactions(params: ListTransactionsParams): Promise<ListTransactionsResult> {
348
- return await this.executeRpc('listtransactions', [params]);
190
+ return await this.executeRpc('listtransactions', [
191
+ params.account ?? '*',
192
+ params.count ?? 10,
193
+ params.skip ?? 0,
194
+ params.include_watchonly ?? false,
195
+ ]);
349
196
  }
350
197
 
351
- /**
352
- * Create a transaction spending the given inputs and creating new outputs.
353
- * Outputs can be addresses or data.
354
- * Returns hex-encoded raw transaction.
355
- * Note that the transaction's inputs are not signed, and
356
- * it is not stored in the wallet or transmitted to the network.
357
- * @param {TxInForCreateRaw[]} inputs The inputs to the transaction (required).
358
- * @param {CreateRawTxOutputs[]} outputs The outputs of the transaction (required).
359
- * @param {number} [locktime] The locktime of the transaction (optional).
360
- * @param {boolean} [replacable] Whether the transaction is replaceable (optional).
361
- * @returns {string} The hex-encoded raw transaction.
362
- */
198
+ /** Create a transaction spending the given inputs and creating new outputs. */
363
199
  public async createRawTransaction(inputs: CreateRawTxInputs[], outputs: CreateRawTxOutputs[], locktime?: number, replacable?: boolean): Promise<string> {
364
- return await this.executeRpc<string>('createrawtransaction', [inputs, outputs, locktime, replacable]);
200
+ return await this.executeRpc('createrawtransaction', [inputs, outputs, locktime, replacable]);
365
201
  }
366
202
 
367
- /**
368
- * Derives one or more addresses corresponding to an output descriptor.
369
- * Examples of output descriptors are:
370
- * pkh(<pubkey>) P2PKH outputs for the given pubkey
371
- * wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey
372
- * sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys
373
- * raw(<hex script>) Outputs whose output script equals the specified hex-encoded bytes
374
- * tr(<pubkey>,multi_a(<n>,<pubkey>,<pubkey>,...)) P2TR-multisig outputs for the given threshold and pubkeys
375
- *
376
- * In the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one
377
- * or more path elements separated by "/", where "h" represents a hardened child key.
378
- *
379
- * See {@link https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md | github.com/bitcoin/bitcoin/descriptors.md}
380
- * for more information.
381
- * @param {string} descriptor The descriptor.
382
- * @param {Array<number>} range If descriptor is ranged, must specify end or [begin,end] to derive.
383
- * @returns {Array<DerivedAddresses>} a list of derived addresses
384
- * @example First three native segwit receive addresses
385
- * ```
386
- * const bitcoind = BitcoinRpcClient.connect()
387
- * const addresses = bitcoind.deriveAddresses("wpkh([d34db33f/84h/0h/0h]xpub6DJ2dN.../0/*)#cjjspncu", [0,2])
388
- * ```
389
- */
203
+ /** Derives one or more addresses corresponding to an output descriptor. */
390
204
  public async deriveAddresses(descriptor: string, range?: Array<number>): Promise<Array<DerivedAddresses>> {
391
205
  return await this.executeRpc('deriveaddresses', [descriptor, range]);
392
206
  }
393
207
 
394
- /**
395
- * Returns the total available balance. The available balance is what the wallet
396
- * considers currently spendable, and is thus affected by options which limit
397
- * spendability such as -spendzeroconfchange.
398
- * @returns {Promise<number>} A promise resolving to the total available balance in BTC.
399
- */
208
+ /** Returns the total available balance. */
400
209
  public async getBalance(): Promise<number> {
401
210
  return await this.executeRpc('getbalance');
402
211
  }
403
212
 
404
- /**
405
- * Returns a new Bitcoin address for receiving payments. If 'label' is specified,
406
- * it is added to the address book so payments received with the address will be associated with 'label'.
407
- * The 'address_type' can be one of "legacy", "p2sh-segwit", "bech32", or "bech32m".
408
- * @param {string} addressType The address type to use (required, options=["legacy", "p2sh-segwit", "bech32", "bech32m"], default="bech32").
409
- * @param {string} [label] The label to associate with the new address (optional).
410
- * @returns {Promise<string>} A promise resolving to the new address.
411
- */
213
+ /** Returns a new Bitcoin address for receiving payments. */
412
214
  public async getNewAddress(addressType: string, label?: string): Promise<string> {
413
215
  return await this.executeRpc('getnewaddress', [label ?? '', addressType]);
414
216
  }
415
217
 
416
- /**
417
- * Returns array of unspent transaction outputs with between minconf and maxconf (inclusive) confirmations.
418
- * Optionally filter to only include txouts paid to specified addresses.
419
- * @param {Object} params The parameters for the listUnspent command.
420
- * @param {number} [params.minconf=0] The minimum number of confirmations an output must have to be included.
421
- * @param {number} [params.maxconf=9999999] The maximum number of confirmations an output can have to be included.
422
- * @param {string[]} [params.address] Only include outputs paid to these addresses.
423
- * @param {boolean} [params.include_unsafe=true] Whether to include outputs that are not safe to spend.
424
- * @returns {Promise<UnspentTxInfo[]>} A promise resolving to an array of {@link UnspentTxInfo} objects.
425
- */
218
+ /** Returns array of unspent transaction outputs. */
426
219
  public async listUnspent(params: {
427
- minconf?: number;
428
- maxconf?: number;
429
- address?: string[];
430
- include_unsafe?: boolean;
220
+ minconf?: number;
221
+ maxconf?: number;
222
+ address?: string[];
223
+ include_unsafe?: boolean;
431
224
  }): Promise<UnspentTxInfo[]> {
432
- const args = { minconf: 0, maxconf: 9999999, include_unsafe: true, ...params };
433
- return await this.executeRpc('listunspent', [args]);
225
+ return await this.executeRpc('listunspent', [
226
+ params.minconf ?? 0,
227
+ params.maxconf ?? 9999999,
228
+ params.address ?? [],
229
+ params.include_unsafe ?? true,
230
+ ]);
434
231
  }
435
232
 
436
- /**
437
- * Send an amount to a given address.
438
- * @param {string} address The address to send to.
439
- * @param {number} amount The amount to send in BTC.
440
- * @returns {Promise<SendToAddressResult>} A promise resolving to the transaction id.
441
- */
233
+ /** Send an amount to a given address. */
442
234
  public async sendToAddress(address: string, amount: number): Promise<RawTransactionV2> {
443
- const txid = await this.executeRpc<string>('sendtoaddress', [address, amount]);
235
+ const txid = await this.executeRpc('sendtoaddress', [address, amount]);
444
236
  return await this.getRawTransaction(txid) as RawTransactionV2;
445
237
  }
446
238
 
447
- /**
448
- * Sign a message with the private key of an address.
449
- * @param {string} address The address to sign the message with.
450
- * @param {string} message The message to sign.
451
- * @returns {Promise<string>} A promise resolving to the signature in base64.
452
- */
239
+ /** Sign a message with the private key of an address. */
453
240
  public async signMessage(address: string, message: string): Promise<string> {
454
241
  return await this.executeRpc('signmessage', [address, message]);
455
242
  }
456
243
 
457
- /**
458
- * Verify a signed message.
459
- * @param {string} address The address to verify the message with.
460
- * @param {string} signature The signature to verify in base64.
461
- * @param {string} message The message to verify.
462
- * @returns {Promise<boolean>} A promise resolving to true if the signature is valid, false otherwise.
463
- */
244
+ /** Verify a signed message. */
464
245
  public async verifyMessage(address: string, signature: string, message: string): Promise<boolean> {
465
246
  return await this.executeRpc('verifymessage', [address, signature, message]);
466
247
  }
467
248
 
468
- /**
469
- * Get detailed information about in-wallet transaction <txid>.
470
- * @param txid: The transaction id. (string, required)
471
- * @param {boolean} include_watchonly Whether to include watch-only addresses in balance calculation and details.
472
- * @returns {WalletTransaction} A promise resolving to a {@link WalletTransaction} object.
473
- */
249
+ /** Get detailed information about in-wallet transaction. */
474
250
  public async getTransaction(txid: string, include_watchonly?: boolean): Promise<WalletTransaction> {
475
251
  return await this.executeRpc('gettransaction', [txid, include_watchonly]);
476
252
  }
477
253
 
478
- /**
479
- * Get detailed information about a transaction.
480
- * By default, this call only returns a transaction if it is in the mempool. If -txindex is enabled
481
- * and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.
482
- * If a blockhash argument is passed, it will return the transaction if the specified block is available and
483
- * the transaction is in that block.
484
- * @param {string} txid The transaction id (required).
485
- * @param {?VerbosityLevel} verbosity Response format: 0 (hex), 1 (json) or 2 (jsonext).
486
- * @param {?string} blockhash The block in which to look for the transaction (optional).
487
- * @returns {GetRawTransaction} A promise resolving to data about a transaction in the form specified by verbosity.
488
- */
254
+ /** Get detailed information about a transaction. */
489
255
  public async getRawTransaction(txid: string, verbosity?: VerbosityLevel, blockhash?: string): Promise<RawTransactionResponse> {
490
- // Get the raw transaction
491
256
  const rawTransaction = await this.executeRpc('getrawtransaction', [txid, verbosity ?? 2, blockhash]);
492
- // Return the raw transaction based on verbosity
493
- switch(verbosity) {
494
- case 0:
495
- return rawTransaction as RawTransactionV0;
496
- case 1:
497
- return rawTransaction as RawTransactionV1;
498
- case 2:
499
- return rawTransaction as RawTransactionV2;
500
- default:
501
- return rawTransaction as RawTransactionV2;
257
+ switch (verbosity) {
258
+ case 0: return rawTransaction as RawTransactionV0;
259
+ case 1: return rawTransaction as RawTransactionV1;
260
+ case 2: return rawTransaction as RawTransactionV2;
261
+ default: return rawTransaction as RawTransactionV2;
502
262
  }
503
263
  }
504
264
 
505
- /**
506
- * Get detailed information about multiple transactions. An extension of {@link getRawTransaction}.
507
- * @param {Array<string>} txids An array of transaction ids.
508
- * @param {?VerbosityLevel} verbosity Response format: 0 (hex), 1 (json) or 2 (jsonext).
509
- * @returns {Promise<Array<RawTransactionResponse>>}
510
- */
265
+ /** Get detailed information about multiple transactions using JSON-RPC batching. */
511
266
  public async getRawTransactions(txids: string[], verbosity?: VerbosityLevel): Promise<RawTransactionResponse[]> {
512
- return await Promise.all(
513
- txids.map(
514
- async (txid) => await this.getRawTransaction(txid, verbosity ?? 2)
515
- )
267
+ const v = verbosity ?? 2;
268
+ const results = await this.#transport.batch(
269
+ txids.map(txid => ({ method: 'getrawtransaction', params: [txid, v] as unknown[] }))
516
270
  );
271
+ return results.map(raw => {
272
+ const normalized = JSONUtils.isUnprototyped(raw) ? JSONUtils.normalize(raw) : raw;
273
+ switch (v) {
274
+ case 0: return normalized as RawTransactionV0;
275
+ case 1: return normalized as RawTransactionV1;
276
+ case 2: return normalized as RawTransactionV2;
277
+ default: return normalized as RawTransactionV2;
278
+ }
279
+ });
517
280
  }
518
-
519
- }
281
+ }