cashscript 0.13.0-next.4 → 0.13.0-next.6

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.
@@ -1,5 +1,5 @@
1
- import { Transaction as LibauthTransaction, WalletTemplate } from '@bitauth/libauth';
2
- import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions, VmResourceUsage } from './interfaces.js';
1
+ import { WalletTemplate } from '@bitauth/libauth';
2
+ import { Unlocker, Output, TransactionDetails, UnlockableUtxo, Utxo, InputOptions, VmResourceUsage, BchChangeOutputOptions } 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';
@@ -23,10 +23,12 @@ export declare class TransactionBuilder {
23
23
  addOutput(output: Output): this;
24
24
  addOutputs(outputs: Output[]): this;
25
25
  addOpReturnOutput(chunks: string[]): this;
26
+ addBchChangeOutputIfNeeded(changeOutputOptions: BchChangeOutputOptions): this;
27
+ getTransactionSize(): bigint;
26
28
  setLocktime(locktime: number): this;
27
29
  private checkMaxFee;
28
30
  private checkFungibleTokenBurn;
29
- buildLibauthTransaction(): LibauthTransaction;
31
+ private buildLibauthTransaction;
30
32
  build(): string;
31
33
  debug(): DebugResults;
32
34
  getVmResourceUsage(verbose?: boolean): Array<VmResourceUsage>;
@@ -1,6 +1,6 @@
1
1
  import { binToHex, decodeTransaction, decodeTransactionUnsafe, encodeTransaction, hexToBin, } from '@bitauth/libauth';
2
2
  import { isUnlockableUtxo, isStandardUnlockableUtxo, isContractUnlocker, } from './interfaces.js';
3
- import { cashScriptOutputToLibauthOutput, createOpReturnOutput, delay, generateLibauthSourceOutputs, validateInput, validateOutput, } from './utils.js';
3
+ import { calculateDust, cashScriptOutputToLibauthOutput, createOpReturnOutput, delay, generateLibauthSourceOutputs, getOutputSize, validateInput, validateOutput, } from './utils.js';
4
4
  import { FailedTransactionError } from './Errors.js';
5
5
  import { debugLibauthTemplate, getLibauthTemplate, getBitauthUri } from './libauth-template/LibauthTemplate.js';
6
6
  import { getWcContractInfo } from './walletconnect-utils.js';
@@ -46,6 +46,35 @@ export class TransactionBuilder {
46
46
  this.outputs.push(createOpReturnOutput(chunks));
47
47
  return this;
48
48
  }
49
+ addBchChangeOutputIfNeeded(changeOutputOptions) {
50
+ const totalBchInputAmount = this.inputs.reduce((total, input) => total + input.satoshis, 0n);
51
+ const totalBchOutputAmount = this.outputs.reduce((total, output) => total + output.amount, 0n);
52
+ const tentativeSurplus = totalBchInputAmount - totalBchOutputAmount;
53
+ const tentativeTransactionSize = this.getTransactionSize();
54
+ const tentativeFee = BigInt(Math.ceil(changeOutputOptions.feeRate * Number(tentativeTransactionSize)));
55
+ const tentativeChangeAmount = tentativeSurplus - tentativeFee;
56
+ if (tentativeChangeAmount < 0n) {
57
+ throw new Error(`Transaction does not have enough funds to cover the transaction fee rate of ${changeOutputOptions.feeRate} even without adding a change output. Current surplus: ${tentativeSurplus}, current fee: ${tentativeFee}, current change amount: ${tentativeChangeAmount}`);
58
+ }
59
+ const changeOutputSize = getOutputSize({
60
+ to: changeOutputOptions.to,
61
+ amount: tentativeSurplus,
62
+ });
63
+ const transactionSize = tentativeTransactionSize + BigInt(changeOutputSize);
64
+ const calculatedFee = BigInt(Math.ceil(changeOutputOptions.feeRate * Number(transactionSize)));
65
+ const changeAmount = tentativeSurplus - calculatedFee;
66
+ const changeOutput = { to: changeOutputOptions.to, amount: changeAmount };
67
+ const changeOutputDust = calculateDust(changeOutput);
68
+ if (changeAmount < changeOutputDust) {
69
+ return this;
70
+ }
71
+ this.outputs.push(changeOutput);
72
+ return this;
73
+ }
74
+ getTransactionSize() {
75
+ const transaction = this.buildLibauthTransaction(true);
76
+ return BigInt(encodeTransaction(transaction).byteLength);
77
+ }
49
78
  setLocktime(locktime) {
50
79
  this.locktime = locktime;
51
80
  return this;
@@ -87,8 +116,10 @@ export class TransactionBuilder {
87
116
  }
88
117
  }
89
118
  }
