opnet 1.8.2 → 1.8.3

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 (47) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/browser/_version.d.ts +1 -1
  3. package/browser/contracts/interfaces/IProviderForCallResult.d.ts +2 -0
  4. package/browser/index.js +174 -63
  5. package/browser/opnet.d.ts +1 -0
  6. package/browser/providers/AbstractRpcProvider.d.ts +2 -0
  7. package/browser/providers/interfaces/JSONRpcMethods.d.ts +1 -0
  8. package/browser/providers/websocket/types/WebSocketOpcodes.d.ts +2 -0
  9. package/browser/transactions/interfaces/BroadcastedTransactionPackage.d.ts +48 -0
  10. package/browser/utxos/UTXOsManager.d.ts +1 -0
  11. package/build/_version.d.ts +1 -1
  12. package/build/_version.js +1 -1
  13. package/build/contracts/CallResult.js +54 -19
  14. package/build/contracts/interfaces/IProviderForCallResult.d.ts +2 -0
  15. package/build/opnet.d.ts +1 -0
  16. package/build/opnet.js +1 -0
  17. package/build/providers/AbstractRpcProvider.d.ts +2 -0
  18. package/build/providers/AbstractRpcProvider.js +15 -2
  19. package/build/providers/WebsocketRpcProvider.js +5 -0
  20. package/build/providers/interfaces/JSONRpcMethods.d.ts +1 -0
  21. package/build/providers/interfaces/JSONRpcMethods.js +1 -0
  22. package/build/providers/websocket/MethodMapping.js +6 -0
  23. package/build/providers/websocket/types/WebSocketOpcodes.d.ts +2 -0
  24. package/build/providers/websocket/types/WebSocketOpcodes.js +2 -0
  25. package/build/transactions/interfaces/BroadcastedTransactionPackage.d.ts +48 -0
  26. package/build/transactions/interfaces/BroadcastedTransactionPackage.js +1 -0
  27. package/build/tsconfig.build.tsbuildinfo +1 -1
  28. package/build/utxos/UTXOsManager.d.ts +1 -0
  29. package/build/utxos/UTXOsManager.js +37 -28
  30. package/docs/api-reference/provider-api.md +16 -0
  31. package/docs/api-reference/types-interfaces.md +91 -0
  32. package/docs/api-reference/utxo-manager-api.md +4 -2
  33. package/docs/svg/tx-broadcast-flow.svg +201 -0
  34. package/docs/transactions/broadcasting.md +124 -9
  35. package/package.json +2 -3
  36. package/src/_version.ts +1 -1
  37. package/src/contracts/CallResult.ts +82 -32
  38. package/src/contracts/Contract.ts +11 -4
  39. package/src/contracts/interfaces/IProviderForCallResult.ts +5 -0
  40. package/src/opnet.ts +1 -0
  41. package/src/providers/AbstractRpcProvider.ts +52 -5
  42. package/src/providers/WebsocketRpcProvider.ts +7 -0
  43. package/src/providers/interfaces/JSONRpcMethods.ts +1 -0
  44. package/src/providers/websocket/MethodMapping.ts +6 -0
  45. package/src/providers/websocket/types/WebSocketOpcodes.ts +2 -0
  46. package/src/transactions/interfaces/BroadcastedTransactionPackage.ts +72 -0
  47. package/src/utxos/UTXOsManager.ts +82 -46
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.8.2] - 2026-03-07
4
+
5
+ - No changes
6
+
7
+
3
8
  ## [v1.8.1] - 2026-03-07
4
9
 
5
10
  ### Breaking Changes
@@ -1 +1 @@
1
- export declare const version = "1.8.1";
1
+ export declare const version = "1.8.3";
@@ -2,6 +2,7 @@ import { Network } from '../../../node_modules/@btc-vision/bitcoin/build/index.j
2
2
  import { Address, ChallengeSolution, IP2WSHAddress } from '../../../node_modules/@btc-vision/transaction/build/index.js';
3
3
  import { UTXO, UTXOs } from '../../bitcoin/UTXOs.js';
4
4
  import { BroadcastedTransaction } from '../../transactions/interfaces/BroadcastedTransaction.js';
5
+ import { BroadcastedTransactionPackage } from '../../transactions/interfaces/BroadcastedTransactionPackage.js';
5
6
  import { RequestUTXOsParamsWithAmount } from '../../utxos/interfaces/IUTXOsManager.js';
