opnet 1.8.2 → 1.8.4

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 (55) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/browser/_version.d.ts +1 -1
  3. package/browser/contracts/CallResult.d.ts +1 -0
  4. package/browser/contracts/interfaces/IProviderForCallResult.d.ts +2 -0
  5. package/browser/index.js +9930 -10100
  6. package/browser/noble-curves.js +3049 -2135
  7. package/browser/noble-hashes.js +1812 -2504
  8. package/browser/opnet.d.ts +1 -0
  9. package/browser/protobuf.js +2695 -2103
  10. package/browser/providers/AbstractRpcProvider.d.ts +2 -0
  11. package/browser/providers/interfaces/JSONRpcMethods.d.ts +1 -0
  12. package/browser/providers/websocket/types/WebSocketOpcodes.d.ts +2 -0
  13. package/browser/rolldown-runtime.js +35 -0
  14. package/browser/transactions/interfaces/BroadcastedTransactionPackage.d.ts +48 -0
  15. package/browser/utxos/UTXOsManager.d.ts +1 -0
  16. package/browser/vendors.js +36998 -45617
  17. package/browser/worker_threads-browser.js +7 -9
  18. package/build/_version.d.ts +1 -1
  19. package/build/_version.js +1 -1
  20. package/build/contracts/CallResult.d.ts +1 -0
  21. package/build/contracts/CallResult.js +59 -30
  22. package/build/contracts/interfaces/IProviderForCallResult.d.ts +2 -0
  23. package/build/opnet.d.ts +1 -0
  24. package/build/opnet.js +1 -0
  25. package/build/providers/AbstractRpcProvider.d.ts +2 -0
  26. package/build/providers/AbstractRpcProvider.js +15 -2
  27. package/build/providers/WebsocketRpcProvider.js +5 -0
  28. package/build/providers/interfaces/JSONRpcMethods.d.ts +1 -0
  29. package/build/providers/interfaces/JSONRpcMethods.js +1 -0
  30. package/build/providers/websocket/MethodMapping.js +6 -0
  31. package/build/providers/websocket/types/WebSocketOpcodes.d.ts +2 -0
  32. package/build/providers/websocket/types/WebSocketOpcodes.js +2 -0
  33. package/build/transactions/interfaces/BroadcastedTransactionPackage.d.ts +48 -0
  34. package/build/transactions/interfaces/BroadcastedTransactionPackage.js +1 -0
  35. package/build/tsconfig.build.tsbuildinfo +1 -1
  36. package/build/utxos/UTXOsManager.d.ts +1 -0
  37. package/build/utxos/UTXOsManager.js +37 -28
  38. package/docs/api-reference/provider-api.md +16 -0
  39. package/docs/api-reference/types-interfaces.md +91 -0
  40. package/docs/api-reference/utxo-manager-api.md +4 -2
  41. package/docs/svg/tx-broadcast-flow.svg +201 -0
  42. package/docs/transactions/broadcasting.md +124 -9
  43. package/package.json +18 -13
  44. package/src/_version.ts +1 -1
  45. package/src/contracts/CallResult.ts +92 -35
  46. package/src/contracts/Contract.ts +11 -4
  47. package/src/contracts/interfaces/IProviderForCallResult.ts +5 -0
  48. package/src/opnet.ts +1 -0
  49. package/src/providers/AbstractRpcProvider.ts +52 -5
  50. package/src/providers/WebsocketRpcProvider.ts +7 -0
  51. package/src/providers/interfaces/JSONRpcMethods.ts +1 -0
  52. package/src/providers/websocket/MethodMapping.ts +6 -0
  53. package/src/providers/websocket/types/WebSocketOpcodes.ts +2 -0
  54. package/src/transactions/interfaces/BroadcastedTransactionPackage.ts +72 -0
  55. package/src/utxos/UTXOsManager.ts +82 -46
