cashscript 0.13.0-next.6 → 0.13.0-next.8

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.
@@ -6,22 +6,45 @@ import { debugLibauthTemplate, getLibauthTemplate, getBitauthUri } from './libau
6
6
  import { getWcContractInfo } from './walletconnect-utils.js';
7
7
  import semver from 'semver';
8
8
  const DEFAULT_SEQUENCE = 0xfffffffe;
9
+ /**
10
+ * Fluent builder for constructing, debugging, and broadcasting CashScript transactions.
11
+ *
12
+ * Inputs are added via `addInput` / `addInputs` with an `Unlocker` produced by a `Contract` or
13
+ * `SignatureTemplate`, outputs are added via `addOutput` / `addOutputs`, and the resulting
14
+ * transaction can be built (`build`), debugged (`debug`), or broadcast (`send`).
15
+ */
9
16
  export class TransactionBuilder {
17
+ /**
18
+ * Create a new TransactionBuilder.
19
+ *
20
+ * @param options - Builder options, including the network provider and optional fee/burn limits.
21
+ */
10
22
  constructor(options) {
11
23
  this.inputs = [];
12
24
  this.outputs = [];
13
25
  this.locktime = 0;
26
+ this.changeLocks = {};
14
27
  this.provider = options.provider;
15
28
  this.options = {
16
29
  allowImplicitFungibleTokenBurn: options.allowImplicitFungibleTokenBurn ?? false,
17
30
  ...options,
18
31
  };
19
32
  }
33
+ /**
34
+ * Add a single UTXO as an input to the transaction.
35
+ *
36
+ * @param utxo - The UTXO to spend.
37
+ * @param unlocker - The unlocker to generate the unlocking bytecode for this input. Typically
38
+ * obtained from `contract.unlock.<functionName>(...args)` or `signatureTemplate.unlockP2PKH()`.
39
+ * @param options - Optional per-input options such as a custom sequence number.
40
+ * @returns This builder for chaining.
41
+ * @throws If the UTXO is invalid.
42
+ */
20
43
  addInput(utxo, unlocker, options) {
21
44
  return this.addInputs([utxo], unlocker, options);
22
45
  }
23
46
  addInputs(utxos, unlocker, options) {
24
- utxos.forEach(validateInput);
47
+ utxos.forEach((utxo) => validateInput(utxo, this.changeLocks));
25
48
  if ((!unlocker && utxos.some((utxo) => !isUnlockableUtxo(utxo)))
26
49
  || (unlocker && utxos.some((utxo) => isUnlockableUtxo(utxo)))) {
27
50
  throw new Error('Either all UTXOs must have an individual unlocker specified, or no UTXOs must have an individual unlocker specified and a shared unlocker must be provided');
@@ -33,19 +56,53 @@ export class TransactionBuilder {
33
56
  this.inputs = this.inputs.concat(utxos.map(((utxo) => ({ ...utxo, unlocker, options }))));
34
57
  return this;
35
58
  }
59
+ /**
60
+ * Add a single output to the transaction.
61
+ *
62
+ * @param output - The output to add. Its address, amount and optional token are validated
63
+ * against the provider's network.
64
+ * @returns This builder for chaining.
65
+ * @throws If the output is invalid.
66
+ */
36
67
  addOutput(output) {
37
68
  return this.addOutputs([output]);
38
69
  }
70
+ /**
71
+ * Add multiple outputs to the transaction.
72
+ *
73
+ * @param outputs - The outputs to add. Each output is validated against the provider's network.
74
+ * @returns This builder for chaining.
75
+ * @throws If any output is invalid.
76
+ */
39
77
  addOutputs(outputs) {
40
- outputs.forEach(validateOutput);
78
+ outputs.forEach((output) => validateOutput(output, this.provider.network, this.changeLocks));
41
79
  this.outputs = this.outputs.concat(outputs);
42
80
  return this;
43
81
  }
44
82
  // TODO: allow uint8array for chunks
83
+ /**
84
+ * Append an `OP_RETURN` output containing the provided data chunks. Hex strings prefixed with
85
+ * `0x` are decoded as bytes; other strings are encoded as UTF-8.
86
+ *
87
+ * @param chunks - The data chunks to include after the `OP_RETURN` opcode.
88
+ * @returns This builder for chaining.
89
+ */
45
90
  addOpReturnOutput(chunks) {
46
- this.outputs.push(createOpReturnOutput(chunks));
91
+ this.addOutput(createOpReturnOutput(chunks));
47
92
  return this;
48
93
  }
94
+ /**
95
+ * Add a BCH change output for the remaining value after fees, if it exceeds the dust limit. The
96
+ * fee is computed from the transaction size at the configured fee rate; dust-sized change is
97
+ * simply absorbed into the fee.
98
+ *
99
+ * Should be called *after* all explicit inputs and outputs are added.
100
+ *
101
+ * @param changeOutputOptions - The destination address and the fee rate (in sats/byte) to use.
102
+ * @returns This builder for chaining.
103
+ * @throws If the available surplus is insufficient to cover the fee for the configured rate or
104
+ * if a BCH change output was already added.
105
+ */
49
106
  addBchChangeOutputIfNeeded(changeOutputOptions) {
50
107
  const totalBchInputAmount = this.inputs.reduce((total, input) => total + input.satoshis, 0n);
51
108
  const totalBchOutputAmount = this.outputs.reduce((total, output) => total + output.amount, 0n);
@@ -66,15 +123,64 @@ export class TransactionBuilder {
66
123
  const changeOutput = { to: changeOutputOptions.to, amount: changeAmount };
67
124
  const changeOutputDust = calculateDust(changeOutput);
68
125
  if (changeAmount < changeOutputDust) {
126
+ this.changeLocks.BCH = true;
69
127
  return this;
70
128
  }
71
- this.outputs.push(changeOutput);
129
+ this.addOutput(changeOutput);
130
+ this.changeLocks.BCH = true;
72
131
  return this;
73
132
  }
133
+ /**
134
+ * Add a fungible token change output for the configured category if the transaction's inputs
135
+ * contain more tokens of that category than its outputs. The change output is sent to the
136
+ * provided token address and carries the dust-minimum BCH amount.
137
+ *
138
+ * Should be called *after* all explicit token outputs for the category are added and *before*
139
+ * `addBchChangeOutputIfNeeded`.
140
+ *
141
+ * @param changeOutputOptions - The token category to handle and the destination token address.
142
+ * @returns This builder for chaining.
143
+ * @throws If the destination is not a token-supporting address, or if a corresponding change output
144
+ * or BCH change output was already added.
145
+ */
146
+ addTokenChangeOutputIfNeeded(changeOutputOptions) {
147
+ const { category, to } = changeOutputOptions;
148
+ const inputAmount = this.inputs
149
+ .filter((input) => input.token?.category === category)
150
+ .reduce((total, input) => total + input.token.amount, 0n);
151
+ const outputAmount = this.outputs
152
+ .filter((output) => output.token?.category === category)
153
+ .reduce((total, output) => total + output.token.amount, 0n);
154
+ const changeAmount = inputAmount - outputAmount;
155
+ if (changeAmount <= 0n) {
156
+ this.changeLocks[category] = true;
157
+ return this;
158
+ }
159
+ const changeOutput = {
160
+ to,
161
+ amount: 0n,
162
+ token: { amount: changeAmount, category },
163
+ };
164
+ changeOutput.amount = BigInt(calculateDust(changeOutput));
165
+ this.addOutput(changeOutput);
166
+ this.changeLocks[category] = true;
167
+ return this;
168
+ }
169
+ /**
170
+ * Build the transaction (skipping fee and burn checks) and return its encoded byte length.
171
+ *
172
+ * @returns The size of the transaction in bytes.
173
+ */
74
174
  getTransactionSize() {
75
175
  const transaction = this.buildLibauthTransaction(true);
76
176
  return BigInt(encodeTransaction(transaction).byteLength);
77
177
  }
178
+ /**
179
+ * Set the `nLockTime` of the transaction.
180
+ *
181
+ * @param locktime - The absolute locktime to use (block height or UNIX timestamp).
182
+ * @returns This builder for chaining.
183
+ */
78
184
  setLocktime(locktime) {
79
185
  this.locktime = locktime;
80
186
  return this;
@@ -144,10 +250,26 @@ export class TransactionBuilder {
144
250
  }
145
251
  return transaction;
146
252
  }
253
+ /**
254
+ * Build the transaction, applying fee and implicit-burn checks, and return the hex-encoded
255
+ * transaction bytes.
256
+ *
257
+ * @returns The signed transaction as a hex string.
258
+ * @throws If the transaction fee exceeds the configured maximum, or if fungible tokens are
259
+ * implicitly burned without `allowImplicitFungibleTokenBurn` enabled.
260
+ */
147
261
  build() {
148
262
  const transaction = this.buildLibauthTransaction();
149
263
  return binToHex(encodeTransaction(transaction));
150
264
  }
265
+ /**
266
+ * Locally evaluate the transaction against the Bitcoin Cash VM using debug information from the
267
+ * contract artifacts. Throws a descriptive error if any input fails evaluation.
268
+ *
269
+ * @returns The full debug execution trace for every scenario in the generated libauth template.
270
+ * @throws If the transaction contains inputs with custom (non-standard) unlockers, or if the
271
+ * evaluation fails (e.g. a failing `require` statement).
272
+ */
151
273
  debug() {
152
274
  if (this.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
153
275
  throw new Error('Cannot debug a transaction with custom unlocker');
@@ -161,6 +283,15 @@ export class TransactionBuilder {
161
283
  }
162
284
  return debugLibauthTemplate(this.getLibauthTemplate(), this);
163
285
  }
286
+ /**
287
+ * Compute VM resource usage (ops, op cost budget, sigchecks, hash iterations) for each input
288
+ * by running the transaction through `debug`.
289
+ *
290
+ * @param verbose - When `true`, also prints a formatted table of the results via `console.table`.
291
+ * @returns One entry per input with its VM resource usage metrics.
292
+ * @throws If the transaction contains inputs with custom (non-standard) unlockers, or if the
293
+ * evaluation fails.
294
+ */
164
295
  getVmResourceUsage(verbose = false) {
165
296
  // Note that only StandardUnlockableUtxo inputs are supported for debugging, so any transaction with custom unlockers
166
297
  // cannot be debugged (and therefore cannot return VM resource usage)
@@ -191,10 +322,27 @@ export class TransactionBuilder {
191
322
  }
192
323
  return vmResourceUsage;
193
324
  }
325
+ /**
326
+ * Build a Bitauth IDE URI that loads the transaction (and all private keys required to sign it)
327
+ * in the online Bitauth IDE debugger.
328
+ *
329
+ * WARNING: The URI embeds every private key used in the transaction. Do not share this URI if
330
+ * the transaction is signed with real private keys.
331
+ *
332
+ * @returns A Bitauth IDE URL for debugging this transaction.
333
+ * @throws If the transaction cannot be built (fee exceeds limit or fungible tokens burned).
334
+ */
194
335
  getBitauthUri() {
195
336
  console.warn('WARNING: it is unsafe to use this Bitauth URI when using real private keys as they are included in the transaction template');
196
337
  return getBitauthUri(this.getLibauthTemplate());
197
338
  }
339
+ /**
340
+ * Build the transaction and return the corresponding libauth `WalletTemplate`. Useful for
341
+ * exporting the transaction to external libauth-compatible tooling.
342
+ *
343
+ * @returns A libauth `WalletTemplate` describing this transaction.
344
+ * @throws If the transaction cannot be built (fee exceeds limit or fungible tokens burned).
345
+ */
198
346
  getLibauthTemplate() {
199
347
  const libauthTransaction = this.buildLibauthTransaction();
200
348
  return getLibauthTemplate(this, libauthTransaction);
@@ -212,7 +360,14 @@ export class TransactionBuilder {
212
360
  }
213
361
  catch (e) {
214
362
  const reason = e.error ?? e.message;
215
- throw new FailedTransactionError(reason, this.getBitauthUri());
363
+ try {
364
+ const bitauthUri = getBitauthUri(this.getLibauthTemplate());
365
+ throw new FailedTransactionError(reason, bitauthUri);
366
+ }
367
+ catch {
368
+ // Preserve the original broadcast failure reason if URI generation fails
369
+ throw new FailedTransactionError(reason, 'Bitauth URI generation failed');
370
+ }
216
371
  }
217
372
  }
218
373
  async getTxDetails(txid, raw) {
@@ -232,6 +387,16 @@ export class TransactionBuilder {
232
387
  // Should not happen
233
388
  throw new Error('Could not retrieve transaction details for over 10 minutes');
234
389
  }
390
+ /**
391
+ * Build the transaction and format it as a BCH WalletConnect transaction object suitable for
392
+ * signing and broadcasting via a BCH WalletConnect-compatible Bitcoin Cash wallet.
393
+ *
394
+ * See the [BCH WalletConnect spec](https://github.com/mainnet-pat/wc2-bch-bcr) for the object format.
395
+ *
396
+ * @param options - Optional WalletConnect options such as `broadcast` and `userPrompt`.
397
+ * @returns A WalletConnect transaction object ready to be sent to a WalletConnect wallet.
398
+ * @throws If the transaction cannot be built (fee exceeds limit or fungible tokens burned).
399
+ */
235
400
  generateWcTransactionObject(options) {
236
401
  const encodedTransaction = this.build();
237
402
  const transaction = decodeTransactionUnsafe(hexToBin(encodedTransaction));
package/dist/index.d.ts CHANGED
@@ -6,7 +6,8 @@ export type { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
6
6
  export * as utils from '@cashscript/utils';
7
7
  export * from './interfaces.js';
8
8
  export * from './Errors.js';
9
- export { type NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
9
+ export * from './network/errors.js';
10
+ export { type NetworkProvider, ElectrumNetworkProvider, MockNetworkProvider, } from './network/index.js';
10
11
  export { randomUtxo, randomToken, randomNFT } from './utils.js';
11
12
  export * from './walletconnect-utils.js';
12
13
  export { gatherBchUtxos, gatherFungibleTokenUtxos, type GatherUtxosResult } from './transaction-utils.js';
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ export { encodeFunctionArgument, } from './Argument.js';
5
5
  export * as utils from '@cashscript/utils';
6
6
  export * from './interfaces.js';
7
7
  export * from './Errors.js';
8
- export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
8
+ export * from './network/errors.js';
9
+ export { ElectrumNetworkProvider, MockNetworkProvider, } from './network/index.js';
9
10
  export { randomUtxo, randomToken, randomNFT } from './utils.js';
10
11
  export * from './walletconnect-utils.js';
11
12
  export { gatherBchUtxos, gatherFungibleTokenUtxos } from './transaction-utils.js';
@@ -62,6 +62,10 @@ export interface BchChangeOutputOptions {
62
62
  to: string | Uint8Array;
63
63
  feeRate: number;
64
64
  }
65
+ export interface TokenChangeOutputOptions {
66
+ category: string;
67
+ to: string | Uint8Array;
68
+ }
65
69
  export interface TokenDetails {
66
70
  amount: bigint;
67
71
  category: string;
@@ -11,11 +11,23 @@ interface CustomElectrumOptions extends OptionsBase {
11
11
  electrum: ElectrumClient<ElectrumClientEvents>;
12
12
  }
13
13
  type Options = OptionsBase | CustomHostNameOptions | CustomElectrumOptions;
14
+ /**
15
+ * A `NetworkProvider` implementation backed by an Electrum Cash server. By default it manages
16
+ * its own connection lifecycle, connecting on demand and disconnecting when idle; for long-lived
17
+ * clients, pass `manualConnectionManagement: true` and call `connect` / `disconnect` explicitly.
18
+ */
14
19
  export default class ElectrumNetworkProvider implements NetworkProvider {
15
20
  network: Network;
16
21
  private electrum;
17
22
  private concurrentRequests;
18
23
  private manualConnectionManagement;
24
+ /**
25
+ * Create a new ElectrumNetworkProvider.
26
+ *
27
+ * @param network - The BCH network to connect to. Defaults to `Network.MAINNET`.
28
+ * @param options - Optional hostname, pre-configured `ElectrumClient`, and/or
29
+ * `manualConnectionManagement` flag.
30
+ */
19
31
  constructor(network?: Network, options?: Options);
20
32
  private instantiateElectrumClient;
21
33
  private getServerForNetwork;
@@ -24,8 +36,29 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
24
36
  getBlockHeight(): Promise<number>;
25
37
  getRawTransaction(txid: string): Promise<string>;
26
38
  sendRawTransaction(txHex: string): Promise<string>;
39
+ /**
40
+ * Manually open the underlying Electrum connection. Only allowed when the provider was created
41
+ * with `manualConnectionManagement: true`.
42
+ *
43
+ * @throws If manual connection management is disabled.
44
+ */
27
45
  connect(): Promise<void>;
46
+ /**
47
+ * Manually close the underlying Electrum connection. Only allowed when the provider was created
48
+ * with `manualConnectionManagement: true`.
49
+ *
50
+ * @throws If manual connection management is disabled.
51
+ * @returns Whether the connection was disconnected successfully.
52
+ */
28
53
  disconnect(): Promise<boolean>;
54
+ /**
55
+ * Perform an arbitrary Electrum JSON-RPC request against the underlying server. Automatically
56
+ * manages the connection lifecycle unless `manualConnectionManagement` was set.
57
+ *
58
+ * @param name - The Electrum method name (e.g. `blockchain.transaction.get`).
59
+ * @param parameters - Parameters passed to the method.
60
+ * @returns The raw Electrum server response.
61
+ */
29
62
  performRequest(name: string, ...parameters: (string | number | boolean)[]): Promise<RequestResponse>;
30
63
  private shouldConnect;
31
64
  private shouldDisconnect;
@@ -3,7 +3,20 @@ import { sha256 } from '@cashscript/utils';
3
3
  import { ElectrumClient, } from '@electrum-cash/network';
4
4
  import { Network } from '../interfaces.js';
5
5
  import { addressToLockScript } from '../utils.js';
6
+ import { NetworkProviderMissingInputsError, NetworkProviderMempoolConflictError, NetworkProviderTransactionAlreadySubmittedError, NetworkProviderAbsoluteTimelockError, NetworkProviderRelativeTimelockError, } from './errors.js';
7
+ /**
8
+ * A `NetworkProvider` implementation backed by an Electrum Cash server. By default it manages
9
+ * its own connection lifecycle, connecting on demand and disconnecting when idle; for long-lived
10
+ * clients, pass `manualConnectionManagement: true` and call `connect` / `disconnect` explicitly.
11
+ */
6
12
  export default class ElectrumNetworkProvider {
13
+ /**
14
+ * Create a new ElectrumNetworkProvider.
15
+ *
16
+ * @param network - The BCH network to connect to. Defaults to `Network.MAINNET`.
17
+ * @param options - Optional hostname, pre-configured `ElectrumClient`, and/or
18
+ * `manualConnectionManagement` flag.
19
+ */
7
20
  constructor(network = Network.MAINNET, options = {}) {
8
21
  this.network = network;
9
22
  this.concurrentRequests = 0;
@@ -62,20 +75,47 @@ export default class ElectrumNetworkProvider {
62
75
  return await this.performRequest('blockchain.transaction.get', txid);
63
76
  }
64
77
  async sendRawTransaction(txHex) {
65
- return await this.performRequest('blockchain.transaction.broadcast', txHex);
78
+ try {
79
+ return await this.performRequest('blockchain.transaction.broadcast', txHex);
80
+ }
81
+ catch (e) {
82
+ const errorMessage = e.message ?? String(e);
83
+ throw classifyNetworkProviderError(errorMessage);
84
+ }
66
85
  }
86
+ /**
87
+ * Manually open the underlying Electrum connection. Only allowed when the provider was created
88
+ * with `manualConnectionManagement: true`.
89
+ *
90
+ * @throws If manual connection management is disabled.
91
+ */
67
92
  async connect() {
68
93
  if (!this.manualConnectionManagement) {
69
94
  throw new Error('Manual connection management is disabled');
70
95
  }
71
96
  return this.electrum.connect();
72
97
  }
98
+ /**
99
+ * Manually close the underlying Electrum connection. Only allowed when the provider was created
100
+ * with `manualConnectionManagement: true`.
101
+ *
102
+ * @throws If manual connection management is disabled.
103
+ * @returns Whether the connection was disconnected successfully.
104
+ */
73
105
  async disconnect() {
74
106
  if (!this.manualConnectionManagement) {
75
107
  throw new Error('Manual connection management is disabled');
76
108
  }
77
109
  return this.electrum.disconnect();
78
110
  }
111
+ /**
112
+ * Perform an arbitrary Electrum JSON-RPC request against the underlying server. Automatically
113
+ * manages the connection lifecycle unless `manualConnectionManagement` was set.
114
+ *
115
+ * @param name - The Electrum method name (e.g. `blockchain.transaction.get`).
116
+ * @param parameters - Parameters passed to the method.
117
+ * @returns The raw Electrum server response.
118
+ */
79
119
  async performRequest(name, ...parameters) {
80
120
  // Only connect the electrum client when no concurrent requests are running
81
121
  if (this.shouldConnect()) {
@@ -113,6 +153,43 @@ export default class ElectrumNetworkProvider {
113
153
  return true;
114
154
  }
115
155
  }
156
+ const MISSING_INPUTS_PATTERNS = [
157
+ 'Missing inputs',
158
+ 'bad-txns-inputs-missingorspent',
159
+ 'bad-txns-inputs-spent',
160
+ ];
161
+ const MEMPOOL_CONFLICT_PATTERNS = [
162
+ 'txn-mempool-conflict',
163
+ ];
164
+ const ALREADY_SUBMITTED_PATTERNS = [
165
+ 'transaction already in block chain',
166
+ 'txn-already-known',
167
+ 'txn-already-in-mempool',
168
+ ];
169
+ const ABSOLUTE_TIMELOCK_PATTERNS = [
170
+ 'bad-txns-nonfinal',
171
+ ];
172
+ const RELATIVE_TIMELOCK_PATTERNS = [
173
+ 'non-BIP68-final',
174
+ ];
175
+ function classifyNetworkProviderError(errorMessage) {
176
+ if (MISSING_INPUTS_PATTERNS.some((pattern) => errorMessage.includes(pattern))) {
177
+ return new NetworkProviderMissingInputsError(errorMessage);
178
+ }
179
+ if (MEMPOOL_CONFLICT_PATTERNS.some((pattern) => errorMessage.includes(pattern))) {
180
+ return new NetworkProviderMempoolConflictError(errorMessage);
181
+ }
182
+ if (ALREADY_SUBMITTED_PATTERNS.some((pattern) => errorMessage.includes(pattern))) {
183
+ return new NetworkProviderTransactionAlreadySubmittedError(errorMessage);
184
+ }
185
+ if (ABSOLUTE_TIMELOCK_PATTERNS.some((pattern) => errorMessage.includes(pattern))) {
186
+ return new NetworkProviderAbsoluteTimelockError(errorMessage);
187
+ }
188
+ if (RELATIVE_TIMELOCK_PATTERNS.some((pattern) => errorMessage.includes(pattern))) {
189
+ return new NetworkProviderRelativeTimelockError(errorMessage);
190
+ }
191
+ return new Error(errorMessage);
192
+ }
116
193
  function lockingBytecodeToElectrumScriptHash(lockingBytecode) {
117
194
  const scriptHash = sha256(lockingBytecode);
118
195
  scriptHash.reverse();
@@ -1,9 +1,23 @@
1
1
  import { Utxo, Network, VmTarget } from '../interfaces.js';
2
2
  import NetworkProvider from './NetworkProvider.js';
3
+ /**
4
+ * Options accepted by the `MockNetworkProvider` constructor.
5
+ */
3
6
  export interface MockNetworkProviderOptions {
7
+ /**
8
+ * When `true` (default), broadcasting a transaction via `sendRawTransaction` updates the
9
+ * in-memory UTXO set: input UTXOs are removed and output UTXOs are added. Set to `false` to
10
+ * keep the UTXO set static.
11
+ */
4
12
  updateUtxoSet?: boolean;
13
+ /** The BCH VM target used for local debugging. Defaults to the current stable VM. */
5
14
  vmTarget?: VmTarget;
6
15
  }
16
+ /**
17
+ * An in-memory `NetworkProvider` useful for tests and examples. It does not connect to any
18
+ * external server; instead UTXOs are manually added via `addUtxo` and transactions are tracked
19
+ * in memory.
20
+ */
7
21
  export default class MockNetworkProvider implements NetworkProvider {
8
22
  private utxoSet;
9
23
  private transactionMap;
@@ -11,13 +25,34 @@ export default class MockNetworkProvider implements NetworkProvider {
11
25
  network: Network;
12
26
  options: MockNetworkProviderOptions;
13
27
  vmTarget: VmTarget;
28
+ /**
29
+ * Create a new MockNetworkProvider.
30
+ *
31
+ * @param options - Optional settings controlling UTXO-set updating and the VM target used by
32
+ * `TransactionBuilder.debug`.
33
+ */
14
34
  constructor(options?: Partial<MockNetworkProviderOptions>);
15
35
  getUtxos(address: string): Promise<Utxo[]>;
16
36
  getUtxosForLockingBytecode(lockingBytecode: Uint8Array | string): Promise<Utxo[]>;
37
+ /**
38
+ * Override the current block height returned by `getBlockHeight`.
39
+ *
40
+ * @param newBlockHeight - The block height to report for subsequent queries.
41
+ */
17
42
  setBlockHeight(newBlockHeight: number): void;
18
43
  getBlockHeight(): Promise<number>;
19
44
  getRawTransaction(txid: string): Promise<string>;
20
45
  sendRawTransaction(txHex: string): Promise<string>;
46
+ /**
47
+ * Add a UTXO to the in-memory set so that it becomes spendable by the specified address or
48
+ * locking bytecode.
49
+ *
50
+ * @param addressOrLockingBytecode - Either a CashAddress or a hex-encoded locking bytecode.
51
+ * @param utxo - The UTXO to make spendable.
52
+ */
21
53
  addUtxo(addressOrLockingBytecode: string, utxo: Utxo): void;
54
+ /**
55
+ * Clear the in-memory UTXO set and transaction history. Block height is preserved.
56
+ */
22
57
  reset(): void;
23
58
  }
@@ -3,7 +3,18 @@ import { sha256 } from '@cashscript/utils';
3
3
  import { Network } from '../interfaces.js';
4
4
  import { addressToLockScript, libauthTokenDetailsToCashScriptTokenDetails } from '../utils.js';
5
5
  import { DEFAULT_VM_TARGET } from '../libauth-template/utils.js';
6
+ /**
7
+ * An in-memory `NetworkProvider` useful for tests and examples. It does not connect to any
8
+ * external server; instead UTXOs are manually added via `addUtxo` and transactions are tracked
9
+ * in memory.
10
+ */
6
11
  export default class MockNetworkProvider {
12
+ /**
13
+ * Create a new MockNetworkProvider.
14
+ *
15
+ * @param options - Optional settings controlling UTXO-set updating and the VM target used by
16
+ * `TransactionBuilder.debug`.
17
+ */
7
18
  constructor(options) {
8
19
  // we use lockingBytecode hex as the key for utxoMap to make cash addresses and token addresses interchangeable
9
20
  this.utxoSet = [];
@@ -21,6 +32,11 @@ export default class MockNetworkProvider {
21
32
  const lockingBytecodeHex = typeof lockingBytecode === 'string' ? lockingBytecode : binToHex(lockingBytecode);
22
33
  return this.utxoSet.filter(([key]) => key === lockingBytecodeHex).map(([, utxo]) => utxo);
23
34
  }
35
+ /**
36
+ * Override the current block height returned by `getBlockHeight`.
37
+ *
38
+ * @param newBlockHeight - The block height to report for subsequent queries.
39
+ */
24
40
  setBlockHeight(newBlockHeight) {
25
41
  this.blockHeight = newBlockHeight;
26
42
  }
@@ -62,11 +78,21 @@ export default class MockNetworkProvider {
62
78
  // Note: the user can technically add the same UTXO multiple times (txid + vout), to the same or different addresses
63
79
  // but we don't check for this in the sendRawTransaction method. We might want to prevent duplicates from being added
64
80
  // in the first place.
81
+ /**
82
+ * Add a UTXO to the in-memory set so that it becomes spendable by the specified address or
83
+ * locking bytecode.
84
+ *
85
+ * @param addressOrLockingBytecode - Either a CashAddress or a hex-encoded locking bytecode.
86
+ * @param utxo - The UTXO to make spendable.
87
+ */
65
88
  addUtxo(addressOrLockingBytecode, utxo) {
66
89
  const lockingBytecode = isHex(addressOrLockingBytecode) ?
67
90
  addressOrLockingBytecode : binToHex(addressToLockScript(addressOrLockingBytecode));
68
91
  this.utxoSet.push([lockingBytecode, utxo]);
69
92
  }
93
+ /**
94
+ * Clear the in-memory UTXO set and transaction history. Block height is preserved.
95
+ */
70
96
  reset() {
71
97
  this.utxoSet = [];
72
98
  this.transactionMap = {};
@@ -0,0 +1,19 @@
1
+ export declare class NetworkProviderError extends Error {
2
+ originalError: string;
3
+ constructor(message: string, originalError: string);
4
+ }
5
+ export declare class NetworkProviderMissingInputsError extends NetworkProviderError {
6
+ constructor(originalError: string);
7
+ }
8
+ export declare class NetworkProviderMempoolConflictError extends NetworkProviderError {
9
+ constructor(originalError: string);
10
+ }
11
+ export declare class NetworkProviderTransactionAlreadySubmittedError extends NetworkProviderError {
12
+ constructor(originalError: string);
13
+ }
14
+ export declare class NetworkProviderAbsoluteTimelockError extends NetworkProviderError {
15
+ constructor(originalError: string);
16
+ }
17
+ export declare class NetworkProviderRelativeTimelockError extends NetworkProviderError {
18
+ constructor(originalError: string);
19
+ }
@@ -0,0 +1,38 @@
1
+ // Base class for errors returned by a network provider when broadcasting a transaction.
2
+ export class NetworkProviderError extends Error {
3
+ constructor(message, originalError) {
4
+ super(message);
5
+ this.originalError = originalError;
6
+ }
7
+ }
8
+ // Thrown when one or more inputs reference UTXOs that are missing or already spent.
9
+ export class NetworkProviderMissingInputsError extends NetworkProviderError {
10
+ constructor(originalError) {
11
+ super(`Transaction inputs are missing or already spent: ${originalError}`, originalError);
12
+ }
13
+ }
14
+ // Thrown when an input is already being spent by another transaction in the mempool (double-spend).
15
+ export class NetworkProviderMempoolConflictError extends NetworkProviderError {
16
+ constructor(originalError) {
17
+ super(`Transaction conflicts with an unconfirmed transaction in the mempool: ${originalError}`, originalError);
18
+ }
19
+ }
20
+ // Thrown when the same transaction has already been submitted (already in mempool or confirmed).
21
+ export class NetworkProviderTransactionAlreadySubmittedError extends NetworkProviderError {
22
+ constructor(originalError) {
23
+ super(`Transaction has already been submitted: ${originalError}`, originalError);
24
+ }
25
+ }
26
+ // Thrown when the transaction's nLockTime has not been satisfied (transaction is not yet final).
27
+ export class NetworkProviderAbsoluteTimelockError extends NetworkProviderError {
28
+ constructor(originalError) {
29
+ super(`Transaction is not yet final (nLockTime not satisfied): ${originalError}`, originalError);
30
+ }
31
+ }
32
+ // Thrown when a BIP68 relative timelock (sequence lock) on an input has not been satisfied.
33
+ export class NetworkProviderRelativeTimelockError extends NetworkProviderError {
34
+ constructor(originalError) {
35
+ super(`BIP68 sequence lock not satisfied: ${originalError}`, originalError);
36
+ }
37
+ }
38
+ //# sourceMappingURL=errors.js.map
@@ -1,5 +1,3 @@
1
1
  export type { default as NetworkProvider } from './NetworkProvider.js';
2
- export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js';
3
2
  export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
4
- export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
5
3
  export { default as MockNetworkProvider } from './MockNetworkProvider.js';
@@ -1,5 +1,3 @@
1
- export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js';
2
1
  export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
3
- export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
4
2
  export { default as MockNetworkProvider } from './MockNetworkProvider.js';
5
3
  //# sourceMappingURL=index.js.map