6
7
  export interface IUTXOManagerForCallResult {
7
8
  getUTXOsForAmount(params: RequestUTXOsParamsWithAmount): Promise<UTXO[]>;
@@ -13,5 +14,6 @@ export interface IProviderForCallResult {
13
14
  readonly utxoManager: IUTXOManagerForCallResult;
14
15
  getChallenge(): Promise<ChallengeSolution>;
15
16
  sendRawTransaction(tx: string, psbt: boolean): Promise<BroadcastedTransaction>;
17
+ sendRawTransactionPackage(txs: string[], isPackage?: boolean): Promise<BroadcastedTransactionPackage>;
16
18
  getCSV1ForAddress(address: Address): IP2WSHAddress;
17
19
  }
package/browser/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { d as decompile, f as fromHex, A as Address, b as fromBase64, N as NetEvent, B as BinaryWriter, c as BinaryReader, e as AddressTypes, h as AddressVerificator, t as toHex, C as ChallengeSolution, r as regtest, o as opnetTestnet, i as testnet, j as bitcoin, T as TransactionFactory, k as BufferHelper, l as AddressMap, m as toBase64, p as pLimit, n as process$1, L as Logger, q as Long, s as abiTypeToSelectorString, u as ABICoder, v as isAbiTuple, w as isAbiStruct, x as ABIDataTypes, y as BigNumber, z as fromBech32, P as P2MR_MS, D as P2TR_MS } from './vendors.js';
2
2
  import { p as protobuf } from './protobuf.js';
3
3
 
4
- const version = "1.8.1";
4
+ const version = "1.8.3";
5
5
 
6
6
  var OPNetTransactionTypes = /* @__PURE__ */ ((OPNetTransactionTypes2) => {
7
7
  OPNetTransactionTypes2["Generic"] = "Generic";
@@ -953,6 +953,19 @@ class TransactionHelper {
953
953
  }
954
954
 
955
955
  const factory = new TransactionFactory();
956
+ function extractPackageFailures(packageResult) {
957
+ const failures = [];
958
+ const results = packageResult.txResults;
959
+ for (const [submittedTxid, result] of Object.entries(results)) {
960
+ if (result.error) {
961
+ failures.push(`tx ${submittedTxid} failed: ${result.error}`);
962
+ }
963
+ }
964
+ if (failures.length === 0 && packageResult.packageMsg !== "success") {
965
+ failures.push(`package rejected: ${packageResult.packageMsg}`);
966
+ }
967
+ return failures;
968
+ }
956
969
  class CallResult {
957
970
  result;
958
971
  accessList;
@@ -1052,6 +1065,13 @@ class CallResult {
1052
1065
  )
1053
1066
  );
1054
1067
  },
1068
+ sendRawTransactionPackage: () => {
1069
+ return Promise.reject(
1070
+ new Error(
1071
+ "Cannot broadcast from offline CallResult. Export signed transaction and broadcast online."
1072
+ )
1073
+ );
1074
+ },
1055
1075
  getCSV1ForAddress: () => {
1056
1076
  if (!data.csvAddress) {
1057
1077
  throw new Error("CSV address not available in offline data");
@@ -1215,43 +1235,69 @@ class CallResult {
1215
1235
  }
1216
1236
  /**
1217
1237
  * Broadcasts a pre-signed interaction transaction.
1238
+ * Uses sendRawTransactionPackage for atomic broadcast when a funding tx is present,
1239
+ * falls back to sendRawTransaction for P2WDA (interaction-only) transactions.
1218
1240
  * @param {SignedInteractionTransactionReceipt} signedTx - The signed transaction data.
1219
1241
  * @returns {Promise<InteractionTransactionReceipt>} The transaction receipt with broadcast results.
1220
1242
  */
1221
1243
  async sendPresignedTransaction(signedTx) {
1222
- if (!signedTx.utxoTracking.isP2WDA) {
1223
- if (!signedTx.fundingTransactionRaw) {
1224
- throw new Error("Funding transaction not created");
1225
- }
1226
- const tx1 = await this.#provider.sendRawTransaction(
1227
- signedTx.fundingTransactionRaw,
1244
+ if (signedTx.utxoTracking.isP2WDA || !signedTx.fundingTransactionRaw) {
1245
+ const tx = await this.#provider.sendRawTransaction(
1246
+ signedTx.interactionTransactionRaw,
1228
1247
  false
1229
1248
  );
1230
- if (!tx1 || tx1.error) {
1231
- throw new Error(`Error sending transaction: ${tx1?.error || "Unknown error"}`);
1249
+ if (!tx || tx.error) {
1250
+ throw new Error(`Error sending transaction: ${tx?.error || "Unknown error"}`);
1251
+ }
1252
+ if (!tx.result) {
1253
+ throw new Error("No transaction ID returned");
1232
1254
  }
1233
- if (!tx1.success) {
1234
- throw new Error(`Error sending transaction: ${tx1.result || "Unknown error"}`);
1255
+ if (!tx.success) {
1256
+ throw new Error(`Error sending transaction: ${tx.result || "Unknown error"}`);
1235
1257
  }
1258
+ this.#processUTXOTracking(signedTx);
1259
+ return {
1260
+ interactionAddress: signedTx.interactionAddress,
1261
+ transactionId: tx.result,
1262
+ peerAcknowledgements: tx.peers || 0,
1263
+ newUTXOs: signedTx.nextUTXOs,
1264
+ estimatedFees: signedTx.estimatedFees,
1265
+ challengeSolution: signedTx.challengeSolution,
1266
+ rawTransaction: signedTx.interactionTransactionRaw,
1267
+ fundingUTXOs: signedTx.fundingUTXOs,
1268
+ fundingInputUtxos: signedTx.fundingInputUtxos,
1269
+ compiledTargetScript: signedTx.compiledTargetScript
1270
+ };
1236
1271
  }
1237
- const tx2 = await this.#provider.sendRawTransaction(
1238
- signedTx.interactionTransactionRaw,
1239
- false
1272
+ const result = await this.#provider.sendRawTransactionPackage(
1273
+ [signedTx.fundingTransactionRaw, signedTx.interactionTransactionRaw],
1274
+ true
1240
1275
  );
1241
- if (!tx2 || tx2.error) {
1242
- throw new Error(`Error sending transaction: ${tx2?.error || "Unknown error"}`);
1276
+ if (!result.success) {
1277
+ throw new Error(
1278
+ `Error sending transaction package: ${result.error || "Unknown error"}`
1279
+ );
1243
1280
  }
1244
- if (!tx2.result) {
1245
- throw new Error("No transaction ID returned");
1281
+ if (result.packageResult) {
1282
+ const failures = extractPackageFailures(result.packageResult);
1283
+ if (failures.length > 0) {
1284
+ throw new Error(`Transaction package failed:
1285
+ ${failures.join("\n")}`);
1286
+ }
1246
1287
  }
1247
- if (!tx2.success) {
1248
- throw new Error(`Error sending transaction: ${tx2.result || "Unknown error"}`);
1288
+ const interactionSeqResult = result.sequentialResults?.[1];
1289
+ if (interactionSeqResult && !interactionSeqResult.success) {
1290
+ throw new Error(
1291
+ `Interaction transaction failed: ${interactionSeqResult.error || "Unknown error"}`
1292
+ );
1249
1293
  }
1294
+ const interactionTxId = interactionSeqResult?.txid || signedTx.interactionTransactionRaw;
1295
+ const peers = interactionSeqResult?.peers || 0;
1250
1296
  this.#processUTXOTracking(signedTx);
1251
1297
  return {
1252
1298
  interactionAddress: signedTx.interactionAddress,
1253
- transactionId: tx2.result,
1254
- peerAcknowledgements: tx2.peers || 0,
1299
+ transactionId: interactionTxId,
1300
+ peerAcknowledgements: peers,
1255
1301
  newUTXOs: signedTx.nextUTXOs,
1256
1302
  estimatedFees: signedTx.estimatedFees,
1257
1303
  challengeSolution: signedTx.challengeSolution,
@@ -1949,6 +1995,7 @@ var JSONRpcMethods = /* @__PURE__ */ ((JSONRpcMethods2) => {
1949
1995
  JSONRpcMethods2["GAS"] = "btc_gas";
1950
1996
  JSONRpcMethods2["GET_TRANSACTION_BY_HASH"] = "btc_getTransactionByHash";
1951
1997
  JSONRpcMethods2["BROADCAST_TRANSACTION"] = "btc_sendRawTransaction";
1998
+ JSONRpcMethods2["BROADCAST_TRANSACTION_PACKAGE"] = "btc_sendRawTransactionPackage";
1952
1999
  JSONRpcMethods2["TRANSACTION_PREIMAGE"] = "btc_preimage";
1953
2000
  JSONRpcMethods2["PUBLIC_KEY_INFO"] = "btc_publicKeyInfo";
1954
2001
  JSONRpcMethods2["GET_UTXOS"] = "btc_getUTXOs";
@@ -1977,7 +2024,7 @@ class UTXOsManager {
1977
2024
  this.provider = provider;
1978
2025
  }
1979
2026
  /**
1980
- * Holds all address-specific data so we dont mix up UTXOs between addresses/wallets.
2027
+ * Holds all address-specific data so we don't mix up UTXOs between addresses/wallets.
1981
2028
  */
1982
2029
  dataByAddress = {};
1983
2030
  /**
@@ -2112,11 +2159,14 @@ class UTXOsManager {
2112
2159
  * Fetch UTXOs for a specific amount needed, from a single address,
2113
2160
  * merging from pending and confirmed UTXOs.
2114
2161
  *
2162
+ * Prioritizes normal UTXOs first, only falling back to CSV UTXOs
2163
+ * if the normal ones cannot cover the requested amount.
2164
+ *
2115
2165
  * @param {object} options
2116
2166
  * @param {string} options.address The address to fetch UTXOs for
2117
2167
  * @param {bigint} options.amount The needed amount
2118
2168
  * @param {boolean} [options.optimize=true] Optimize the UTXOs
2119
- * @param {boolean} [options.csvAddress] Use CSV UTXOs in priority
2169
+ * @param {boolean} [options.csvAddress] Use CSV UTXOs as fallback
2120
2170
  * @param {boolean} [options.mergePendingUTXOs=true] Merge pending
2121
2171
  * @param {boolean} [options.filterSpentUTXOs=true] Filter out spent
2122
2172
  * @param {boolean} [options.throwErrors=false] Throw error if insufficient
@@ -2135,52 +2185,47 @@ class UTXOsManager {
2135
2185
  maxUTXOs = 5e3,
2136
2186
  throwIfUTXOsLimitReached = false
2137
2187
  }) {
2138
- const utxosPromises = [];
2139
- if (csvAddress) {
2140
- utxosPromises.push(
2141
- this.getUTXOs({
2142
- address: csvAddress,
2143
- optimize: true,
2144
- mergePendingUTXOs: false,
2145
- filterSpentUTXOs: true,
2146
- olderThan: 1n,
2147
- isCSV: true
2148
- })
2149
- );
2150
- }
2151
- utxosPromises.push(
2152
- this.getUTXOs({
2153
- address,
2154
- optimize,
2155
- mergePendingUTXOs,
2156
- filterSpentUTXOs,
2157
- olderThan
2158
- })
2159
- );
2160
- const combinedUTXOs = (await Promise.all(utxosPromises)).flat();
2161
- const utxoUntilAmount = [];
2188
+ const selected = [];
2162
2189
  let currentValue = 0n;
2163
- for (const utxo of combinedUTXOs) {
2164
- if (maxUTXOs && utxoUntilAmount.length >= maxUTXOs) {
2165
- if (throwIfUTXOsLimitReached) {
2166
- throw new Error(
2167
- `Woah. You must consolidate your UTXOs (${combinedUTXOs.length})! This transaction is too large.`
2168
- );
2169
- }
2170
- break;
2171
- }
2172
- utxoUntilAmount.push(utxo);
2173
- currentValue += utxo.value;
2174
- if (currentValue >= amount) {
2175
- break;
2176
- }
2190
+ const normalUTXOs = await this.getUTXOs({
2191
+ address,
2192
+ optimize,
2193
+ mergePendingUTXOs,
2194
+ filterSpentUTXOs,
2195
+ olderThan
2196
+ });
2197
+ currentValue = this.selectUTXOsGreedily(
2198
+ normalUTXOs,
2199
+ selected,
2200
+ currentValue,
2201
+ amount,
2202
+ maxUTXOs,
2203
+ throwIfUTXOsLimitReached
2204
+ );
2205
+ if (currentValue < amount && csvAddress) {
2206
+ const csvUTXOs = await this.getUTXOs({
2207
+ address: csvAddress,
2208
+ optimize: true,
2209
+ mergePendingUTXOs: false,
2210
+ filterSpentUTXOs: true,
2211
+ olderThan: 1n,
2212
+ isCSV: true
2213
+ });
2214
+ currentValue = this.selectUTXOsGreedily(
2215
+ csvUTXOs,
2216
+ selected,
2217
+ currentValue,
2218
+ amount,
2219
+ maxUTXOs,
2220
+ throwIfUTXOsLimitReached
2221
+ );
2177
2222
  }
2178
2223
  if (currentValue < amount && throwErrors) {
2179
2224
  throw new Error(
2180
2225
  `Insufficient UTXOs to cover amount. Available: ${currentValue}, Needed: ${amount}`
2181
2226
  );
2182
2227
  }
2183
- return utxoUntilAmount;
2228
+ return selected;
2184
2229
  }
2185
2230
  /**
2186
2231
  * Fetch UTXOs for multiple addresses in a single batch request.
@@ -2254,6 +2299,33 @@ class UTXOsManager {
2254
2299
  }
2255
2300
  return result;
2256
2301
  }
2302
+ /**
2303
+ * Sort UTXOs by value descending and greedily append to `selected` until
2304
+ * `currentValue >= amount` or the pool is exhausted. Mutates `candidates`
2305
+ * (sort in-place) and `selected` (pushes chosen UTXOs). Returns the
2306
+ * updated cumulative value.
2307
+ */
2308
+ selectUTXOsGreedily(candidates, selected, currentValue, amount, maxUTXOs, throwIfLimitReached) {
2309
+ candidates.sort((a, b) => {
2310
+ if (b.value > a.value) return 1;
2311
+ if (b.value < a.value) return -1;
2312
+ return 0;
2313
+ });
2314
+ for (const utxo of candidates) {
2315
+ if (currentValue >= amount) break;
2316
+ if (maxUTXOs && selected.length >= maxUTXOs) {
2317
+ if (throwIfLimitReached) {
2318
+ throw new Error(
2319
+ `Woah. You must consolidate your UTXOs (${candidates.length + selected.length})! This transaction is too large.`
2320
+ );
2321
+ }
2322
+ break;
2323
+ }
2324
+ selected.push(utxo);
2325
+ currentValue += utxo.value;
2326
+ }
2327
+ return currentValue;
2328
+ }
2257
2329
  /**
2258
2330
  * Fetch UTXOs for multiple addresses in a single batch RPC call.
2259
2331
  * @private
@@ -2914,6 +2986,32 @@ class AbstractRpcProvider {
2914
2986
  return rawTx.result;
2915
2987
  });
2916
2988
  }
2989
+ /**
2990
+ * Broadcast a package of raw transactions atomically.
2991
+ * @description Submits an ordered array of raw transactions via Bitcoin Core's submitpackage
2992
+ * RPC for atomic acceptance, or falls back to validated sequential broadcast.
2993
+ * @param {string[]} txs The raw transactions to send as hex strings (max 25)
2994
+ * @param {boolean} [isPackage=true] Whether to use atomic package submission (submitpackage)
2995
+ * or validated sequential broadcast (testmempoolaccept + sendrawtransaction)
2996
+ * @returns {Promise<BroadcastedTransactionPackage>} The result of the package broadcast
2997
+ * @throws {Error} If something went wrong while broadcasting the package
2998
+ */
2999
+ async sendRawTransactionPackage(txs, isPackage = true) {
3000
+ if (!txs.length) {
3001
+ throw new Error("sendRawTransactionPackage: txs array must not be empty");
3002
+ }
3003
+ for (let i = 0; i < txs.length; i++) {
3004
+ if (!/^[0-9A-Fa-f]+$/.test(txs[i])) {
3005
+ throw new Error(`sendRawTransactionPackage: txs[${i}] is not a valid hex string`);
3006
+ }
3007
+ }
3008
+ const payload = this.buildJsonRpcPayload(
3009
+ JSONRpcMethods.BROADCAST_TRANSACTION_PACKAGE,
3010
+ [txs, isPackage]
3011
+ );
3012
+ const result = await this.callPayloadSingle(payload);
3013
+ return result.result;
3014
+ }
2917
3015
  /**
2918
3016
  * Get block witnesses.
2919
3017
  * @description This method is used to get the witnesses of a block. This proves that the actions executed inside a block are valid and confirmed by the network. If the minimum number of witnesses are not met, the block is considered as potentially invalid.
@@ -3965,6 +4063,7 @@ var WebSocketRequestOpcode = /* @__PURE__ */ ((WebSocketRequestOpcode2) => {
3965
4063
  WebSocketRequestOpcode2[WebSocketRequestOpcode2["GET_TRANSACTION_BY_HASH"] = 32] = "GET_TRANSACTION_BY_HASH";
3966
4064
  WebSocketRequestOpcode2[WebSocketRequestOpcode2["GET_TRANSACTION_RECEIPT"] = 33] = "GET_TRANSACTION_RECEIPT";
3967
4065
  WebSocketRequestOpcode2[WebSocketRequestOpcode2["BROADCAST_TRANSACTION"] = 34] = "BROADCAST_TRANSACTION";
4066
+ WebSocketRequestOpcode2[WebSocketRequestOpcode2["BROADCAST_TRANSACTION_PACKAGE"] = 39] = "BROADCAST_TRANSACTION_PACKAGE";
3968
4067
  WebSocketRequestOpcode2[WebSocketRequestOpcode2["GET_PREIMAGE"] = 35] = "GET_PREIMAGE";
3969
4068
  WebSocketRequestOpcode2[WebSocketRequestOpcode2["GET_MEMPOOL_INFO"] = 36] = "GET_MEMPOOL_INFO";
3970
4069
  WebSocketRequestOpcode2[WebSocketRequestOpcode2["GET_PENDING_TRANSACTION"] = 37] = "GET_PENDING_TRANSACTION";
@@ -3999,6 +4098,7 @@ var WebSocketResponseOpcode = /* @__PURE__ */ ((WebSocketResponseOpcode2) => {
3999
4098
  WebSocketResponseOpcode2[WebSocketResponseOpcode2["TRANSACTION"] = 160] = "TRANSACTION";
4000
4099
  WebSocketResponseOpcode2[WebSocketResponseOpcode2["TRANSACTION_RECEIPT"] = 161] = "TRANSACTION_RECEIPT";
4001
4100
  WebSocketResponseOpcode2[WebSocketResponseOpcode2["BROADCAST_RESULT"] = 162] = "BROADCAST_RESULT";
4101
+ WebSocketResponseOpcode2[WebSocketResponseOpcode2["BROADCAST_PACKAGE_RESULT"] = 167] = "BROADCAST_PACKAGE_RESULT";
4002
4102
  WebSocketResponseOpcode2[WebSocketResponseOpcode2["PREIMAGE"] = 163] = "PREIMAGE";
4003
4103
  WebSocketResponseOpcode2[WebSocketResponseOpcode2["MEMPOOL_INFO"] = 164] = "MEMPOOL_INFO";
4004
4104
  WebSocketResponseOpcode2[WebSocketResponseOpcode2["PENDING_TRANSACTION"] = 165] = "PENDING_TRANSACTION";
@@ -4077,6 +4177,12 @@ const METHOD_MAPPINGS = {
4077
4177
  requestType: "BroadcastTransactionRequest",
4078
4178
  responseType: "BroadcastTransactionResponse"
4079
4179
  },
4180
+ [JSONRpcMethods.BROADCAST_TRANSACTION_PACKAGE]: {
4181
+ requestOpcode: WebSocketRequestOpcode.BROADCAST_TRANSACTION_PACKAGE,
4182
+ responseOpcode: WebSocketResponseOpcode.BROADCAST_PACKAGE_RESULT,
4183
+ requestType: "BroadcastTransactionPackageRequest",
4184
+ responseType: "BroadcastTransactionPackageResponse"
4185
+ },
4080
4186
  [JSONRpcMethods.TRANSACTION_PREIMAGE]: {
4081
4187
  requestOpcode: WebSocketRequestOpcode.GET_PREIMAGE,
4082
4188
  responseOpcode: WebSocketResponseOpcode.PREIMAGE,
@@ -4724,6 +4830,11 @@ class WebSocketRpcProvider extends AbstractRpcProvider {
4724
4830
  2: params[0],
4725
4831
  3: params[1] ?? false
4726
4832
  };
4833
+ case JSONRpcMethods.BROADCAST_TRANSACTION_PACKAGE:
4834
+ return {
4835
+ 2: params[0],
4836
+ 3: params[1] ?? true
4837
+ };
4727
4838
  case JSONRpcMethods.TRANSACTION_PREIMAGE:
4728
4839
  return {};
4729
4840
  case JSONRpcMethods.GET_BALANCE:
@@ -70,6 +70,7 @@ export * from './storage/interfaces/IStorageValue.js';
70
70
  export * from './storage/StoredValue.js';
71
71
  export * from './contracts/interfaces/IRawContract.js';
72
72
  export * from './transactions/interfaces/BroadcastedTransaction.js';
73
+ export * from './transactions/interfaces/BroadcastedTransactionPackage.js';
73
74
  export * from './transactions/interfaces/ITransaction.js';
74
75
  export * from './transactions/interfaces/ITransactionReceipt.js';
75
76
  export * from './transactions/metadata/TransactionReceipt.js';
@@ -18,6 +18,7 @@ import { OPNetTransactionTypes } from '../interfaces/opnet/OPNetTransactionTypes
18
18
  import { MempoolTransactionData } from '../mempool/MempoolTransactionData.js';
19
19
  import { StoredValue } from '../storage/StoredValue.js';
20
20
  import { BroadcastedTransaction } from '../transactions/interfaces/BroadcastedTransaction.js';
21
+ import { BroadcastedTransactionPackage } from '../transactions/interfaces/BroadcastedTransactionPackage.js';
21
22
  import { TransactionReceipt } from '../transactions/metadata/TransactionReceipt.js';
22
23
  import { TransactionBase } from '../transactions/Transaction.js';
23
24
  import { UTXOsManager } from '../utxos/UTXOsManager.js';
@@ -59,6 +60,7 @@ export declare abstract class AbstractRpcProvider {
59
60
  gasParameters(): Promise<BlockGasParameters>;
60
61
  sendRawTransaction(tx: string, psbt: boolean): Promise<BroadcastedTransaction>;
61
62
  sendRawTransactions(txs: string[]): Promise<BroadcastedTransaction[]>;
63
+ sendRawTransactionPackage(txs: string[], isPackage?: boolean): Promise<BroadcastedTransactionPackage>;
62
64
  getBlockWitness(height?: BigNumberish, trusted?: boolean, limit?: number, page?: number): Promise<BlockWitnesses>;
63
65
  getReorg(fromBlock?: BigNumberish, toBlock?: BigNumberish): Promise<ReorgInformation[]>;
64
66
  abstract _send(payload: JsonRpcPayload | JsonRpcPayload[]): Promise<JsonRpcCallResult>;
@@ -8,6 +8,7 @@ export declare enum JSONRpcMethods {
8
8
  GAS = "btc_gas",
9
9
  GET_TRANSACTION_BY_HASH = "btc_getTransactionByHash",
10
10
  BROADCAST_TRANSACTION = "btc_sendRawTransaction",
11
+ BROADCAST_TRANSACTION_PACKAGE = "btc_sendRawTransactionPackage",
11
12
  TRANSACTION_PREIMAGE = "btc_preimage",
12
13
  PUBLIC_KEY_INFO = "btc_publicKeyInfo",
13
14
  GET_UTXOS = "btc_getUTXOs",
@@ -10,6 +10,7 @@ export declare enum WebSocketRequestOpcode {
10
10
  GET_TRANSACTION_BY_HASH = 32,
11
11
  GET_TRANSACTION_RECEIPT = 33,
12
12
  BROADCAST_TRANSACTION = 34,
13
+ BROADCAST_TRANSACTION_PACKAGE = 39,
13
14
  GET_PREIMAGE = 35,
14
15
  GET_MEMPOOL_INFO = 36,
15
16
  GET_PENDING_TRANSACTION = 37,
@@ -43,6 +44,7 @@ export declare enum WebSocketResponseOpcode {
43
44
  TRANSACTION = 160,
44
45
  TRANSACTION_RECEIPT = 161,
45
46
  BROADCAST_RESULT = 162,
47
+ BROADCAST_PACKAGE_RESULT = 167,
46
48
  PREIMAGE = 163,
47
49
  MEMPOOL_INFO = 164,
48
50
  PENDING_TRANSACTION = 165,
@@ -0,0 +1,48 @@
1
+ export interface SequentialBroadcastTxResult {
2
+ readonly txid: string;
3
+ readonly success: boolean;
4
+ readonly error?: string;
5
+ readonly peers?: number;
6
+ }
7
+ export interface BroadcastedTransactionPackage {
8
+ readonly success: boolean;
9
+ readonly error?: string;
10
+ readonly testResults?: readonly TestMempoolAcceptResult[];
11
+ readonly packageResult?: PackageResult;
12
+ readonly sequentialResults?: readonly SequentialBroadcastTxResult[];
13
+ readonly fellBackToSequential?: boolean;
14
+ }
15
+ export interface TestMempoolAcceptResult {
16
+ readonly txid: string;
17
+ readonly wtxid: string;
18
+ readonly allowed?: boolean;
19
+ readonly vsize?: number;
20
+ readonly packageError?: string;
21
+ readonly rejectReason?: string;
22
+ readonly rejectDetails?: string;
23
+ readonly fees?: TestMempoolAcceptFees;
24
+ }
25
+ export interface TestMempoolAcceptFees {
26
+ readonly base: number;
27
+ readonly effectiveFeerate: number;
28
+ readonly effectiveIncludes: readonly string[];
29
+ }
30
+ export interface PackageTxResult {
31
+ readonly txid: string;
32
+ readonly otherWtxid?: string;
33
+ readonly vsize?: number;
34
+ readonly fees?: PackageTxFees;
35
+ readonly error?: string;
36
+ }
37
+ export interface PackageTxFees {
38
+ readonly base: number;
39
+ readonly effectiveFeerate?: number;
40
+ readonly effectiveIncludes?: readonly string[];
41
+ }
42
+ export interface PackageResult {
43
+ readonly packageMsg: string;
44
+ readonly txResults: {
45
+ readonly [wtxid: string]: PackageTxResult;
46
+ };
47
+ readonly replacedTransactions?: readonly string[];
48
+ }
@@ -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;
@@ -1 +1 @@
1
- export declare const version = "1.8.1";
1
+ export declare const version = "1.8.3";
package/build/_version.js CHANGED
@@ -1 +1 @@
1
- export const version = '1.8.1';
1
+ export const version = '1.8.3';
@@ -4,6 +4,19 @@ import { decodeRevertData } from '../utils/RevertDecoder.js';
4
4
  import { CallResultSerializer, NetworkName } from './CallResultSerializer.js';
5
5
  import { TransactionHelper } from './TransactionHelpper.js';
6
6
  const factory = new TransactionFactory();
7
+ function extractPackageFailures(packageResult) {
8
+ const failures = [];
9
+ const results = packageResult.txResults;
10
+ for (const [submittedTxid, result] of Object.entries(results)) {
11
+ if (result.error) {
12
+ failures.push(`tx ${submittedTxid} failed: ${result.error}`);
13
+ }
14
+ }
15
+ if (failures.length === 0 && packageResult.packageMsg !== 'success') {
16
+ failures.push(`package rejected: ${packageResult.packageMsg}`);
17
+ }
18
+ return failures;
19
+ }
7
20
  export class CallResult {
8
21
  result;
9
22
  accessList;
@@ -82,6 +95,9 @@ export class CallResult {
82
95
  sendRawTransaction: () => {
83
96
  return Promise.reject(new Error('Cannot broadcast from offline CallResult. Export signed transaction and broadcast online.'));
84
97
  },
98
+ sendRawTransactionPackage: () => {
99
+ return Promise.reject(new Error('Cannot broadcast from offline CallResult. Export signed transaction and broadcast online.'));
100
+ },
85
101
  getCSV1ForAddress: () => {
86
102
  if (!data.csvAddress) {
87
103
  throw new Error('CSV address not available in offline data');
@@ -241,33 +257,52 @@ export class CallResult {
241
257
  };
242
258
  }
243
259
  async sendPresignedTransaction(signedTx) {
244
- if (!signedTx.utxoTracking.isP2WDA) {
245
- if (!signedTx.fundingTransactionRaw) {
246
- throw new Error('Funding transaction not created');
260
+ if (signedTx.utxoTracking.isP2WDA || !signedTx.fundingTransactionRaw) {
261
+ const tx = await this.#provider.sendRawTransaction(signedTx.interactionTransactionRaw, false);
262
+ if (!tx || tx.error) {
263
+ throw new Error(`Error sending transaction: ${tx?.error || 'Unknown error'}`);
247
264
  }
248
- const tx1 = await this.#provider.sendRawTransaction(signedTx.fundingTransactionRaw, false);
249
- if (!tx1 || tx1.error) {
250
- throw new Error(`Error sending transaction: ${tx1?.error || 'Unknown error'}`);
265
+ if (!tx.result) {
266
+ throw new Error('No transaction ID returned');
251
267
  }
252
- if (!tx1.success) {
253
- throw new Error(`Error sending transaction: ${tx1.result || 'Unknown error'}`);
268
+ if (!tx.success) {
269
+ throw new Error(`Error sending transaction: ${tx.result || 'Unknown error'}`);
270
+ }
271
+ this.#processUTXOTracking(signedTx);
272
+ return {
273
+ interactionAddress: signedTx.interactionAddress,
274
+ transactionId: tx.result,
275
+ peerAcknowledgements: tx.peers || 0,
276
+ newUTXOs: signedTx.nextUTXOs,
277
+ estimatedFees: signedTx.estimatedFees,
278
+ challengeSolution: signedTx.challengeSolution,
279
+ rawTransaction: signedTx.interactionTransactionRaw,
280
+ fundingUTXOs: signedTx.fundingUTXOs,
281
+ fundingInputUtxos: signedTx.fundingInputUtxos,
282
+ compiledTargetScript: signedTx.compiledTargetScript,
283
+ };
284
+ }
285
+ const result = await this.#provider.sendRawTransactionPackage([signedTx.fundingTransactionRaw, signedTx.interactionTransactionRaw], true);
286
+ if (!result.success) {
287
+ throw new Error(`Error sending transaction package: ${result.error || 'Unknown error'}`);
288
+ }
289
+ if (result.packageResult) {
290
+ const failures = extractPackageFailures(result.packageResult);
291
+ if (failures.length > 0) {
292
+ throw new Error(`Transaction package failed:\n${failures.join('\n')}`);
254
293
  }
255
294
  }
256
- const tx2 = await this.#provider.sendRawTransaction(signedTx.interactionTransactionRaw, false);
257
- if (!tx2 || tx2.error) {
258
- throw new Error(`Error sending transaction: ${tx2?.error || 'Unknown error'}`);
259
- }
260
- if (!tx2.result) {
261
- throw new Error('No transaction ID returned');
262
- }
263
- if (!tx2.success) {
264
- throw new Error(`Error sending transaction: ${tx2.result || 'Unknown error'}`);
295
+ const interactionSeqResult = result.sequentialResults?.[1];
296
+ if (interactionSeqResult && !interactionSeqResult.success) {
297
+ throw new Error(`Interaction transaction failed: ${interactionSeqResult.error || 'Unknown error'}`);
265
298
  }
299
+ const interactionTxId = interactionSeqResult?.txid || signedTx.interactionTransactionRaw;
300
+ const peers = interactionSeqResult?.peers || 0;
266
301
  this.#processUTXOTracking(signedTx);
267
302
  return {
268
303
  interactionAddress: signedTx.interactionAddress,
269
- transactionId: tx2.result,
270
- peerAcknowledgements: tx2.peers || 0,
304
+ transactionId: interactionTxId,
305
+ peerAcknowledgements: peers,
271
306
  newUTXOs: signedTx.nextUTXOs,
272
307
  estimatedFees: signedTx.estimatedFees,
273
308
  challengeSolution: signedTx.challengeSolution,
@@ -2,6 +2,7 @@ import { Network } from '@btc-vision/bitcoin';
2
2
  import { Address, ChallengeSolution, IP2WSHAddress } from '@btc-vision/transaction';
3
3
  import { UTXO, UTXOs } from '../../bitcoin/UTXOs.js';
4
4
  import { BroadcastedTransaction } from '../../transactions/interfaces/BroadcastedTransaction.js';
5
+ import { BroadcastedTransactionPackage } from '../../transactions/interfaces/BroadcastedTransactionPackage.js';
5
6
  import { RequestUTXOsParamsWithAmount } from '../../utxos/interfaces/IUTXOsManager.js';
6
7
  export interface IUTXOManagerForCallResult {
7
8
  getUTXOsForAmount(params: RequestUTXOsParamsWithAmount): Promise<UTXO[]>;
@@ -13,5 +14,6 @@ export interface IProviderForCallResult {
13
14
  readonly utxoManager: IUTXOManagerForCallResult;
14
15
  getChallenge(): Promise<ChallengeSolution>;
15
16
  sendRawTransaction(tx: string, psbt: boolean): Promise<BroadcastedTransaction>;
17
+ sendRawTransactionPackage(txs: string[], isPackage?: boolean): Promise<BroadcastedTransactionPackage>;
16
18
  getCSV1ForAddress(address: Address): IP2WSHAddress;
17
19
  }
package/build/opnet.d.ts CHANGED
@@ -70,6 +70,7 @@ export * from './storage/interfaces/IStorageValue.js';
70
70
  export * from './storage/StoredValue.js';
71
71
  export * from './contracts/interfaces/IRawContract.js';
72
72
  export * from './transactions/interfaces/BroadcastedTransaction.js';
73
+ export * from './transactions/interfaces/BroadcastedTransactionPackage.js';
73
74
  export * from './transactions/interfaces/ITransaction.js';
74
75
  export * from './transactions/interfaces/ITransactionReceipt.js';
75
76
  export * from './transactions/metadata/TransactionReceipt.js';
package/build/opnet.js CHANGED
@@ -70,6 +70,7 @@ export * from './storage/interfaces/IStorageValue.js';
70
70
  export * from './storage/StoredValue.js';
71
71
  export * from './contracts/interfaces/IRawContract.js';
72
72
  export * from './transactions/interfaces/BroadcastedTransaction.js';
73
+ export * from './transactions/interfaces/BroadcastedTransactionPackage.js';
73
74
  export * from './transactions/interfaces/ITransaction.js';
74
75
  export * from './transactions/interfaces/ITransactionReceipt.js';
75
76
  export * from './transactions/metadata/TransactionReceipt.js';