cashscript 0.13.0-next.6 → 0.13.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Argument.d.ts +16 -0
- package/dist/Argument.js +16 -0
- package/dist/Contract.d.ts +44 -6
- package/dist/Contract.js +38 -11
- package/dist/Errors.d.ts +9 -0
- package/dist/Errors.js +15 -0
- package/dist/SignatureTemplate.d.ts +50 -0
- package/dist/SignatureTemplate.js +49 -0
- package/dist/TransactionBuilder.d.ts +157 -0
- package/dist/TransactionBuilder.js +118 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/network/ElectrumNetworkProvider.d.ts +33 -0
- package/dist/network/ElectrumNetworkProvider.js +78 -1
- package/dist/network/MockNetworkProvider.d.ts +35 -0
- package/dist/network/MockNetworkProvider.js +26 -0
- package/dist/network/errors.d.ts +19 -0
- package/dist/network/errors.js +38 -0
- package/dist/network/index.d.ts +0 -2
- package/dist/network/index.js +0 -2
- package/dist/transaction-utils.d.ts +23 -0
- package/dist/transaction-utils.js +19 -0
- package/dist/utils.d.ts +24 -2
- package/dist/utils.js +50 -14
- package/dist/walletconnect-utils.d.ts +23 -0
- package/dist/walletconnect-utils.js +23 -0
- package/package.json +5 -5
- package/dist/network/BitcoinRpcNetworkProvider.d.ts +0 -17
- package/dist/network/BitcoinRpcNetworkProvider.js +0 -32
- package/dist/network/FullStackNetworkProvider.d.ts +0 -41
- package/dist/network/FullStackNetworkProvider.js +0 -36
|
@@ -6,7 +6,19 @@ 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 = [];
|
|
@@ -17,6 +29,16 @@ export class TransactionBuilder {
|
|
|
17
29
|
...options,
|
|
18
30
|
};
|
|
19
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Add a single UTXO as an input to the transaction.
|
|
34
|
+
*
|
|
35
|
+
* @param utxo - The UTXO to spend.
|
|
36
|
+
* @param unlocker - The unlocker to generate the unlocking bytecode for this input. Typically
|
|
37
|
+
* obtained from `contract.unlock.<functionName>(...args)` or `signatureTemplate.unlockP2PKH()`.
|
|
38
|
+
* @param options - Optional per-input options such as a custom sequence number.
|
|
39
|
+
* @returns This builder for chaining.
|
|
40
|
+
* @throws If the UTXO is invalid.
|
|
41
|
+
*/
|
|
20
42
|
addInput(utxo, unlocker, options) {
|
|
21
43
|
return this.addInputs([utxo], unlocker, options);
|
|
22
44
|
}
|
|
@@ -33,19 +55,50 @@ export class TransactionBuilder {
|
|
|
33
55
|
this.inputs = this.inputs.concat(utxos.map(((utxo) => ({ ...utxo, unlocker, options }))));
|
|
34
56
|
return this;
|
|
35
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Add a single output to the transaction.
|
|
60
|
+
*
|
|
61
|
+
* @param output - The output to add. Its address, amount and optional token are validated
|
|
62
|
+
* against the provider's network.
|
|
63
|
+
* @returns This builder for chaining.
|
|
64
|
+
* @throws If the output is invalid.
|
|
65
|
+
*/
|
|
36
66
|
addOutput(output) {
|
|
37
67
|
return this.addOutputs([output]);
|
|
38
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Add multiple outputs to the transaction.
|
|
71
|
+
*
|
|
72
|
+
* @param outputs - The outputs to add. Each output is validated against the provider's network.
|
|
73
|
+
* @returns This builder for chaining.
|
|
74
|
+
* @throws If any output is invalid.
|
|
75
|
+
*/
|
|
39
76
|
addOutputs(outputs) {
|
|
40
|
-
outputs.forEach(validateOutput);
|
|
77
|
+
outputs.forEach((output) => validateOutput(output, this.provider.network));
|
|
41
78
|
this.outputs = this.outputs.concat(outputs);
|
|
42
79
|
return this;
|
|
43
80
|
}
|
|
44
81
|
// TODO: allow uint8array for chunks
|
|
82
|
+
/**
|
|
83
|
+
* Append an `OP_RETURN` output containing the provided data chunks. Hex strings prefixed with
|
|
84
|
+
* `0x` are decoded as bytes; other strings are encoded as UTF-8.
|
|
85
|
+
*
|
|
86
|
+
* @param chunks - The data chunks to include after the `OP_RETURN` opcode.
|
|
87
|
+
* @returns This builder for chaining.
|
|
88
|
+
*/
|
|
45
89
|
addOpReturnOutput(chunks) {
|
|
46
90
|
this.outputs.push(createOpReturnOutput(chunks));
|
|
47
91
|
return this;
|
|
48
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Add a BCH change output for the remaining value after fees, if it exceeds the dust limit. The
|
|
95
|
+
* fee is computed from the transaction size at the configured fee rate; dust-sized change is
|
|
96
|
+
* simply absorbed into the fee.
|
|
97
|
+
*
|
|
98
|
+
* @param changeOutputOptions - The destination address and the fee rate (in sats/byte) to use.
|
|
99
|
+
* @returns This builder for chaining.
|
|
100
|
+
* @throws If the available surplus is insufficient to cover the fee for the configured rate.
|
|
101
|
+
*/
|
|
49
102
|
addBchChangeOutputIfNeeded(changeOutputOptions) {
|
|
50
103
|
const totalBchInputAmount = this.inputs.reduce((total, input) => total + input.satoshis, 0n);
|
|
51
104
|
const totalBchOutputAmount = this.outputs.reduce((total, output) => total + output.amount, 0n);
|
|
@@ -71,10 +124,21 @@ export class TransactionBuilder {
|
|
|
71
124
|
this.outputs.push(changeOutput);
|
|
72
125
|
return this;
|
|
73
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Build the transaction (skipping fee and burn checks) and return its encoded byte length.
|
|
129
|
+
*
|
|
130
|
+
* @returns The size of the transaction in bytes.
|
|
131
|
+
*/
|
|
74
132
|
getTransactionSize() {
|
|
75
133
|
const transaction = this.buildLibauthTransaction(true);
|
|
76
134
|
return BigInt(encodeTransaction(transaction).byteLength);
|
|
77
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Set the `nLockTime` of the transaction.
|
|
138
|
+
*
|
|
139
|
+
* @param locktime - The absolute locktime to use (block height or UNIX timestamp).
|
|
140
|
+
* @returns This builder for chaining.
|
|
141
|
+
*/
|
|
78
142
|
setLocktime(locktime) {
|
|
79
143
|
this.locktime = locktime;
|
|
80
144
|
return this;
|
|
@@ -144,10 +208,26 @@ export class TransactionBuilder {
|
|
|
144
208
|
}
|
|
145
209
|
return transaction;
|
|
146
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Build the transaction, applying fee and implicit-burn checks, and return the hex-encoded
|
|
213
|
+
* transaction bytes.
|
|
214
|
+
*
|
|
215
|
+
* @returns The signed transaction as a hex string.
|
|
216
|
+
* @throws If the transaction fee exceeds the configured maximum, or if fungible tokens are
|
|
217
|
+
* implicitly burned without `allowImplicitFungibleTokenBurn` enabled.
|
|
218
|
+
*/
|
|
147
219
|
build() {
|
|
148
220
|
const transaction = this.buildLibauthTransaction();
|
|
149
221
|
return binToHex(encodeTransaction(transaction));
|
|
150
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Locally evaluate the transaction against the Bitcoin Cash VM using debug information from the
|
|
225
|
+
* contract artifacts. Throws a descriptive error if any input fails evaluation.
|
|
226
|
+
*
|
|
227
|
+
* @returns The full debug execution trace for every scenario in the generated libauth template.
|
|
228
|
+
* @throws If the transaction contains inputs with custom (non-standard) unlockers, or if the
|
|
229
|
+
* evaluation fails (e.g. a failing `require` statement).
|
|
230
|
+
*/
|
|
151
231
|
debug() {
|
|
152
232
|
if (this.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
|
|
153
233
|
throw new Error('Cannot debug a transaction with custom unlocker');
|
|
@@ -161,6 +241,15 @@ export class TransactionBuilder {
|
|
|
161
241
|
}
|
|
162
242
|
return debugLibauthTemplate(this.getLibauthTemplate(), this);
|
|
163
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Compute VM resource usage (ops, op cost budget, sigchecks, hash iterations) for each input
|
|
246
|
+
* by running the transaction through `debug`.
|
|
247
|
+
*
|
|
248
|
+
* @param verbose - When `true`, also prints a formatted table of the results via `console.table`.
|
|
249
|
+
* @returns One entry per input with its VM resource usage metrics.
|
|
250
|
+
* @throws If the transaction contains inputs with custom (non-standard) unlockers, or if the
|
|
251
|
+
* evaluation fails.
|
|
252
|
+
*/
|
|
164
253
|
getVmResourceUsage(verbose = false) {
|
|
165
254
|
// Note that only StandardUnlockableUtxo inputs are supported for debugging, so any transaction with custom unlockers
|
|
166
255
|
// cannot be debugged (and therefore cannot return VM resource usage)
|
|
@@ -191,10 +280,27 @@ export class TransactionBuilder {
|
|
|
191
280
|
}
|
|
192
281
|
return vmResourceUsage;
|
|
193
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Build a Bitauth IDE URI that loads the transaction (and all private keys required to sign it)
|
|
285
|
+
* in the online Bitauth IDE debugger.
|
|
286
|
+
*
|
|
287
|
+
* WARNING: The URI embeds every private key used in the transaction. Do not share this URI if
|
|
288
|
+
* the transaction is signed with real private keys.
|
|
289
|
+
*
|
|
290
|
+
* @returns A Bitauth IDE URL for debugging this transaction.
|
|
291
|
+
* @throws If the transaction cannot be built (fee exceeds limit or fungible tokens burned).
|
|
292
|
+
*/
|
|
194
293
|
getBitauthUri() {
|
|
195
294
|
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
295
|
return getBitauthUri(this.getLibauthTemplate());
|
|
197
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Build the transaction and return the corresponding libauth `WalletTemplate`. Useful for
|
|
299
|
+
* exporting the transaction to external libauth-compatible tooling.
|
|
300
|
+
*
|
|
301
|
+
* @returns A libauth `WalletTemplate` describing this transaction.
|
|
302
|
+
* @throws If the transaction cannot be built (fee exceeds limit or fungible tokens burned).
|
|
303
|
+
*/
|
|
198
304
|
getLibauthTemplate() {
|
|
199
305
|
const libauthTransaction = this.buildLibauthTransaction();
|
|
200
306
|
return getLibauthTemplate(this, libauthTransaction);
|
|
@@ -212,7 +318,7 @@ export class TransactionBuilder {
|
|
|
212
318
|
}
|
|
213
319
|
catch (e) {
|
|
214
320
|
const reason = e.error ?? e.message;
|
|
215
|
-
throw new FailedTransactionError(reason, this.
|
|
321
|
+
throw new FailedTransactionError(reason, getBitauthUri(this.getLibauthTemplate()));
|
|
216
322
|
}
|
|
217
323
|
}
|
|
218
324
|
async getTxDetails(txid, raw) {
|
|
@@ -232,6 +338,16 @@ export class TransactionBuilder {
|
|
|
232
338
|
// Should not happen
|
|
233
339
|
throw new Error('Could not retrieve transaction details for over 10 minutes');
|
|
234
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Build the transaction and format it as a BCH WalletConnect transaction object suitable for
|
|
343
|
+
* signing and broadcasting via a BCH WalletConnect-compatible Bitcoin Cash wallet.
|
|
344
|
+
*
|
|
345
|
+
* See the [BCH WalletConnect spec](https://github.com/mainnet-pat/wc2-bch-bcr) for the object format.
|
|
346
|
+
*
|
|
347
|
+
* @param options - Optional WalletConnect options such as `broadcast` and `userPrompt`.
|
|
348
|
+
* @returns A WalletConnect transaction object ready to be sent to a WalletConnect wallet.
|
|
349
|
+
* @throws If the transaction cannot be built (fee exceeds limit or fungible tokens burned).
|
|
350
|
+
*/
|
|
235
351
|
generateWcTransactionObject(options) {
|
|
236
352
|
const encodedTransaction = this.build();
|
|
237
353
|
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
|
|
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
|
|
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';
|
|
@@ -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
|
-
|
|
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
|
package/dist/network/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/network/index.js
CHANGED
|
@@ -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
|
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
import { Utxo } from './interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Result of `gatherBchUtxos` and `gatherFungibleTokenUtxos`: the selected UTXOs and the total
|
|
4
|
+
* amount they cover (satoshis for BCH, token amount for fungible tokens).
|
|
5
|
+
*/
|
|
2
6
|
export interface GatherUtxosResult {
|
|
3
7
|
utxos: Utxo[];
|
|
4
8
|
totalAmount: bigint;
|
|
5
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Select non-token UTXOs from the provided list, largest-first, until the requested amount of
|
|
12
|
+
* satoshis is covered.
|
|
13
|
+
*
|
|
14
|
+
* @param utxos - UTXOs to choose from. Token UTXOs are ignored.
|
|
15
|
+
* @param amount - The minimum total satoshis that the selected UTXOs must cover.
|
|
16
|
+
* @returns The selected UTXOs and their cumulative satoshi amount.
|
|
17
|
+
* @throws If the available non-token UTXOs do not cover the requested amount.
|
|
18
|
+
*/
|
|
6
19
|
export declare function gatherBchUtxos(utxos: Utxo[], amount: bigint): GatherUtxosResult;
|
|
20
|
+
/**
|
|
21
|
+
* Select fungible token UTXOs (for a specific token category) from the provided list,
|
|
22
|
+
* largest-first, until the requested token amount is covered. NFT UTXOs are ignored.
|
|
23
|
+
*
|
|
24
|
+
* @param utxos - UTXOs to choose from.
|
|
25
|
+
* @param tokenCategory - The hex-encoded token category to filter on.
|
|
26
|
+
* @param amount - The minimum total token amount that the selected UTXOs must cover.
|
|
27
|
+
* @returns The selected UTXOs and their cumulative token amount.
|
|
28
|
+
* @throws If the available fungible token UTXOs do not cover the requested amount.
|
|
29
|
+
*/
|
|
7
30
|
export declare function gatherFungibleTokenUtxos(utxos: Utxo[], tokenCategory: string, amount: bigint): GatherUtxosResult;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { isFungibleTokenUtxo, isNonTokenUtxo } from './utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* Select non-token UTXOs from the provided list, largest-first, until the requested amount of
|
|
4
|
+
* satoshis is covered.
|
|
5
|
+
*
|
|
6
|
+
* @param utxos - UTXOs to choose from. Token UTXOs are ignored.
|
|
7
|
+
* @param amount - The minimum total satoshis that the selected UTXOs must cover.
|
|
8
|
+
* @returns The selected UTXOs and their cumulative satoshi amount.
|
|
9
|
+
* @throws If the available non-token UTXOs do not cover the requested amount.
|
|
10
|
+
*/
|
|
2
11
|
export function gatherBchUtxos(utxos, amount) {
|
|
3
12
|
const sortedBchUtxos = utxos
|
|
4
13
|
.filter(isNonTokenUtxo)
|
|
@@ -16,6 +25,16 @@ export function gatherBchUtxos(utxos, amount) {
|
|
|
16
25
|
}
|
|
17
26
|
return { utxos: targetUtxos, totalAmount: total };
|
|
18
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Select fungible token UTXOs (for a specific token category) from the provided list,
|
|
30
|
+
* largest-first, until the requested token amount is covered. NFT UTXOs are ignored.
|
|
31
|
+
*
|
|
32
|
+
* @param utxos - UTXOs to choose from.
|
|
33
|
+
* @param tokenCategory - The hex-encoded token category to filter on.
|
|
34
|
+
* @param amount - The minimum total token amount that the selected UTXOs must cover.
|
|
35
|
+
* @returns The selected UTXOs and their cumulative token amount.
|
|
36
|
+
* @throws If the available fungible token UTXOs do not cover the requested amount.
|
|
37
|
+
*/
|
|
19
38
|
export function gatherFungibleTokenUtxos(utxos, tokenCategory, amount) {
|
|
20
39
|
const sortedTokenUtxos = utxos
|
|
21
40
|
.filter((utxo) => isFungibleTokenUtxo(utxo) && utxo.token.category === tokenCategory)
|