cashscript 0.13.0-next.7 → 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.
- package/dist/Errors.d.ts +6 -0
- package/dist/Errors.js +10 -0
- package/dist/TransactionBuilder.d.ts +20 -2
- package/dist/TransactionBuilder.js +55 -6
- package/dist/interfaces.d.ts +4 -0
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +13 -3
- package/package.json +3 -3
package/dist/Errors.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export declare class OutputTokenCategoryInvalidError extends Error {
|
|
|
17
17
|
export declare class OutputTokenCommitmentInvalidError extends Error {
|
|
18
18
|
constructor(commitment: string);
|
|
19
19
|
}
|
|
20
|
+
export declare class OutputBchChangeLockedError extends Error {
|
|
21
|
+
constructor();
|
|
22
|
+
}
|
|
23
|
+
export declare class OutputTokenChangeLockedError extends Error {
|
|
24
|
+
constructor(category: string);
|
|
25
|
+
}
|
|
20
26
|
export declare class TokensToNonTokenAddressError extends Error {
|
|
21
27
|
constructor(address: string);
|
|
22
28
|
}
|
package/dist/Errors.js
CHANGED
|
@@ -29,6 +29,16 @@ export class OutputTokenCommitmentInvalidError extends Error {
|
|
|
29
29
|
super(`Provided token commitment ${commitment} is not a hex string`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
export class OutputBchChangeLockedError extends Error {
|
|
33
|
+
constructor() {
|
|
34
|
+
super('Tried to add a BCH input or output after a BCH change output was already added');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class OutputTokenChangeLockedError extends Error {
|
|
38
|
+
constructor(category) {
|
|
39
|
+
super(`Tried to add a token input or output with category ${category} after a change output with the same category was already added`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
32
42
|
export class TokensToNonTokenAddressError extends Error {
|
|
33
43
|
constructor(address) {
|
|
34
44
|
super(`Tried to send tokens to an address without token support, ${address}.`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WalletTemplate } from '@bitauth/libauth';
|
|
2
|
-
import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions, VmResourceUsage, BchChangeOutputOptions } from './interfaces.js';
|
|
2
|
+
import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions, VmResourceUsage, BchChangeOutputOptions, TokenChangeOutputOptions } from './interfaces.js';
|
|
3
3
|
import { NetworkProvider } from './network/index.js';
|
|
4
4
|
import { DebugResults } from './debugging.js';
|
|
5
5
|
import { WcTransactionOptions } from './walletconnect-utils.js';
|
|
@@ -30,6 +30,7 @@ export declare class TransactionBuilder {
|
|
|
30
30
|
outputs: Output[];
|
|
31
31
|
locktime: number;
|
|
32
32
|
options: TransactionBuilderOptions;
|
|
33
|
+
private changeLocks;
|
|
33
34
|
/**
|
|
34
35
|
* Create a new TransactionBuilder.
|
|
35
36
|
*
|
|
@@ -95,11 +96,28 @@ export declare class TransactionBuilder {
|
|
|
95
96
|
* fee is computed from the transaction size at the configured fee rate; dust-sized change is
|
|
96
97
|
* simply absorbed into the fee.
|
|
97
98
|
*
|
|
99
|
+
* Should be called *after* all explicit inputs and outputs are added.
|
|
100
|
+
*
|
|
98
101
|
* @param changeOutputOptions - The destination address and the fee rate (in sats/byte) to use.
|
|
99
102
|
* @returns This builder for chaining.
|
|
100
|
-
* @throws If the available surplus is insufficient to cover the fee for the configured rate
|
|
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.
|
|
101
105
|
*/
|
|
102
106
|
addBchChangeOutputIfNeeded(changeOutputOptions: BchChangeOutputOptions): this;
|
|
107
|
+
/**
|
|
108
|
+
* Add a fungible token change output for the configured category if the transaction's inputs
|
|
109
|
+
* contain more tokens of that category than its outputs. The change output is sent to the
|
|
110
|
+
* provided token address and carries the dust-minimum BCH amount.
|
|
111
|
+
*
|
|
112
|
+
* Should be called *after* all explicit token outputs for the category are added and *before*
|
|
113
|
+
* `addBchChangeOutputIfNeeded`.
|
|
114
|
+
*
|
|
115
|
+
* @param changeOutputOptions - The token category to handle and the destination token address.
|
|
116
|
+
* @returns This builder for chaining.
|
|
117
|
+
* @throws If the destination is not a token-supporting address, or if a corresponding change output
|
|
118
|
+
* or BCH change output was already added.
|
|
119
|
+
*/
|
|
120
|
+
addTokenChangeOutputIfNeeded(changeOutputOptions: TokenChangeOutputOptions): this;
|
|
103
121
|
/**
|
|
104
122
|
* Build the transaction (skipping fee and burn checks) and return its encoded byte length.
|
|
105
123
|
*
|
|
@@ -23,6 +23,7 @@ export class TransactionBuilder {
|
|
|
23
23
|
this.inputs = [];
|
|
24
24
|
this.outputs = [];
|
|
25
25
|
this.locktime = 0;
|
|
26
|
+
this.changeLocks = {};
|
|
26
27
|
this.provider = options.provider;
|
|
27
28
|
this.options = {
|
|
28
29
|
allowImplicitFungibleTokenBurn: options.allowImplicitFungibleTokenBurn ?? false,
|
|
@@ -43,7 +44,7 @@ export class TransactionBuilder {
|
|
|
43
44
|
return this.addInputs([utxo], unlocker, options);
|
|
44
45
|
}
|
|
45
46
|
addInputs(utxos, unlocker, options) {
|
|
46
|
-
utxos.forEach(validateInput);
|
|
47
|
+
utxos.forEach((utxo) => validateInput(utxo, this.changeLocks));
|
|
47
48
|
if ((!unlocker && utxos.some((utxo) => !isUnlockableUtxo(utxo)))
|
|
48
49
|
|| (unlocker && utxos.some((utxo) => isUnlockableUtxo(utxo)))) {
|
|
49
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');
|
|
@@ -74,7 +75,7 @@ export class TransactionBuilder {
|
|
|
74
75
|
* @throws If any output is invalid.
|
|
75
76
|
*/
|
|
76
77
|
addOutputs(outputs) {
|
|
77
|
-
outputs.forEach((output) => validateOutput(output, this.provider.network));
|
|
78
|
+
outputs.forEach((output) => validateOutput(output, this.provider.network, this.changeLocks));
|
|
78
79
|
this.outputs = this.outputs.concat(outputs);
|
|
79
80
|
return this;
|
|
80
81
|
}
|
|
@@ -87,7 +88,7 @@ export class TransactionBuilder {
|
|
|
87
88
|
* @returns This builder for chaining.
|
|
88
89
|
*/
|
|
89
90
|
addOpReturnOutput(chunks) {
|
|
90
|
-
this.
|
|
91
|
+
this.addOutput(createOpReturnOutput(chunks));
|
|
91
92
|
return this;
|
|
92
93
|
}
|
|
93
94
|
/**
|
|
@@ -95,9 +96,12 @@ export class TransactionBuilder {
|
|
|
95
96
|
* fee is computed from the transaction size at the configured fee rate; dust-sized change is
|
|
96
97
|
* simply absorbed into the fee.
|
|
97
98
|
*
|
|
99
|
+
* Should be called *after* all explicit inputs and outputs are added.
|
|
100
|
+
*
|
|
98
101
|
* @param changeOutputOptions - The destination address and the fee rate (in sats/byte) to use.
|
|
99
102
|
* @returns This builder for chaining.
|
|
100
|
-
* @throws If the available surplus is insufficient to cover the fee for the configured rate
|
|
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.
|
|
101
105
|
*/
|
|
102
106
|
addBchChangeOutputIfNeeded(changeOutputOptions) {
|
|
103
107
|
const totalBchInputAmount = this.inputs.reduce((total, input) => total + input.satoshis, 0n);
|
|
@@ -119,9 +123,47 @@ export class TransactionBuilder {
|
|
|
119
123
|
const changeOutput = { to: changeOutputOptions.to, amount: changeAmount };
|
|
120
124
|
const changeOutputDust = calculateDust(changeOutput);
|
|
121
125
|
if (changeAmount < changeOutputDust) {
|
|
126
|
+
this.changeLocks.BCH = true;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
this.addOutput(changeOutput);
|
|
130
|
+
this.changeLocks.BCH = true;
|
|
131
|
+
return this;
|
|
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;
|
|
122
157
|
return this;
|
|
123
158
|
}
|
|
124
|
-
|
|
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;
|
|
125
167
|
return this;
|
|
126
168
|
}
|
|
127
169
|
/**
|
|
@@ -318,7 +360,14 @@ export class TransactionBuilder {
|
|
|
318
360
|
}
|
|
319
361
|
catch (e) {
|
|
320
362
|
const reason = e.error ?? e.message;
|
|
321
|
-
|
|
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
|
+
}
|
|
322
371
|
}
|
|
323
372
|
}
|
|
324
373
|
async getTxDetails(txid, raw) {
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Transaction } from '@bitauth/libauth';
|
|
2
2
|
import { Script } from '@cashscript/utils';
|
|
3
3
|
import { Utxo, Output, Network, LibauthOutput, TokenDetails, AddressType, UnlockableUtxo, LibauthTokenDetails, ContractType } from './interfaces.js';
|
|
4
|
-
export declare function validateInput(utxo: Utxo): void;
|
|
5
|
-
export declare function validateOutput(output: Output, network: Network): void;
|
|
4
|
+
export declare function validateInput(utxo: Utxo, changeLocks: Record<string, boolean>): void;
|
|
5
|
+
export declare function validateOutput(output: Output, network: Network, changeLocks: Record<string, boolean>): void;
|
|
6
6
|
export declare function isOpReturnOutput(output: Output): boolean;
|
|
7
7
|
export declare function calculateDust(output: Output): number;
|
|
8
8
|
export declare function getOutputSize(output: Output): number;
|
package/dist/utils.js
CHANGED
|
@@ -2,14 +2,16 @@ import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLocki
|
|
|
2
2
|
import { encodeInt, hash160, hash256, sha256, Op, scriptToBytecode, encodeNullDataScript, } from '@cashscript/utils';
|
|
3
3
|
import { Network, } from './interfaces.js';
|
|
4
4
|
import { VERSION_SIZE, LOCKTIME_SIZE } from './constants.js';
|
|
5
|
-
import { OutputSatoshisTooSmallError, OutputTokenAmountTooSmallError, TokensToNonTokenAddressError, UndefinedInputError, OutputAddressNetworkMismatchError, OutputTokenCategoryInvalidError, OutputTokenCommitmentInvalidError, } from './Errors.js';
|
|
5
|
+
import { OutputSatoshisTooSmallError, OutputTokenAmountTooSmallError, TokensToNonTokenAddressError, UndefinedInputError, OutputAddressNetworkMismatchError, OutputTokenCategoryInvalidError, OutputTokenCommitmentInvalidError, OutputBchChangeLockedError, OutputTokenChangeLockedError, } from './Errors.js';
|
|
6
6
|
// ////////// PARAMETER VALIDATION ////////////////////////////////////////////
|
|
7
|
-
export function validateInput(utxo) {
|
|
7
|
+
export function validateInput(utxo, changeLocks) {
|
|
8
8
|
if (!utxo) {
|
|
9
9
|
throw new UndefinedInputError();
|
|
10
10
|
}
|
|
11
|
+
validateChangeLocks(changeLocks, utxo.token?.category);
|
|
11
12
|
}
|
|
12
|
-
export function validateOutput(output, network) {
|
|
13
|
+
export function validateOutput(output, network, changeLocks) {
|
|
14
|
+
validateChangeLocks(changeLocks, output.token?.category);
|
|
13
15
|
if (isOpReturnOutput(output))
|
|
14
16
|
return;
|
|
15
17
|
const minimumAmount = calculateDust(output);
|
|
@@ -39,6 +41,14 @@ export function validateOutput(output, network) {
|
|
|
39
41
|
throw new OutputAddressNetworkMismatchError(output.to, networkPrefix);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
44
|
+
function validateChangeLocks(changeLocks, category) {
|
|
45
|
+
if (changeLocks.BCH) {
|
|
46
|
+
throw new OutputBchChangeLockedError();
|
|
47
|
+
}
|
|
48
|
+
if (category && changeLocks[category]) {
|
|
49
|
+
throw new OutputTokenChangeLockedError(category);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
42
52
|
export function isOpReturnOutput(output) {
|
|
43
53
|
return typeof output.to !== 'string' && output.to[0] === Op.OP_RETURN;
|
|
44
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cashscript",
|
|
3
|
-
"version": "0.13.0-next.
|
|
3
|
+
"version": "0.13.0-next.8",
|
|
4
4
|
"description": "Easily write and interact with Bitcoin Cash contracts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin cash",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@bitauth/libauth": "^3.1.0-next.8",
|
|
45
|
-
"@cashscript/utils": "^0.13.0-next.
|
|
45
|
+
"@cashscript/utils": "^0.13.0-next.8",
|
|
46
46
|
"@electrum-cash/network": "^4.1.3",
|
|
47
47
|
"fflate": "^0.8.2",
|
|
48
48
|
"semver": "^7.7.2"
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"typescript": "^5.9.2",
|
|
57
57
|
"vitest": "^4.0.15"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "579998062f2f9c52393568b09b580407228cb79f"
|
|
60
60
|
}
|