@@ -11,6 +11,7 @@ export declare class UTXOsManager {
11
11
  getUTXOs({ address, isCSV, optimize, mergePendingUTXOs, filterSpentUTXOs, olderThan, }: RequestUTXOsParams): Promise<UTXOs>;
12
12
  getUTXOsForAmount({ address, amount, csvAddress, optimize, mergePendingUTXOs, filterSpentUTXOs, throwErrors, olderThan, maxUTXOs, throwIfUTXOsLimitReached, }: RequestUTXOsParamsWithAmount): Promise<UTXOs>;
13
13
  getMultipleUTXOs({ requests, mergePendingUTXOs, filterSpentUTXOs, }: RequestMultipleUTXOsParams): Promise<Record<string, UTXOs>>;
14
+ private selectUTXOsGreedily;
14
15
  private fetchMultipleUTXOs;
15
16
  private getAddressData;
16
17
  private maybeFetchUTXOs;
@@ -96,44 +96,31 @@ export class UTXOsManager {
96
96
  return finalUTXOs;
97
97
  }
98
98
  async getUTXOsForAmount({ address, amount, csvAddress, optimize = true, mergePendingUTXOs = true, filterSpentUTXOs = true, throwErrors = false, olderThan, maxUTXOs = 5000, throwIfUTXOsLimitReached = false, }) {
99
- const utxosPromises = [];
100
- if (csvAddress) {
101
- utxosPromises.push(this.getUTXOs({
99
+ const selected = [];
100
+ let currentValue = 0n;
101
+ const normalUTXOs = await this.getUTXOs({
102
+ address,
103
+ optimize,
104
+ mergePendingUTXOs,
105
+ filterSpentUTXOs,
106
+ olderThan,
107
+ });
108
+ currentValue = this.selectUTXOsGreedily(normalUTXOs, selected, currentValue, amount, maxUTXOs, throwIfUTXOsLimitReached);
109
+ if (currentValue < amount && csvAddress) {
110
+ const csvUTXOs = await this.getUTXOs({
102
111
  address: csvAddress,
103
112
  optimize: true,
104
113
  mergePendingUTXOs: false,
105
114
  filterSpentUTXOs: true,
106
115
  olderThan: 1n,
107
116
  isCSV: true,
108
- }));
109
- }
110
- utxosPromises.push(this.getUTXOs({
111
- address,
112
- optimize,
113
- mergePendingUTXOs,
114
- filterSpentUTXOs,
115
- olderThan,
116
- }));
117
- const combinedUTXOs = (await Promise.all(utxosPromises)).flat();
118
- const utxoUntilAmount = [];
119
- let currentValue = 0n;
120
- for (const utxo of combinedUTXOs) {
121
- if (maxUTXOs && utxoUntilAmount.length >= maxUTXOs) {
122
- if (throwIfUTXOsLimitReached) {
123
- throw new Error(`Woah. You must consolidate your UTXOs (${combinedUTXOs.length})! This transaction is too large.`);
124
- }
125
- break;
126
- }
127
- utxoUntilAmount.push(utxo);
128
- currentValue += utxo.value;
129
- if (currentValue >= amount) {
130
- break;
131
- }
117
+ });
118
+ currentValue = this.selectUTXOsGreedily(csvUTXOs, selected, currentValue, amount, maxUTXOs, throwIfUTXOsLimitReached);
132
119
  }
133
120
  if (currentValue < amount && throwErrors) {
134
121
  throw new Error(`Insufficient UTXOs to cover amount. Available: ${currentValue}, Needed: ${amount}`);
135
122
  }
136
- return utxoUntilAmount;
123
+ return selected;
137
124
  }
138
125
  async getMultipleUTXOs({ requests, mergePendingUTXOs = true, filterSpentUTXOs = true, }) {
139
126
  if (requests.length === 0) {
@@ -190,6 +177,28 @@ export class UTXOsManager {
190
177
  }
191
178
  return result;
192
179
  }
180
+ selectUTXOsGreedily(candidates, selected, currentValue, amount, maxUTXOs, throwIfLimitReached) {
181
+ candidates.sort((a, b) => {
182
+ if (b.value > a.value)
183
+ return 1;
184
+ if (b.value < a.value)
185
+ return -1;
186
+ return 0;
187
+ });
188
+ for (const utxo of candidates) {
189
+ if (currentValue >= amount)
190
+ break;
191
+ if (maxUTXOs && selected.length >= maxUTXOs) {
192
+ if (throwIfLimitReached) {
193
+ throw new Error(`Woah. You must consolidate your UTXOs (${candidates.length + selected.length})! This transaction is too large.`);
194
+ }
195
+ break;
196
+ }
197
+ selected.push(utxo);
198
+ currentValue += utxo.value;
199
+ }
200
+ return currentValue;
201
+ }
193
202
  async fetchMultipleUTXOs(requests) {
194
203
  const payloads = requests.map((request) => {
195
204
  const data = [request.address, request.optimize ?? true];
@@ -218,6 +218,22 @@ Broadcast multiple transactions.
218
218
  sendRawTransactions(txs: string[]): Promise<BroadcastedTransaction[]>
219
219
  ```
220
220
 
221
+ ### sendRawTransactionPackage
222
+
223
+ Broadcast a package of raw transactions atomically via Bitcoin Core's `submitpackage` RPC, or fall back to validated sequential broadcast.
224
+
225
+ ```typescript
226
+ sendRawTransactionPackage(
227
+ txs: string[],
228
+ isPackage?: boolean
229
+ ): Promise<BroadcastedTransactionPackage>
230
+ ```
231
+
232
+ | Parameter | Type | Default | Description |
233
+ |-----------|------|---------|-------------|
234
+ | `txs` | `string[]` | Required | Raw transactions as hex strings (max 25) |
235
+ | `isPackage` | `boolean` | `true` | Use atomic `submitpackage` (`true`) or validated sequential broadcast (`false`) |
236
+
221
237
  ### getChallenge
222
238
 
223
239
  Get the current PoW challenge.
@@ -274,6 +274,97 @@ interface BroadcastedTransaction {
274
274
  }
275
275
  ```
276
276
 
277
+ ### BroadcastedTransactionPackage
278
+
279
+ Result of a package broadcast via [`sendRawTransactionPackage()`](./provider-api.md#sendrawtransactionpackage).
280
+
281
+ ```typescript
282
+ interface BroadcastedTransactionPackage {
283
+ /** Whether the overall package broadcast succeeded. */
284
+ readonly success: boolean;
285
+ /** Error message if the broadcast failed. */
286
+ readonly error?: string;
287
+ /** Present when testMempoolAccept was used (sequential or single tx path). */
288
+ readonly testResults?: readonly TestMempoolAcceptResult[];
289
+ /** Present when submitPackage was used successfully. */
290
+ readonly packageResult?: PackageResult;
291
+ /** Per-transaction results for sequential or single tx broadcasts. */
292
+ readonly sequentialResults?: readonly SequentialBroadcastTxResult[];
293
+ /** True when submitPackage failed and the node fell back to sequential broadcast. */
294
+ readonly fellBackToSequential?: boolean;
295
+ }
296
+ ```
297
+
298
+ ### SequentialBroadcastTxResult
299
+
300
+ ```typescript
301
+ interface SequentialBroadcastTxResult {
302
+ readonly txid: string;
303
+ readonly success: boolean;
304
+ readonly error?: string;
305
+ readonly peers?: number;
306
+ }
307
+ ```
308
+
309
+ ### TestMempoolAcceptResult
310
+
311
+ ```typescript
312
+ interface TestMempoolAcceptResult {
313
+ readonly txid: string;
314
+ readonly wtxid: string;
315
+ readonly allowed?: boolean;
316
+ readonly vsize?: number;
317
+ readonly packageError?: string;
318
+ readonly rejectReason?: string;
319
+ readonly rejectDetails?: string;
320
+ readonly fees?: TestMempoolAcceptFees;
321
+ }
322
+ ```
323
+
324
+ ### TestMempoolAcceptFees
325
+
326
+ ```typescript
327
+ interface TestMempoolAcceptFees {
328
+ readonly base: number;
329
+ readonly effectiveFeerate: number;
330
+ readonly effectiveIncludes: readonly string[];
331
+ }
332
+ ```
333
+
334
+ ### PackageResult
335
+
336
+ ```typescript
337
+ interface PackageResult {
338
+ readonly packageMsg: string;
339
+ readonly txResults: {
340
+ readonly [wtxid: string]: PackageTxResult;
341
+ };
342
+ readonly replacedTransactions?: readonly string[];
343
+ }
344
+ ```
345
+
346
+ ### PackageTxResult
347
+
348
+ ```typescript
349
+ interface PackageTxResult {
350
+ readonly txid: string;
351
+ readonly otherWtxid?: string;
352
+ readonly vsize?: number;
353
+ readonly fees?: PackageTxFees;
354
+ readonly error?: string;
355
+ }
356
+ ```
357
+
358
+ ### PackageTxFees
359
+
360
+ ```typescript
361
+ interface PackageTxFees {
362
+ readonly base: number;
363
+ readonly effectiveFeerate?: number;
364
+ readonly effectiveIncludes?: readonly string[];
365
+ }
366
+ ```
367
+
277
368
  ---
278
369
 
279
370
  ## Contract Types
@@ -53,12 +53,14 @@ async getUTXOsForAmount(
53
53
  interface RequestUTXOsParamsWithAmount extends RequestUTXOsParams {
54
54
  readonly amount: bigint; // Amount to cover
55
55
  readonly throwErrors?: boolean; // Throw if insufficient
56
- readonly csvAddress?: string; // CSV address for priority
57
- readonly maxUTXOs?: number; // Max UTXOs to select
56
+ readonly csvAddress?: string; // CSV address as fallback
57
+ readonly maxUTXOs?: number; // Max UTXOs to select (default: 5000)
58
58
  readonly throwIfUTXOsLimitReached?: boolean; // Throw if limit reached
59
59
  }
60
60
  ```
61
61
 
62
+ > **Note:** `getUTXOsForAmount` prioritizes normal UTXOs first and only falls back to CSV UTXOs if normal ones cannot cover the requested amount. UTXOs are sorted by value descending (largest first) for greedy selection.
63
+
62
64
  ### spentUTXO
63
65
 
64
66
  Mark UTXOs as spent and register new UTXOs (for tracking).
@@ -0,0 +1,201 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 860 1080" font-family="'Segoe UI','Helvetica Neue',sans-serif">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="0.3" y2="1">
4
+ <stop offset="0%" stop-color="#1a1520"/>
5
+ <stop offset="50%" stop-color="#151119"/>
6
+ <stop offset="100%" stop-color="#0e0b12"/>
7
+ </linearGradient>
8
+ <linearGradient id="card" x1="0" y1="0" x2="0" y2="1">
9
+ <stop offset="0%" stop-color="#2a2233"/>
10
+ <stop offset="100%" stop-color="#1f1928"/>
11
+ </linearGradient>
12
+ <linearGradient id="entryGrad" x1="0" y1="0" x2="1" y2="1">
13
+ <stop offset="0%" stop-color="#c084fc"/>
14
+ <stop offset="100%" stop-color="#a855f7"/>
15
+ </linearGradient>
16
+ <filter id="softGlow">
17
+ <feGaussianBlur stdDeviation="6" result="blur"/>
18
+ <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
19
+ </filter>
20
+ <filter id="cardShadow">
21
+ <feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.35"/>
22
+ </filter>
23
+ <marker id="arrW" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto">
24
+ <path d="M0,0 L7,2.5 L0,5" fill="none" stroke="#7c6f8a" stroke-width="1.2" stroke-linejoin="round"/>
25
+ </marker>
26
+ <marker id="arrG" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto">
27
+ <path d="M0,0 L7,2.5 L0,5" fill="none" stroke="#4ade80" stroke-width="1.2" stroke-linejoin="round"/>
28
+ </marker>
29
+ <marker id="arrR" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto">
30
+ <path d="M0,0 L7,2.5 L0,5" fill="none" stroke="#fb7185" stroke-width="1.2" stroke-linejoin="round"/>
31
+ </marker>
32
+ <marker id="arrO" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto">
33
+ <path d="M0,0 L7,2.5 L0,5" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-linejoin="round"/>
34
+ </marker>
35
+ </defs>
36
+
37
+ <!-- Background -->
38
+ <rect width="860" height="1080" fill="url(#bg)" rx="16"/>
39
+
40
+ <!-- Decorative ambient circles -->
41
+ <circle cx="780" cy="80" r="120" fill="#a855f7" opacity="0.03"/>
42
+ <circle cx="60" cy="950" r="160" fill="#4ade80" opacity="0.025"/>
43
+ <circle cx="430" cy="540" r="300" fill="#a855f7" opacity="0.012"/>
44
+
45
+ <!-- ====== TITLE ====== -->
46
+ <text x="430" y="52" text-anchor="middle" fill="#e8dff0" font-size="22" font-weight="600" letter-spacing="0.5">How Transactions Get Broadcast</text>
47
+ <text x="430" y="76" text-anchor="middle" fill="#7c6f8a" font-size="13">From your wallet to the Bitcoin network</text>
48
+
49
+ <!-- ====== ENTRY PILL ====== -->
50
+ <rect x="295" y="104" width="270" height="52" rx="26" fill="url(#entryGrad)" filter="url(#softGlow)" opacity="0.9"/>
51
+ <text x="430" y="127" text-anchor="middle" fill="#fff" font-size="14" font-weight="600">You submit transactions</text>
52
+ <text x="430" y="145" text-anchor="middle" fill="#ede2ff" font-size="11" opacity="0.85">one or more, as an ordered batch</text>
53
+
54
+ <!-- Arrow down -->
55
+ <line x1="430" y1="156" x2="430" y2="194" stroke="#7c6f8a" stroke-width="1.5" marker-end="url(#arrW)"/>
56
+
57
+ <!-- ====== DECISION: How many? ====== -->
58
+ <rect x="320" y="198" width="220" height="48" rx="24" fill="url(#card)" stroke="#3d3350" stroke-width="1" filter="url(#cardShadow)"/>
59
+ <text x="430" y="220" text-anchor="middle" fill="#d4bfea" font-size="13" font-weight="500">How many transactions?</text>
60
+ <text x="430" y="237" text-anchor="middle" fill="#7c6f8a" font-size="11">checking the batch size</text>
61
+
62
+ <!-- ====== PATH A: Just One (left branch) ====== -->
63
+ <line x1="320" y1="222" x2="150" y2="222" stroke="#4ade80" stroke-width="1.5" marker-end="url(#arrG)"/>
64
+ <rect x="128" y="206" width="48" height="20" rx="10" fill="#1a2e1a"/>
65
+ <text x="152" y="220" text-anchor="middle" fill="#4ade80" font-size="10" font-weight="600">JUST 1</text>
66
+
67
+ <rect x="22" y="258" width="220" height="120" rx="14" fill="url(#card)" stroke="#2d4a2d" stroke-width="1" filter="url(#cardShadow)"/>
68
+ <circle cx="50" cy="286" r="12" fill="#1a2e1a" stroke="#4ade80" stroke-width="1"/>
69
+ <text x="50" y="291" text-anchor="middle" fill="#4ade80" font-size="14">1</text>
70
+ <text x="72" y="291" fill="#d4bfea" font-size="13" font-weight="500">Simple Path</text>
71
+ <line x1="38" y1="306" x2="226" y2="306" stroke="#2d4a2d" stroke-width="0.7"/>
72
+ <text x="132" y="326" text-anchor="middle" fill="#b8a5cc" font-size="11.5">First, validate the transaction</text>
73
+ <text x="132" y="344" text-anchor="middle" fill="#b8a5cc" font-size="11.5">against the mempool.</text>
74
+ <text x="132" y="365" text-anchor="middle" fill="#b8a5cc" font-size="11.5">If it passes, send it out.</text>
75
+
76
+ <line x1="132" y1="378" x2="132" y2="410" stroke="#4ade80" stroke-width="1.2" marker-end="url(#arrG)"/>
77
+ <rect x="82" y="414" width="100" height="30" rx="15" fill="#0f2a14" stroke="#22c55e" stroke-width="1"/>
78
+ <text x="132" y="434" text-anchor="middle" fill="#4ade80" font-size="12" font-weight="500">Done</text>
79
+
80
+ <!-- ====== Multiple txs (down branch) ====== -->
81
+ <line x1="430" y1="246" x2="430" y2="294" stroke="#7c6f8a" stroke-width="1.5" marker-end="url(#arrW)"/>
82
+ <rect x="380" y="252" width="82" height="20" rx="10" fill="#1f1928"/>
83
+ <text x="421" y="266" text-anchor="middle" fill="#b8a5cc" font-size="10">MULTIPLE</text>
84
+
85
+ <!-- ====== DECISION: Package mode? ====== -->
86
+ <rect x="310" y="298" width="240" height="48" rx="24" fill="url(#card)" stroke="#3d3350" stroke-width="1" filter="url(#cardShadow)"/>
87
+ <text x="430" y="320" text-anchor="middle" fill="#d4bfea" font-size="13" font-weight="500">Try package mode?</text>
88
+ <text x="430" y="337" text-anchor="middle" fill="#7c6f8a" font-size="11">on by default</text>
89
+
90
+ <!-- ====== PATH B: Package Submission (right branch) ====== -->
91
+ <line x1="550" y1="322" x2="618" y2="322" stroke="#4ade80" stroke-width="1.5" marker-end="url(#arrG)"/>
92
+ <rect x="560" y="306" width="44" height="20" rx="10" fill="#1a2e1a"/>
93
+ <text x="582" y="320" text-anchor="middle" fill="#4ade80" font-size="10" font-weight="600">YES</text>
94
+
95
+ <rect x="622" y="270" width="216" height="142" rx="14" fill="url(#card)" stroke="#2d4a2d" stroke-width="1" filter="url(#cardShadow)"/>
96
+ <circle cx="650" cy="298" r="12" fill="#1a2e1a" stroke="#4ade80" stroke-width="1"/>
97
+ <text x="650" y="303" text-anchor="middle" fill="#4ade80" font-size="14">2</text>
98
+ <text x="672" y="303" fill="#d4bfea" font-size="13" font-weight="500">Package Relay</text>
99
+ <line x1="638" y1="318" x2="822" y2="318" stroke="#2d4a2d" stroke-width="0.7"/>
100
+ <text x="730" y="339" text-anchor="middle" fill="#b8a5cc" font-size="11.5">Submit all transactions as</text>
101
+ <text x="730" y="357" text-anchor="middle" fill="#b8a5cc" font-size="11.5">a single atomic package.</text>
102
+ <text x="730" y="378" text-anchor="middle" fill="#b8a5cc" font-size="11.5">The network validates them</text>
103
+ <text x="730" y="396" text-anchor="middle" fill="#b8a5cc" font-size="11.5">together, including fees.</text>
104
+
105
+ <!-- Package success -->
106
+ <line x1="838" y1="341" x2="852" y2="341" stroke="#4ade80" stroke-width="1"/>
107
+ <line x1="852" y1="341" x2="852" y2="430" stroke="#4ade80" stroke-width="1"/>
108
+ <line x1="852" y1="430" x2="790" y2="430" stroke="#4ade80" stroke-width="1.2" marker-end="url(#arrG)"/>
109
+ <rect x="690" y="416" width="95" height="30" rx="15" fill="#0f2a14" stroke="#22c55e" stroke-width="1"/>
110
+ <text x="737" y="436" text-anchor="middle" fill="#4ade80" font-size="12" font-weight="500">Done</text>
111
+ <text x="848" y="390" fill="#4ade80" font-size="10" font-weight="500">OK</text>
112
+
113
+ <!-- Package failure: fallback -->
114
+ <line x1="730" y1="412" x2="730" y2="482" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="5,4" marker-end="url(#arrO)"/>
115
+ <rect x="688" y="454" width="84" height="20" rx="10" fill="#2e2510"/>
116
+ <text x="730" y="468" text-anchor="middle" fill="#fbbf24" font-size="10" font-weight="600">FAILED</text>
117
+
118
+ <rect x="640" y="486" width="180" height="56" rx="12" fill="#2a2010" stroke="#92400e" stroke-width="1" filter="url(#cardShadow)"/>
119
+ <text x="730" y="510" text-anchor="middle" fill="#fbbf24" font-size="12" font-weight="500">Falls back gracefully</text>
120
+ <text x="730" y="528" text-anchor="middle" fill="#a08050" font-size="10.5">tries the one-by-one approach</text>
121
+
122
+ <!-- Fallback arrow merges into sequential path -->
123
+ <path d="M640,514 Q560,560 500,580" fill="none" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="5,4" marker-end="url(#arrO)"/>
124
+
125
+ <!-- ====== PATH C: Sequential (down from package decision) ====== -->
126
+ <line x1="430" y1="346" x2="430" y2="488" stroke="#fb7185" stroke-width="1.5" marker-end="url(#arrR)"/>
127
+ <rect x="408" y="352" width="38" height="20" rx="10" fill="#2e1a1f"/>
128
+ <text x="427" y="366" text-anchor="middle" fill="#fb7185" font-size="10" font-weight="600">NO</text>
129
+
130
+ <!-- Merge zone label -->
131
+ <rect x="126" y="480" width="410" height="28" rx="14" fill="none" stroke="#3d3350" stroke-width="0.8" stroke-dasharray="6,4"/>
132
+ <text x="331" y="499" text-anchor="middle" fill="#7c6f8a" font-size="10.5">One-by-one approach (or fallback from package failure)</text>
133
+
134
+ <!-- Step 3: Validate all -->
135
+ <rect x="250" y="530" width="290" height="100" rx="14" fill="url(#card)" stroke="#3d2d4a" stroke-width="1" filter="url(#cardShadow)"/>
136
+ <circle cx="282" cy="560" r="14" fill="#2a1a38" stroke="#a855f7" stroke-width="1"/>
137
+ <text x="282" y="565" text-anchor="middle" fill="#c084fc" font-size="13" font-weight="600">3</text>
138
+ <text x="306" y="565" fill="#d4bfea" font-size="13" font-weight="500">Validate Everything First</text>
139
+ <line x1="266" y1="580" x2="524" y2="580" stroke="#3d2d4a" stroke-width="0.7"/>
140
+ <text x="395" y="600" text-anchor="middle" fill="#b8a5cc" font-size="11.5">Check every transaction against</text>
141
+ <text x="395" y="618" text-anchor="middle" fill="#b8a5cc" font-size="11.5">the mempool before sending any.</text>
142
+
143
+ <line x1="395" y1="630" x2="395" y2="668" stroke="#7c6f8a" stroke-width="1.5" marker-end="url(#arrW)"/>
144
+
145
+ <rect x="290" y="672" width="210" height="42" rx="21" fill="url(#card)" stroke="#3d3350" stroke-width="1"/>
146
+ <text x="395" y="698" text-anchor="middle" fill="#d4bfea" font-size="12.5">Did they all pass?</text>
147
+
148
+ <!-- Fail left -->
149
+ <line x1="290" y1="693" x2="180" y2="693" stroke="#fb7185" stroke-width="1.5" marker-end="url(#arrR)"/>
150
+ <rect x="236" y="680" width="42" height="18" rx="9" fill="#2e1a1f"/>
151
+ <text x="257" y="693" text-anchor="middle" fill="#fb7185" font-size="10" font-weight="600">NO</text>
152
+
153
+ <rect x="36" y="676" width="140" height="42" rx="12" fill="#2a0f15" stroke="#9f1239" stroke-width="1" filter="url(#cardShadow)"/>
154
+ <text x="106" y="695" text-anchor="middle" fill="#fb7185" font-size="12" font-weight="500">Rejected</text>
155
+ <text x="106" y="710" text-anchor="middle" fill="#885060" font-size="10">tells you what failed</text>
156
+
157
+ <!-- Pass down -->
158
+ <line x1="395" y1="714" x2="395" y2="758" stroke="#4ade80" stroke-width="1.5" marker-end="url(#arrG)"/>
159
+ <rect x="370" y="722" width="44" height="18" rx="9" fill="#1a2e1a"/>
160
+ <text x="392" y="735" text-anchor="middle" fill="#4ade80" font-size="10" font-weight="600">YES</text>
161
+
162
+ <!-- Step 4: Send one by one -->
163
+ <rect x="250" y="762" width="290" height="118" rx="14" fill="url(#card)" stroke="#3d2d4a" stroke-width="1" filter="url(#cardShadow)"/>
164
+ <circle cx="282" cy="792" r="14" fill="#2a1a38" stroke="#a855f7" stroke-width="1"/>
165
+ <text x="282" y="797" text-anchor="middle" fill="#c084fc" font-size="13" font-weight="600">4</text>
166
+ <text x="306" y="797" fill="#d4bfea" font-size="13" font-weight="500">Send One by One</text>
167
+ <line x1="266" y1="812" x2="524" y2="812" stroke="#3d2d4a" stroke-width="0.7"/>
168
+ <text x="395" y="832" text-anchor="middle" fill="#b8a5cc" font-size="11.5">Send each transaction in order.</text>
169
+ <text x="395" y="852" text-anchor="middle" fill="#b8a5cc" font-size="11.5">If one fails, stop immediately</text>
170
+ <text x="395" y="870" text-anchor="middle" fill="#b8a5cc" font-size="11.5">since later ones depend on it.</text>
171
+
172
+ <!-- Fail branch from send loop -->
173
+ <line x1="250" y1="840" x2="180" y2="840" stroke="#fb7185" stroke-width="1.2" marker-end="url(#arrR)"/>
174
+ <rect x="36" y="822" width="140" height="42" rx="12" fill="#2a0f15" stroke="#9f1239" stroke-width="1" filter="url(#cardShadow)"/>
175
+ <text x="106" y="841" text-anchor="middle" fill="#fb7185" font-size="12" font-weight="500">Stopped early</text>
176
+ <text x="106" y="856" text-anchor="middle" fill="#885060" font-size="10">partial results returned</text>
177
+
178
+ <!-- Loop indicator -->
179
+ <path d="M540,800 C574,800 574,864 540,864" fill="none" stroke="#7c6f8a" stroke-width="1" stroke-dasharray="3,3"/>
180
+ <text x="582" y="836" fill="#7c6f8a" font-size="10">repeats</text>
181
+
182
+ <!-- Success from send loop -->
183
+ <line x1="395" y1="880" x2="395" y2="920" stroke="#4ade80" stroke-width="1.5" marker-end="url(#arrG)"/>
184
+
185
+ <!-- Final success -->
186
+ <rect x="335" y="924" width="120" height="36" rx="18" fill="#0f2a14" stroke="#22c55e" stroke-width="1.2" filter="url(#softGlow)"/>
187
+ <text x="395" y="948" text-anchor="middle" fill="#4ade80" font-size="13" font-weight="600">All sent</text>
188
+
189
+ <!-- ====== LEGEND ====== -->
190
+ <rect x="50" y="990" width="760" height="70" rx="14" fill="#1a1520" stroke="#2a2233" stroke-width="1"/>
191
+ <circle cx="100" cy="1025" r="8" fill="#0f2a14" stroke="#22c55e" stroke-width="1"/>
192
+ <text x="118" y="1030" fill="#9a8aaa" font-size="11">Success</text>
193
+ <circle cx="220" cy="1025" r="8" fill="#2a0f15" stroke="#f43f5e" stroke-width="1"/>
194
+ <text x="238" y="1030" fill="#9a8aaa" font-size="11">Failure</text>
195
+ <circle cx="340" cy="1025" r="8" fill="#2a2010" stroke="#f59e0b" stroke-width="1"/>
196
+ <text x="358" y="1030" fill="#9a8aaa" font-size="11">Fallback</text>
197
+ <line x1="460" y1="1025" x2="490" y2="1025" stroke="#7c6f8a" stroke-width="1.5" marker-end="url(#arrW)"/>
198
+ <text x="500" y="1030" fill="#9a8aaa" font-size="11">Flow</text>
199
+ <line x1="570" y1="1025" x2="600" y2="1025" stroke="#fbbf24" stroke-width="1.5" stroke-dasharray="5,4" marker-end="url(#arrO)"/>
200
+ <text x="610" y="1030" fill="#9a8aaa" font-size="11">Graceful retry</text>
201
+ </svg>
@@ -6,15 +6,7 @@ This guide covers broadcasting raw transactions on OPNet.
6
6
 
7
7
  After building and signing a transaction, it needs to be broadcast to the network. OPNet supports both single and batch transaction broadcasting.
8
8
 
9
- ```mermaid
10
- flowchart LR
11
- A[Signed Transaction] --> B[sendRawTransaction]
12
- B --> C[Network]
13
- C --> D[BroadcastedTransaction]
14
- D --> E{Success?}
15
- E -->|Yes| F[Propagated]
16
- E -->|No| G[Error]
17
- ```
9
+ ![Transaction Broadcast Flow](../svg/tx-broadcast-flow.svg)
18
10
 
19
11
  ---
20
12
 
@@ -81,6 +73,129 @@ interface BroadcastedTransaction {
81
73
 
82
74
  ---
83
75
 
76
+ ## Send Transaction Package
77
+
78
+ ### Atomic Package Broadcasting
79
+
80
+ Use `sendRawTransactionPackage` to broadcast an ordered array of raw transactions atomically via Bitcoin Core's `submitpackage` RPC. This is ideal for CPFP (Child-Pays-For-Parent) chains or any set of dependent transactions that must be accepted together.
81
+
82
+ ```typescript
83
+ import { JSONRpcProvider, BroadcastedTransactionPackage } from 'opnet';
84
+ import { networks } from '@btc-vision/bitcoin';
85
+
86
+ const network = networks.regtest;
87
+ const provider = new JSONRpcProvider({ url: 'https://regtest.opnet.org', network });
88
+
89
+ const parentTx = '02000000000101...';
90
+ const childTx = '02000000000101...';
91
+
92
+ // Atomic package submission (uses submitpackage RPC)
93
+ const result: BroadcastedTransactionPackage = await provider.sendRawTransactionPackage(
94
+ [parentTx, childTx],
95
+ true, // isPackage=true (default) for atomic submission
96
+ );
97
+
98
+ if (result.success) {
99
+ console.log('Package broadcast successfully!');
100
+
101
+ if (result.packageResult) {
102
+ console.log('Package message:', result.packageResult.packageMsg);
103
+ for (const [wtxid, txResult] of Object.entries(result.packageResult.txResults)) {
104
+ console.log(` ${wtxid}: txid=${txResult.txid}, vsize=${txResult.vsize}`);
105
+ }
106
+ }
107
+
108
+ if (result.sequentialResults) {
109
+ for (const seq of result.sequentialResults) {
110
+ console.log(`${seq.txid}: success=${seq.success}, peers=${seq.peers}`);
111
+ }
112
+ }
113
+ } else {
114
+ console.log('Package broadcast failed:', result.error);
115
+ }
116
+ ```
117
+
118
+ ### Sequential Validated Broadcasting
119
+
120
+ Set `isPackage=false` to use validated sequential broadcast instead. This first validates all transactions with `testmempoolaccept`, then broadcasts each one individually.
121
+
122
+ ```typescript
123
+ // Sequential broadcast (testmempoolaccept + sendrawtransaction)
124
+ const result = await provider.sendRawTransactionPackage(
125
+ [tx1, tx2, tx3],
126
+ false, // sequential validated broadcast
127
+ );
128
+
129
+ if (result.success) {
130
+ if (result.testResults) {
131
+ for (const test of result.testResults) {
132
+ console.log(`${test.txid}: allowed=${test.allowed}, vsize=${test.vsize}`);
133
+ }
134
+ }
135
+
136
+ if (result.sequentialResults) {
137
+ for (const seq of result.sequentialResults) {
138
+ console.log(`${seq.txid}: success=${seq.success}, peers=${seq.peers}`);
139
+ }
140
+ }
141
+ }
142
+
143
+ // Check if the node fell back to sequential from package
144
+ if (result.fellBackToSequential) {
145
+ console.log('submitpackage failed, fell back to sequential broadcast');
146
+ }
147
+ ```
148
+
149
+ ### Method Signature
150
+
151
+ ```typescript
152
+ async sendRawTransactionPackage(
153
+ txs: string[], // Raw transactions as hex strings (max 25)
154
+ isPackage?: boolean // Use atomic submitpackage (default: true)
155
+ ): Promise<BroadcastedTransactionPackage>
156
+ ```
157
+
158
+ ---
159
+
160
+ ## BroadcastedTransactionPackage Result
161
+
162
+ ```typescript
163
+ interface BroadcastedTransactionPackage {
164
+ success: boolean; // Whether the overall broadcast succeeded
165
+ error?: string; // Error message if failed
166
+ testResults?: readonly TestMempoolAcceptResult[]; // From testmempoolaccept validation
167
+ packageResult?: PackageResult; // From submitpackage (atomic path)
168
+ sequentialResults?: readonly SequentialBroadcastTxResult[]; // Per-tx results (sequential path)
169
+ fellBackToSequential?: boolean; // True if submitpackage failed and fell back
170
+ }
171
+
172
+ interface SequentialBroadcastTxResult {
173
+ txid: string; // The txid of the transaction
174
+ success: boolean; // Whether the individual transaction was broadcast
175
+ error?: string; // Error message if this transaction failed
176
+ peers?: number; // Number of peers that received the transaction
177
+ }
178
+
179
+ interface TestMempoolAcceptResult {
180
+ txid: string;
181
+ wtxid: string;
182
+ allowed?: boolean;
183
+ vsize?: number;
184
+ fees?: TestMempoolAcceptFees;
185
+ packageError?: string;
186
+ rejectReason?: string;
187
+ rejectDetails?: string;
188
+ }
189
+
190
+ interface PackageResult {
191
+ packageMsg: string;
192
+ txResults: { [wtxid: string]: PackageTxResult };
193
+ replacedTransactions?: readonly string[];
194
+ }
195
+ ```
196
+
197
+ ---
198
+
84
199
  ## Send Multiple Transactions
85
200
 
86
201
  ### Batch Broadcasting
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opnet",
3
3
  "type": "module",
4
- "version": "1.8.2",
4
+ "version": "1.8.4",
5
5
  "author": "OP_NET",
6
6
  "description": "The perfect library for building Bitcoin-based applications.",
7
7
  "engines": {
@@ -104,22 +104,23 @@
104
104
  "@babel/preset-env": "^7.29.0",
105
105
  "@babel/preset-typescript": "^7.28.5",
106
106
  "@eslint/js": "^10.0.1",
107
- "@microsoft/api-extractor": "7.57.6",
108
- "@types/node": "^25.3.5",
109
- "@vitest/browser": "^4.0.18",
110
- "@vitest/browser-playwright": "^4.0.18",
111
- "@vitest/ui": "^4.0.18",
107
+ "@microsoft/api-extractor": "7.57.7",
108
+ "@types/node": "^25.5.0",
109
+ "@vitest/browser": "^4.1.0",
110
+ "@vitest/browser-playwright": "^4.1.0",
111
+ "@vitest/ui": "^4.1.0",
112
+ "esbuild": "^0.27.4",
112
113
  "eslint": "^10.0.3",
113
114
  "madge": "^8.0.0",
114
115
  "playwright": "^1.58.2",
115
116
  "stream-browserify": "^3.0.0",
116
117
  "typedoc": "^0.28.17",
117
118
  "typescript": "^5.9.3",
118
- "typescript-eslint": "^8.56.1",
119
- "vite": "^7.3.1",
119
+ "typescript-eslint": "^8.57.0",
120
+ "vite": "^8.0.0",
120
121
  "vite-plugin-dts": "4.5.4",
121
122
  "vite-plugin-node-polyfills": "^0.25.0",
122
- "vitest": "^4.0.18"
123
+ "vitest": "^4.1.0"
123
124
  },
124
125
  "peerDependencies": {
125
126
  "react-native-worklets": ">=0.7.0"
@@ -132,17 +133,21 @@
132
133
  "dependencies": {
133
134
  "@btc-vision/bip32": "^7.1.2",
134
135
  "@btc-vision/bitcoin": "^7.0.0",
135
- "@btc-vision/bitcoin-rpc": "^1.1.0",
136
+ "@btc-vision/bitcoin-rpc": "^1.1.2",
136
137
  "@btc-vision/ecpair": "^4.0.5",
137
138
  "@btc-vision/logger": "^1.0.8",
138
- "@btc-vision/transaction": "^1.8.0",
139
+ "@btc-vision/transaction": "^1.8.2",
139
140
  "@noble/hashes": "^2.0.1",
140
141
  "bignumber.js": "^10.0.2",
141
142
  "long": "^5.3.2",
142
- "opnet": "^1.8.1-rc.3",
143
143
  "p-limit": "^7.3.0",
144
144
  "pako": "^2.1.0",
145
145
  "protobufjs": "^8.0.0",
146
- "undici": "^7.22.0"
146
+ "undici": "^7.24.0"
147
+ },
148
+ "overrides": {
149
+ "vite-plugin-node-polyfills": {
150
+ "vite": "$vite"
151
+ }
147
152
  }
148
153
  }