90
- buildLibauthTransaction() {
91
- this.checkFungibleTokenBurn();
119
+ buildLibauthTransaction(skipChecks = false) {
120
+ if (!skipChecks) {
121
+ this.checkFungibleTokenBurn();
122
+ }
92
123
  const inputs = this.inputs.map((utxo) => ({
93
124
  outpointIndex: utxo.vout,
94
125
  outpointTransactionHash: hexToBin(utxo.txid),
@@ -108,7 +139,9 @@ export class TransactionBuilder {
108
139
  inputScripts.forEach((script, i) => {
109
140
  transaction.inputs[i].unlockingBytecode = script;
110
141
  });
111
- this.checkMaxFee(transaction);
142
+ if (!skipChecks) {
143
+ this.checkMaxFee(transaction);
144
+ }
112
145
  return transaction;
113
146
  }
114
147
  build() {
@@ -163,7 +196,8 @@ export class TransactionBuilder {
163
196
  return getBitauthUri(this.getLibauthTemplate());
164
197
  }
165
198
  getLibauthTemplate() {
166
- return getLibauthTemplate(this);
199
+ const libauthTransaction = this.buildLibauthTransaction();
200
+ return getLibauthTemplate(this, libauthTransaction);
167
201
  }
168
202
  async send(raw) {
169
203
  const tx = this.build();
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export * from './Errors.js';
9
9
  export { type NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
10
10
  export { randomUtxo, randomToken, randomNFT } from './utils.js';
11
11
  export * from './walletconnect-utils.js';
12
+ export { gatherBchUtxos, gatherFungibleTokenUtxos, type GatherUtxosResult } from './transaction-utils.js';
package/dist/index.js CHANGED
@@ -8,4 +8,5 @@ export * from './Errors.js';
8
8
  export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, MockNetworkProvider, } from './network/index.js';
9
9
  export { randomUtxo, randomToken, randomNFT } from './utils.js';
10
10
  export * from './walletconnect-utils.js';
11
+ export { gatherBchUtxos, gatherFungibleTokenUtxos } from './transaction-utils.js';
11
12
  //# sourceMappingURL=index.js.map
@@ -58,6 +58,10 @@ export interface Output {
58
58
  amount: bigint;
59
59
  token?: TokenDetails;
60
60
  }
61
+ export interface BchChangeOutputOptions {
62
+ to: string | Uint8Array;
63
+ feeRate: number;
64
+ }
61
65
  export interface TokenDetails {
62
66
  amount: bigint;
63
67
  category: string;
@@ -1,6 +1,6 @@
1
- import { WalletTemplate } from '@bitauth/libauth';
1
+ import { TransactionBch, WalletTemplate } from '@bitauth/libauth';
2
2
  import { DebugResults } from '../debugging.js';
3
3
  import { TransactionBuilder } from '../TransactionBuilder.js';
4
- export declare const getLibauthTemplate: (transactionBuilder: TransactionBuilder) => WalletTemplate;
4
+ export declare const getLibauthTemplate: (transactionBuilder: TransactionBuilder, libauthTransaction: TransactionBch) => WalletTemplate;
5
5
  export declare const debugLibauthTemplate: (template: WalletTemplate, transaction: TransactionBuilder) => DebugResults;
6
6
  export declare const getBitauthUri: (template: WalletTemplate) => string;
@@ -8,11 +8,10 @@ import { zlibSync } from 'fflate';
8
8
  import MockNetworkProvider from '../network/MockNetworkProvider.js';
9
9
  import { addHexPrefixExceptEmpty, DEFAULT_VM_TARGET, formatBytecodeForDebugging, formatParametersForDebugging, getLockScriptName, getSignatureAndPubkeyFromP2PKHInput, getUnlockScriptName, serialiseTokenDetails } from './utils.js';
10
10
  // TODO: Add / improve descriptions throughout the template generation
11
- export const getLibauthTemplate = (transactionBuilder) => {
11
+ export const getLibauthTemplate = (transactionBuilder, libauthTransaction) => {
12
12
  if (transactionBuilder.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
13
13
  throw new Error('Cannot use debugging functionality with a transaction that contains custom unlockers');
14
14
  }
15
- const libauthTransaction = transactionBuilder.buildLibauthTransaction();
16
15
  const vmTarget = transactionBuilder.provider instanceof MockNetworkProvider
17
16
  ? transactionBuilder.provider.vmTarget
18
17
  : DEFAULT_VM_TARGET;
@@ -64,7 +64,7 @@ export const formatBytecodeForDebugging = (artifact) => {
64
64
  .map((asmElement) => (isHex(asmElement) ? `<0x${asmElement}>` : asmElement))
65
65
  .join('\n');
66
66
  }
67
- return formatBitAuthScript(bytecodeToScript(hexToBin(artifact.debug.bytecode)), artifact.debug.sourceMap, artifact.source);
67
+ return formatBitAuthScript(bytecodeToScript(hexToBin(artifact.debug.bytecode)), artifact.debug.sourceMap, artifact.source, artifact.debug.sourceTags);
68
68
  };
69
69
  export const serialiseTokenDetails = (token) => {
70
70
  if (!token)
@@ -1,14 +1,14 @@
1
1
  import { Utxo, Network, VmTarget } from '../interfaces.js';
2
2
  import NetworkProvider from './NetworkProvider.js';
3
3
  export interface MockNetworkProviderOptions {
4
- updateUtxoSet: boolean;
4
+ updateUtxoSet?: boolean;
5
5
  vmTarget?: VmTarget;
6
6
  }
7
7
  export default class MockNetworkProvider implements NetworkProvider {
8
8
  private utxoSet;
9
9
  private transactionMap;
10
+ private blockHeight;
10
11
  network: Network;
11
- blockHeight: number;
12
12
  options: MockNetworkProviderOptions;
13
13
  vmTarget: VmTarget;
14
14
  constructor(options?: Partial<MockNetworkProviderOptions>);
@@ -8,8 +8,8 @@ export default class MockNetworkProvider {
8
8
  // we use lockingBytecode hex as the key for utxoMap to make cash addresses and token addresses interchangeable
9
9
  this.utxoSet = [];
10
10
  this.transactionMap = {};
11
- this.network = Network.MOCKNET;
12
11
  this.blockHeight = 133700;
12
+ this.network = Network.MOCKNET;
13
13
  this.options = { updateUtxoSet: true, ...options };
14
14
  this.vmTarget = this.options.vmTarget ?? DEFAULT_VM_TARGET;
15
15
  }
@@ -0,0 +1,7 @@
1
+ import { Utxo } from './interfaces.js';
2
+ export interface GatherUtxosResult {
3
+ utxos: Utxo[];
4
+ totalAmount: bigint;
5
+ }
6
+ export declare function gatherBchUtxos(utxos: Utxo[], amount: bigint): GatherUtxosResult;
7
+ export declare function gatherFungibleTokenUtxos(utxos: Utxo[], tokenCategory: string, amount: bigint): GatherUtxosResult;
@@ -0,0 +1,36 @@
1
+ import { isFungibleTokenUtxo, isNonTokenUtxo } from './utils.js';
2
+ export function gatherBchUtxos(utxos, amount) {
3
+ const sortedBchUtxos = utxos
4
+ .filter(isNonTokenUtxo)
5
+ .toSorted((a, b) => Number(b.satoshis - a.satoshis));
6
+ const targetUtxos = [];
7
+ let total = 0n;
8
+ for (const utxo of sortedBchUtxos) {
9
+ if (total >= amount)
10
+ break;
11
+ total += utxo.satoshis;
12
+ targetUtxos.push(utxo);
13
+ }
14
+ if (total < amount) {
15
+ throw new Error('Not enough funds to cover the required amount');
16
+ }
17
+ return { utxos: targetUtxos, totalAmount: total };
18
+ }
19
+ export function gatherFungibleTokenUtxos(utxos, tokenCategory, amount) {
20
+ const sortedTokenUtxos = utxos
21
+ .filter((utxo) => isFungibleTokenUtxo(utxo) && utxo.token.category === tokenCategory)
22
+ .toSorted((a, b) => Number(b.token.amount - a.token.amount));
23
+ const targetUtxos = [];
24
+ let total = 0n;
25
+ for (const utxo of sortedTokenUtxos) {
26
+ if (total >= amount)
27
+ break;
28
+ total += utxo.token.amount;
29
+ targetUtxos.push(utxo);
30
+ }
31
+ if (total < amount) {
32
+ throw new Error('Not enough fungible tokens to cover the required amount');
33
+ }
34
+ return { utxos: targetUtxos, totalAmount: total };
35
+ }
36
+ //# sourceMappingURL=transaction-utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cashscript",
3
- "version": "0.13.0-next.4",
3
+ "version": "0.13.0-next.6",
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.4",
45
+ "@cashscript/utils": "^0.13.0-next.6",
46
46
  "@electrum-cash/network": "^4.1.3",
47
47
  "@mr-zwets/bchn-api-wrapper": "^1.0.1",
48
48
  "fflate": "^0.8.2",
@@ -56,5 +56,5 @@
56
56
  "typescript": "^5.9.2",
57
57
  "vitest": "^4.0.15"
58
58
  },
59
- "gitHead": "a9b4062bda82a2d80adf1bf21b846a62d7592e5b"
59
+ "gitHead": "c204d0ccadc16bbce1206cf00c0960c254ce3af0"
60
60
  }