cashscript 0.7.6 → 0.8.0-next.1
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/README.md +2 -6
- package/dist/{main/Argument.d.ts → Argument.d.ts} +1 -1
- package/dist/{module/Argument.js → Argument.js} +1 -1
- package/dist/{module/Contract.d.ts → Contract.d.ts} +8 -6
- package/dist/{module/Contract.js → Contract.js} +15 -25
- package/dist/{main/Errors.d.ts → Errors.d.ts} +4 -1
- package/dist/{module/Errors.js → Errors.js} +7 -3
- package/dist/{main/SignatureTemplate.d.ts → SignatureTemplate.d.ts} +2 -3
- package/dist/{module/SignatureTemplate.js → SignatureTemplate.js} +4 -5
- package/dist/{module/Transaction.d.ts → Transaction.d.ts} +7 -5
- package/dist/Transaction.js +352 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -0
- package/dist/{main/index.d.ts → index.d.ts} +1 -1
- package/dist/{module/index.js → index.js} +2 -3
- package/dist/{module/interfaces.d.ts → interfaces.d.ts} +33 -6
- package/dist/{module/interfaces.js → interfaces.js} +0 -2
- package/dist/{module/network → network}/BitcoinRpcNetworkProvider.d.ts +2 -3
- package/dist/network/BitcoinRpcNetworkProvider.js +29 -0
- package/dist/{module/network → network}/ElectrumNetworkProvider.js +53 -72
- package/dist/network/FullStackNetworkProvider.js +33 -0
- package/dist/{main/network → network}/index.d.ts +0 -1
- package/dist/{module/network → network}/index.js +0 -1
- package/dist/{main/utils.d.ts → utils.d.ts} +10 -6
- package/dist/{module/utils.js → utils.js} +90 -46
- package/package.json +18 -16
- package/dist/main/Argument.js +0 -60
- package/dist/main/Contract.d.ts +0 -23
- package/dist/main/Contract.js +0 -87
- package/dist/main/Errors.js +0 -84
- package/dist/main/SignatureTemplate.js +0 -45
- package/dist/main/Transaction.d.ts +0 -39
- package/dist/main/Transaction.js +0 -258
- package/dist/main/constants.d.ts +0 -5
- package/dist/main/constants.js +0 -9
- package/dist/main/index.js +0 -50
- package/dist/main/interfaces.d.ts +0 -43
- package/dist/main/interfaces.js +0 -32
- package/dist/main/network/BitboxNetworkProvider.d.ts +0 -26
- package/dist/main/network/BitboxNetworkProvider.js +0 -40
- package/dist/main/network/BitcoinRpcNetworkProvider.d.ts +0 -41
- package/dist/main/network/BitcoinRpcNetworkProvider.js +0 -49
- package/dist/main/network/ElectrumNetworkProvider.js +0 -162
- package/dist/main/network/FullStackNetworkProvider.js +0 -55
- package/dist/main/network/NetworkProvider.js +0 -3
- package/dist/main/network/index.js +0 -15
- package/dist/main/utils.js +0 -205
- package/dist/module/Argument.d.ts +0 -3
- package/dist/module/Errors.d.ts +0 -66
- package/dist/module/SignatureTemplate.d.ts +0 -15
- package/dist/module/Transaction.js +0 -251
- package/dist/module/constants.d.ts +0 -5
- package/dist/module/constants.js +0 -6
- package/dist/module/index.d.ts +0 -10
- package/dist/module/network/BitboxNetworkProvider.d.ts +0 -26
- package/dist/module/network/BitboxNetworkProvider.js +0 -37
- package/dist/module/network/BitcoinRpcNetworkProvider.js +0 -46
- package/dist/module/network/ElectrumNetworkProvider.d.ts +0 -19
- package/dist/module/network/FullStackNetworkProvider.d.ts +0 -40
- package/dist/module/network/FullStackNetworkProvider.js +0 -52
- package/dist/module/network/NetworkProvider.d.ts +0 -31
- package/dist/module/network/index.d.ts +0 -5
- package/dist/module/utils.d.ts +0 -26
- /package/dist/{main/network → network}/ElectrumNetworkProvider.d.ts +0 -0
- /package/dist/{main/network → network}/FullStackNetworkProvider.d.ts +0 -0
- /package/dist/{main/network → network}/NetworkProvider.d.ts +0 -0
- /package/dist/{module/network → network}/NetworkProvider.js +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CashScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
4
|
[](https://codecov.io/gh/Bitcoin-com/cashscript/)
|
|
5
5
|
[](https://www.npmjs.com/package/cashscript)
|
|
6
6
|
[](https://www.npmjs.com/package/cashscript)
|
|
@@ -26,16 +26,12 @@ npm install cashscript
|
|
|
26
26
|
import { Contract, ... } from 'cashscript';
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
```js
|
|
30
|
-
const { Contract, ... } = require('cashscript');
|
|
31
|
-
```
|
|
32
|
-
|
|
33
29
|
Using the CashScript SDK, you can import contract artifact files, create new instances of these contracts, and interact with these instances:
|
|
34
30
|
|
|
35
31
|
```ts
|
|
36
32
|
...
|
|
37
33
|
// Import the P2PKH artifact
|
|
38
|
-
|
|
34
|
+
import P2PKH from './p2pkh-artifact.json' assert { type: 'json' };
|
|
39
35
|
|
|
40
36
|
// Instantiate a network provider for CashScript's network operations
|
|
41
37
|
const provider = new ElectrumNetworkProvider('mainnet');
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
2
|
-
export declare type Argument =
|
|
2
|
+
export declare type Argument = bigint | boolean | string | Uint8Array | SignatureTemplate;
|
|
3
3
|
export declare function encodeArgument(argument: Argument, typeStr: string): Uint8Array | SignatureTemplate;
|
|
@@ -11,7 +11,7 @@ export function encodeArgument(argument, typeStr) {
|
|
|
11
11
|
return encodeBool(argument);
|
|
12
12
|
}
|
|
13
13
|
if (type === PrimitiveType.INT) {
|
|
14
|
-
if (typeof argument !== '
|
|
14
|
+
if (typeof argument !== 'bigint') {
|
|
15
15
|
throw new TypeError(typeof argument, type);
|
|
16
16
|
}
|
|
17
17
|
return encodeInt(argument);
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { Artifact } from '@cashscript/utils';
|
|
2
2
|
import { Transaction } from './Transaction.js';
|
|
3
3
|
import { Argument } from './Argument.js';
|
|
4
|
-
import { Utxo } from './interfaces.js';
|
|
5
|
-
import NetworkProvider from './network/NetworkProvider.js';
|
|
4
|
+
import { ContractOptions, Utxo } from './interfaces.js';
|
|
6
5
|
export declare class Contract {
|
|
7
6
|
private artifact;
|
|
8
|
-
private
|
|
7
|
+
private options?;
|
|
9
8
|
name: string;
|
|
10
9
|
address: string;
|
|
10
|
+
tokenAddress: string;
|
|
11
|
+
bytecode: string;
|
|
11
12
|
bytesize: number;
|
|
12
13
|
opcount: number;
|
|
13
14
|
functions: {
|
|
14
15
|
[name: string]: ContractFunction;
|
|
15
16
|
};
|
|
16
17
|
private redeemScript;
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
private provider;
|
|
19
|
+
private addressType;
|
|
20
|
+
constructor(artifact: Artifact, constructorArgs: Argument[], options?: ContractOptions | undefined);
|
|
21
|
+
getBalance(): Promise<bigint>;
|
|
19
22
|
getUtxos(): Promise<Utxo[]>;
|
|
20
|
-
getRedeemScriptHex(): string;
|
|
21
23
|
private createFunction;
|
|
22
24
|
}
|
|
23
25
|
export declare type ContractFunction = (...args: Argument[]) => Transaction;
|
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { binToHex } from '@bitauth/libauth';
|
|
11
2
|
import { asmToScript, calculateBytesize, countOpcodes, generateRedeemScript, scriptToBytecode, } from '@cashscript/utils';
|
|
12
3
|
import { Transaction } from './Transaction.js';
|
|
13
4
|
import { encodeArgument } from './Argument.js';
|
|
14
|
-
import { scriptToAddress
|
|
5
|
+
import { scriptToAddress } from './utils.js';
|
|
15
6
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
16
7
|
import { ElectrumNetworkProvider } from './network/index.js';
|
|
17
8
|
export class Contract {
|
|
18
|
-
constructor(artifact, constructorArgs,
|
|
9
|
+
constructor(artifact, constructorArgs, options) {
|
|
19
10
|
this.artifact = artifact;
|
|
20
|
-
this.
|
|
11
|
+
this.options = options;
|
|
12
|
+
const defaultProvider = new ElectrumNetworkProvider();
|
|
13
|
+
const defaultAddressType = 'p2sh32';
|
|
14
|
+
this.provider = this.options?.provider ?? defaultProvider;
|
|
15
|
+
this.addressType = this.options?.addressType ?? defaultAddressType;
|
|
21
16
|
const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName'];
|
|
22
17
|
if (!expectedProperties.every((property) => property in artifact)) {
|
|
23
18
|
throw new Error('Invalid or incomplete artifact provided');
|
|
@@ -47,23 +42,18 @@ export class Contract {
|
|
|
47
42
|
});
|
|
48
43
|
}
|
|
49
44
|
this.name = artifact.contractName;
|
|
50
|
-
this.address = scriptToAddress(this.redeemScript, this.provider.network);
|
|
45
|
+
this.address = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, false);
|
|
46
|
+
this.tokenAddress = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, true);
|
|
47
|
+
this.bytecode = binToHex(scriptToBytecode(this.redeemScript));
|
|
51
48
|
this.bytesize = calculateBytesize(this.redeemScript);
|
|
52
49
|
this.opcount = countOpcodes(this.redeemScript);
|
|
53
50
|
}
|
|
54
|
-
getBalance() {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return utxos.reduce((acc, utxo) => acc + utxo.satoshis, 0);
|
|
58
|
-
});
|
|
51
|
+
async getBalance() {
|
|
52
|
+
const utxos = await this.getUtxos();
|
|
53
|
+
return utxos.reduce((acc, utxo) => acc + utxo.satoshis, 0n);
|
|
59
54
|
}
|
|
60
|
-
getUtxos() {
|
|
61
|
-
return
|
|
62
|
-
return this.provider.getUtxos(this.address);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
getRedeemScriptHex() {
|
|
66
|
-
return binToHex(scriptToBytecode(this.redeemScript));
|
|
55
|
+
async getUtxos() {
|
|
56
|
+
return this.provider.getUtxos(this.address);
|
|
67
57
|
}
|
|
68
58
|
createFunction(abiFunction, selector) {
|
|
69
59
|
return (...args) => {
|
|
@@ -3,7 +3,10 @@ export declare class TypeError extends Error {
|
|
|
3
3
|
constructor(actual: string, expected: Type);
|
|
4
4
|
}
|
|
5
5
|
export declare class OutputSatoshisTooSmallError extends Error {
|
|
6
|
-
constructor(satoshis:
|
|
6
|
+
constructor(satoshis: bigint, minimumAmount: bigint);
|
|
7
|
+
}
|
|
8
|
+
export declare class TokensToNonTokenAddressError extends Error {
|
|
9
|
+
constructor(address: string);
|
|
7
10
|
}
|
|
8
11
|
export declare class FailedTransactionError extends Error {
|
|
9
12
|
reason: string;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { DUST_LIMIT } from './constants.js';
|
|
2
1
|
export class TypeError extends Error {
|
|
3
2
|
constructor(actual, expected) {
|
|
4
3
|
super(`Found type '${actual}' where type '${expected.toString()}' was expected`);
|
|
5
4
|
}
|
|
6
5
|
}
|
|
7
6
|
export class OutputSatoshisTooSmallError extends Error {
|
|
8
|
-
constructor(satoshis) {
|
|
9
|
-
super(`Tried to add an output with ${satoshis} satoshis, which is less than the
|
|
7
|
+
constructor(satoshis, minimumAmount) {
|
|
8
|
+
super(`Tried to add an output with ${satoshis} satoshis, which is less than the required minimum for this output-type (${minimumAmount})`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class TokensToNonTokenAddressError extends Error {
|
|
12
|
+
constructor(address) {
|
|
13
|
+
super(`Tried to send tokens to an address without token support, ${address}.`);
|
|
10
14
|
}
|
|
11
15
|
}
|
|
12
16
|
export class FailedTransactionError extends Error {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { Secp256k1 } from '@bitauth/libauth';
|
|
2
1
|
import { HashType, SignatureAlgorithm } from './interfaces.js';
|
|
3
2
|
export default class SignatureTemplate {
|
|
4
3
|
private hashtype;
|
|
5
4
|
private signatureAlgorithm;
|
|
6
5
|
private privateKey;
|
|
7
6
|
constructor(signer: Keypair | Uint8Array | string, hashtype?: HashType, signatureAlgorithm?: SignatureAlgorithm);
|
|
8
|
-
generateSignature(payload: Uint8Array,
|
|
7
|
+
generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array;
|
|
9
8
|
getHashType(bchForkId?: boolean): number;
|
|
10
|
-
getPublicKey(
|
|
9
|
+
getPublicKey(): Uint8Array;
|
|
11
10
|
}
|
|
12
11
|
interface Keypair {
|
|
13
12
|
toWIF(): string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { decodePrivateKeyWif, SigningSerializationFlag } from '@bitauth/libauth';
|
|
2
|
-
import { sha256 } from '@cashscript/utils';
|
|
1
|
+
import { decodePrivateKeyWif, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
|
|
3
2
|
import { HashType, SignatureAlgorithm } from './interfaces.js';
|
|
4
3
|
export default class SignatureTemplate {
|
|
5
4
|
constructor(signer, hashtype = HashType.SIGHASH_ALL, signatureAlgorithm = SignatureAlgorithm.SCHNORR) {
|
|
@@ -16,7 +15,7 @@ export default class SignatureTemplate {
|
|
|
16
15
|
this.privateKey = signer;
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
|
-
generateSignature(payload,
|
|
18
|
+
generateSignature(payload, bchForkId) {
|
|
20
19
|
const signature = this.signatureAlgorithm === SignatureAlgorithm.SCHNORR
|
|
21
20
|
? secp256k1.signMessageHashSchnorr(this.privateKey, payload)
|
|
22
21
|
: secp256k1.signMessageHashDER(this.privateKey, payload);
|
|
@@ -25,7 +24,7 @@ export default class SignatureTemplate {
|
|
|
25
24
|
getHashType(bchForkId = true) {
|
|
26
25
|
return bchForkId ? (this.hashtype | SigningSerializationFlag.forkId) : this.hashtype;
|
|
27
26
|
}
|
|
28
|
-
getPublicKey(
|
|
27
|
+
getPublicKey() {
|
|
29
28
|
return secp256k1.derivePublicKeyCompressed(this.privateKey);
|
|
30
29
|
}
|
|
31
30
|
}
|
|
@@ -33,7 +32,7 @@ function isKeypair(obj) {
|
|
|
33
32
|
return typeof obj.toWIF === 'function';
|
|
34
33
|
}
|
|
35
34
|
function decodeWif(wif) {
|
|
36
|
-
const result = decodePrivateKeyWif(
|
|
35
|
+
const result = decodePrivateKeyWif(wif);
|
|
37
36
|
if (typeof result === 'string') {
|
|
38
37
|
throw new Error(result);
|
|
39
38
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbiFunction, Script } from '@cashscript/utils';
|
|
2
|
-
import { Utxo, Recipient, TransactionDetails } from './interfaces.js';
|
|
2
|
+
import { Utxo, Recipient, TokenDetails, TransactionDetails } from './interfaces.js';
|
|
3
3
|
import NetworkProvider from './network/NetworkProvider.js';
|
|
4
4
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
5
5
|
export declare class Transaction {
|
|
@@ -13,23 +13,25 @@ export declare class Transaction {
|
|
|
13
13
|
private outputs;
|
|
14
14
|
private sequence;
|
|
15
15
|
private locktime;
|
|
16
|
-
private hardcodedFee;
|
|
17
16
|
private feePerByte;
|
|
17
|
+
private hardcodedFee;
|
|
18
18
|
private minChange;
|
|
19
|
+
private tokenChange;
|
|
19
20
|
constructor(address: string, provider: NetworkProvider, redeemScript: Script, abiFunction: AbiFunction, args: (Uint8Array | SignatureTemplate)[], selector?: number | undefined);
|
|
20
21
|
from(input: Utxo): this;
|
|
21
22
|
from(inputs: Utxo[]): this;
|
|
22
23
|
experimentalFromP2PKH(input: Utxo, template: SignatureTemplate): this;
|
|
23
24
|
experimentalFromP2PKH(inputs: Utxo[], template: SignatureTemplate): this;
|
|
24
|
-
to(to: string, amount:
|
|
25
|
+
to(to: string, amount: bigint, token?: TokenDetails): this;
|
|
25
26
|
to(outputs: Recipient[]): this;
|
|
26
27
|
withOpReturn(chunks: string[]): this;
|
|
27
28
|
withAge(age: number): this;
|
|
28
29
|
withTime(time: number): this;
|
|
29
|
-
withHardcodedFee(hardcodedFee:
|
|
30
|
+
withHardcodedFee(hardcodedFee: bigint): this;
|
|
30
31
|
withFeePerByte(feePerByte: number): this;
|
|
31
|
-
withMinChange(minChange:
|
|
32
|
+
withMinChange(minChange: bigint): this;
|
|
32
33
|
withoutChange(): this;
|
|
34
|
+
withoutTokenChange(): this;
|
|
33
35
|
build(): Promise<string>;
|
|
34
36
|
send(): Promise<TransactionDetails>;
|
|
35
37
|
send(raw: true): Promise<string>;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { hexToBin, binToHex, encodeTransaction, addressContentsToLockingBytecode, decodeTransaction, LockingBytecodeType, } from '@bitauth/libauth';
|
|
2
|
+
import delay from 'delay';
|
|
3
|
+
import { hash160, hash256, placeholder, scriptToBytecode, } from '@cashscript/utils';
|
|
4
|
+
import { isSignableUtxo, } from './interfaces.js';
|
|
5
|
+
import { meep, createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, createSighashPreimage, validateRecipient, utxoComparator, cashScriptOutputToLibauthOutput, calculateDust, getOutputSize, } from './utils.js';
|
|
6
|
+
import SignatureTemplate from './SignatureTemplate.js';
|
|
7
|
+
const bip68 = await import('bip68');
|
|
8
|
+
export class Transaction {
|
|
9
|
+
constructor(address, provider, redeemScript, abiFunction, args, selector) {
|
|
10
|
+
this.address = address;
|
|
11
|
+
this.provider = provider;
|
|
12
|
+
this.redeemScript = redeemScript;
|
|
13
|
+
this.abiFunction = abiFunction;
|
|
14
|
+
this.args = args;
|
|
15
|
+
this.selector = selector;
|
|
16
|
+
this.inputs = [];
|
|
17
|
+
this.outputs = [];
|
|
18
|
+
this.sequence = 0xfffffffe;
|
|
19
|
+
this.feePerByte = 1.0;
|
|
20
|
+
this.minChange = 0n;
|
|
21
|
+
this.tokenChange = true;
|
|
22
|
+
}
|
|
23
|
+
from(inputOrInputs) {
|
|
24
|
+
if (!Array.isArray(inputOrInputs)) {
|
|
25
|
+
inputOrInputs = [inputOrInputs];
|
|
26
|
+
}
|
|
27
|
+
this.inputs = this.inputs.concat(inputOrInputs);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
experimentalFromP2PKH(inputOrInputs, template) {
|
|
31
|
+
if (!Array.isArray(inputOrInputs)) {
|
|
32
|
+
inputOrInputs = [inputOrInputs];
|
|
33
|
+
}
|
|
34
|
+
inputOrInputs = inputOrInputs.map((input) => ({ ...input, template }));
|
|
35
|
+
this.inputs = this.inputs.concat(inputOrInputs);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
to(toOrOutputs, amount, token) {
|
|
39
|
+
if (typeof toOrOutputs === 'string' && typeof amount === 'bigint') {
|
|
40
|
+
const recipient = { to: toOrOutputs, amount, token };
|
|
41
|
+
return this.to([recipient]);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(toOrOutputs) && amount === undefined) {
|
|
44
|
+
toOrOutputs.forEach(validateRecipient);
|
|
45
|
+
this.outputs = this.outputs.concat(toOrOutputs);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
throw new Error('Incorrect arguments passed to function \'to\'');
|
|
49
|
+
}
|
|
50
|
+
withOpReturn(chunks) {
|
|
51
|
+
this.outputs.push(createOpReturnOutput(chunks));
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
withAge(age) {
|
|
55
|
+
this.sequence = bip68.encode({ blocks: age });
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
withTime(time) {
|
|
59
|
+
this.locktime = time;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
withHardcodedFee(hardcodedFee) {
|
|
63
|
+
this.hardcodedFee = hardcodedFee;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
withFeePerByte(feePerByte) {
|
|
67
|
+
this.feePerByte = feePerByte;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
withMinChange(minChange) {
|
|
71
|
+
this.minChange = minChange;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
withoutChange() {
|
|
75
|
+
return this.withMinChange(BigInt(Number.MAX_VALUE));
|
|
76
|
+
}
|
|
77
|
+
withoutTokenChange() {
|
|
78
|
+
this.tokenChange = false;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
async build() {
|
|
82
|
+
this.locktime = this.locktime ?? await this.provider.getBlockHeight();
|
|
83
|
+
await this.setInputsAndOutputs();
|
|
84
|
+
const bytecode = scriptToBytecode(this.redeemScript);
|
|
85
|
+
const inputs = this.inputs.map((utxo) => ({
|
|
86
|
+
outpointIndex: utxo.vout,
|
|
87
|
+
outpointTransactionHash: hexToBin(utxo.txid),
|
|
88
|
+
sequenceNumber: this.sequence,
|
|
89
|
+
unlockingBytecode: new Uint8Array(),
|
|
90
|
+
}));
|
|
91
|
+
const outputs = this.outputs.map(cashScriptOutputToLibauthOutput);
|
|
92
|
+
const transaction = {
|
|
93
|
+
inputs,
|
|
94
|
+
locktime: this.locktime,
|
|
95
|
+
outputs,
|
|
96
|
+
version: 2,
|
|
97
|
+
};
|
|
98
|
+
const inputScripts = [];
|
|
99
|
+
this.inputs.forEach((utxo, i) => {
|
|
100
|
+
// UTXO's with signature templates are signed using P2PKH
|
|
101
|
+
if (isSignableUtxo(utxo)) {
|
|
102
|
+
const pubkey = utxo.template.getPublicKey();
|
|
103
|
+
const pubkeyHash = hash160(pubkey);
|
|
104
|
+
const addressContents = { payload: pubkeyHash, type: LockingBytecodeType.p2pkh };
|
|
105
|
+
const prevOutScript = addressContentsToLockingBytecode(addressContents);
|
|
106
|
+
const hashtype = utxo.template.getHashType();
|
|
107
|
+
const preimage = createSighashPreimage(transaction, this.inputs, i, prevOutScript, hashtype);
|
|
108
|
+
const sighash = hash256(preimage);
|
|
109
|
+
const signature = utxo.template.generateSignature(sighash);
|
|
110
|
+
const inputScript = scriptToBytecode([signature, pubkey]);
|
|
111
|
+
inputScripts.push(inputScript);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
let covenantHashType = -1;
|
|
115
|
+
const completeArgs = this.args.map((arg) => {
|
|
116
|
+
if (!(arg instanceof SignatureTemplate))
|
|
117
|
+
return arg;
|
|
118
|
+
// First signature is used for sighash preimage (maybe not the best way)
|
|
119
|
+
if (covenantHashType < 0)
|
|
120
|
+
covenantHashType = arg.getHashType();
|
|
121
|
+
const preimage = createSighashPreimage(transaction, this.inputs, i, bytecode, arg.getHashType());
|
|
122
|
+
const sighash = hash256(preimage);
|
|
123
|
+
return arg.generateSignature(sighash);
|
|
124
|
+
});
|
|
125
|
+
const preimage = this.abiFunction.covenant
|
|
126
|
+
? createSighashPreimage(transaction, this.inputs, i, bytecode, covenantHashType)
|
|
127
|
+
: undefined;
|
|
128
|
+
const inputScript = createInputScript(this.redeemScript, completeArgs, this.selector, preimage);
|
|
129
|
+
inputScripts.push(inputScript);
|
|
130
|
+
});
|
|
131
|
+
inputScripts.forEach((script, i) => {
|
|
132
|
+
transaction.inputs[i].unlockingBytecode = script;
|
|
133
|
+
});
|
|
134
|
+
return binToHex(encodeTransaction(transaction));
|
|
135
|
+
}
|
|
136
|
+
async send(raw) {
|
|
137
|
+
const tx = await this.build();
|
|
138
|
+
try {
|
|
139
|
+
const txid = await this.provider.sendRawTransaction(tx);
|
|
140
|
+
return raw ? await this.getTxDetails(txid, raw) : await this.getTxDetails(txid);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
const reason = e.error ?? e.message;
|
|
144
|
+
throw buildError(reason, meep(tx, this.inputs, this.redeemScript));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async getTxDetails(txid, raw) {
|
|
148
|
+
for (let retries = 0; retries < 1200; retries += 1) {
|
|
149
|
+
await delay(500);
|
|
150
|
+
try {
|
|
151
|
+
const hex = await this.provider.getRawTransaction(txid);
|
|
152
|
+
if (raw)
|
|
153
|
+
return hex;
|
|
154
|
+
const libauthTransaction = decodeTransaction(hexToBin(hex));
|
|
155
|
+
return { ...libauthTransaction, txid, hex };
|
|
156
|
+
}
|
|
157
|
+
catch (ignored) {
|
|
158
|
+
// ignored
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Should not happen
|
|
162
|
+
throw new Error('Could not retrieve transaction details for over 10 minutes');
|
|
163
|
+
}
|
|
164
|
+
async meep() {
|
|
165
|
+
const tx = await this.build();
|
|
166
|
+
return meep(tx, this.inputs, this.redeemScript);
|
|
167
|
+
}
|
|
168
|
+
async setInputsAndOutputs() {
|
|
169
|
+
if (this.outputs.length === 0) {
|
|
170
|
+
throw Error('Attempted to build a transaction without outputs');
|
|
171
|
+
}
|
|
172
|
+
// Construct object with total output of fungible tokens by tokenId
|
|
173
|
+
const netBalanceTokens = {};
|
|
174
|
+
// Construct list with all nfts in inputs
|
|
175
|
+
const listNftsInputs = [];
|
|
176
|
+
// If inputs are manually selected, add their tokens to balance
|
|
177
|
+
for (const input of this.inputs) {
|
|
178
|
+
if (!input.token)
|
|
179
|
+
continue;
|
|
180
|
+
const tokenCategory = input.token.category;
|
|
181
|
+
if (!netBalanceTokens[tokenCategory]) {
|
|
182
|
+
netBalanceTokens[tokenCategory] = input.token.amount;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
netBalanceTokens[tokenCategory] += input.token.amount;
|
|
186
|
+
}
|
|
187
|
+
if (input.token.nft) {
|
|
188
|
+
listNftsInputs.push({ ...input.token.nft, category: input.token.category });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Construct list with all nfts in outputs
|
|
192
|
+
let listNftsOutputs = [];
|
|
193
|
+
// Subtract all token outputs from the token balances
|
|
194
|
+
for (const output of this.outputs) {
|
|
195
|
+
if (!output.token)
|
|
196
|
+
continue;
|
|
197
|
+
const tokenCategory = output.token.category;
|
|
198
|
+
if (!netBalanceTokens[tokenCategory]) {
|
|
199
|
+
netBalanceTokens[tokenCategory] = -output.token.amount;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
netBalanceTokens[tokenCategory] -= output.token.amount;
|
|
203
|
+
}
|
|
204
|
+
if (output.token.nft) {
|
|
205
|
+
listNftsOutputs.push({ ...output.token.nft, category: output.token.category });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// If inputs are manually provided, check token balances
|
|
209
|
+
if (this.inputs.length > 0) {
|
|
210
|
+
for (const [category, balance] of Object.entries(netBalanceTokens)) {
|
|
211
|
+
// Add token change outputs if applicable
|
|
212
|
+
if (this.tokenChange && balance > 0) {
|
|
213
|
+
const tokenDetails = {
|
|
214
|
+
category,
|
|
215
|
+
amount: balance,
|
|
216
|
+
};
|
|
217
|
+
const tokenChangeOutput = { to: this.address, amount: BigInt(1000), token: tokenDetails };
|
|
218
|
+
this.outputs.push(tokenChangeOutput);
|
|
219
|
+
}
|
|
220
|
+
// Throw error when token balance is insufficient
|
|
221
|
+
if (balance < 0) {
|
|
222
|
+
throw new Error(`Insufficient token balance for token with category ${category}.`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Compare nfts in- and outputs, check if inputs have nfts corresponding to outputs
|
|
226
|
+
// Keep list of nfts in inputs without matching output
|
|
227
|
+
// First check immutable nfts, then mutable & minting nfts together
|
|
228
|
+
// this is so the mutable nft in input does not get match to an output nft corresponding to an immutable nft in the inputs
|
|
229
|
+
let unusedNfts = listNftsInputs;
|
|
230
|
+
for (const nftInput of listNftsInputs) {
|
|
231
|
+
if (nftInput.capability === 'none') {
|
|
232
|
+
for (let i = 0; i < listNftsOutputs.length; i++) {
|
|
233
|
+
// Deep equality check token objects
|
|
234
|
+
if (JSON.stringify(listNftsOutputs[i]) === JSON.stringify(nftInput)) {
|
|
235
|
+
listNftsOutputs.splice(i, 1);
|
|
236
|
+
unusedNfts = unusedNfts.filter((nft) => nft !== nftInput);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const nftInput of listNftsInputs) {
|
|
243
|
+
if (nftInput.capability === 'minting') {
|
|
244
|
+
const newListNftsOutputs = listNftsOutputs.filter((nftOutput) => nftOutput.category !== nftInput.category);
|
|
245
|
+
if (newListNftsOutputs !== listNftsOutputs) {
|
|
246
|
+
unusedNfts = unusedNfts.filter((nft) => nft !== nftInput);
|
|
247
|
+
listNftsOutputs = newListNftsOutputs;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (nftInput.capability === 'mutable') {
|
|
251
|
+
for (let i = 0; i < listNftsOutputs.length; i++) {
|
|
252
|
+
if (listNftsOutputs[i].category === nftInput.category) {
|
|
253
|
+
listNftsOutputs.splice(i, 1);
|
|
254
|
+
unusedNfts = unusedNfts.filter((nft) => nft !== nftInput);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (listNftsOutputs.length !== 0) {
|
|
261
|
+
throw new Error('Nfts in outputs don\'t have corresponding nfts in inputs!');
|
|
262
|
+
}
|
|
263
|
+
if (this.tokenChange) {
|
|
264
|
+
for (const unusedNft of unusedNfts) {
|
|
265
|
+
const tokenDetails = {
|
|
266
|
+
category: unusedNft.category,
|
|
267
|
+
amount: BigInt(0),
|
|
268
|
+
nft: {
|
|
269
|
+
capability: unusedNft.capability,
|
|
270
|
+
commitment: unusedNft.commitment,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
const nftChangeOutput = { to: this.address, amount: BigInt(1000), token: tokenDetails };
|
|
274
|
+
this.outputs.push(nftChangeOutput);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Replace all SignatureTemplate with 65-length placeholder Uint8Arrays
|
|
279
|
+
const placeholderArgs = this.args.map((arg) => (arg instanceof SignatureTemplate ? placeholder(65) : arg));
|
|
280
|
+
// Create a placeholder preimage of the correct size
|
|
281
|
+
const placeholderPreimage = this.abiFunction.covenant
|
|
282
|
+
? placeholder(getPreimageSize(scriptToBytecode(this.redeemScript)))
|
|
283
|
+
: undefined;
|
|
284
|
+
// Create a placeholder input script for size calculation using the placeholder
|
|
285
|
+
// arguments and correctly sized placeholder preimage
|
|
286
|
+
const placeholderScript = createInputScript(this.redeemScript, placeholderArgs, this.selector, placeholderPreimage);
|
|
287
|
+
// Add one extra byte per input to over-estimate tx-in count
|
|
288
|
+
const inputSize = getInputSize(placeholderScript) + 1;
|
|
289
|
+
// Note that we use the addPrecision function to add "decimal points" to BigInt numbers
|
|
290
|
+
// Calculate amount to send and base fee (excluding additional fees per UTXO)
|
|
291
|
+
let amount = addPrecision(this.outputs.reduce((acc, output) => acc + output.amount, 0n));
|
|
292
|
+
let fee = addPrecision(this.hardcodedFee ?? getTxSizeWithoutInputs(this.outputs) * this.feePerByte);
|
|
293
|
+
// Select and gather UTXOs and calculate fees and available funds
|
|
294
|
+
let satsAvailable = 0n;
|
|
295
|
+
if (this.inputs.length > 0) {
|
|
296
|
+
// If inputs are already defined, the user provided the UTXOs and we perform no further UTXO selection
|
|
297
|
+
if (!this.hardcodedFee)
|
|
298
|
+
fee += addPrecision(this.inputs.length * inputSize * this.feePerByte);
|
|
299
|
+
satsAvailable = addPrecision(this.inputs.reduce((acc, input) => acc + input.satoshis, 0n));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
// If inputs are not defined yet, we retrieve the contract's UTXOs and perform selection
|
|
303
|
+
const utxos = await this.provider.getUtxos(this.address);
|
|
304
|
+
// We sort the UTXOs mainly so there is consistent behaviour between network providers
|
|
305
|
+
// even if they report UTXOs in a different order
|
|
306
|
+
utxos.sort(utxoComparator).reverse();
|
|
307
|
+
for (const utxo of utxos) {
|
|
308
|
+
this.inputs.push(utxo);
|
|
309
|
+
satsAvailable += addPrecision(utxo.satoshis);
|
|
310
|
+
if (!this.hardcodedFee)
|
|
311
|
+
fee += addPrecision(inputSize * this.feePerByte);
|
|
312
|
+
if (satsAvailable > amount + fee)
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Remove "decimal points" from BigInt numbers (rounding up for fee, down for others)
|
|
317
|
+
satsAvailable = removePrecisionFloor(satsAvailable);
|
|
318
|
+
amount = removePrecisionFloor(amount);
|
|
319
|
+
fee = removePrecisionCeil(fee);
|
|
320
|
+
// Calculate change and check available funds
|
|
321
|
+
let change = satsAvailable - amount - fee;
|
|
322
|
+
if (change < 0) {
|
|
323
|
+
throw new Error(`Insufficient funds: available (${satsAvailable}) < needed (${amount + fee}).`);
|
|
324
|
+
}
|
|
325
|
+
// Account for the fee of adding a change output
|
|
326
|
+
if (!this.hardcodedFee) {
|
|
327
|
+
const changeOutputSize = getOutputSize({ to: this.address, amount: 0n });
|
|
328
|
+
change -= BigInt(changeOutputSize * this.feePerByte);
|
|
329
|
+
}
|
|
330
|
+
// Add a change output if applicable
|
|
331
|
+
const changeOutput = { to: this.address, amount: change };
|
|
332
|
+
if (change >= this.minChange && change >= calculateDust(changeOutput)) {
|
|
333
|
+
this.outputs.push(changeOutput);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Note: the below is a very simple implementation of a "decimal point" system for BigInt numbers
|
|
338
|
+
// It is safe to use for UTXO fee calculations due to its low numbers, but should not be used for other purposes
|
|
339
|
+
// Also note that multiplication and division between two "decimal" bigints is not supported
|
|
340
|
+
// High precision may not work with some 'number' inputs, so we set the default to 6 "decimal places"
|
|
341
|
+
const addPrecision = (amount, precision = 6) => {
|
|
342
|
+
if (typeof amount === 'number') {
|
|
343
|
+
return BigInt(Math.ceil(amount * 10 ** precision));
|
|
344
|
+
}
|
|
345
|
+
return amount * BigInt(10 ** precision);
|
|
346
|
+
};
|
|
347
|
+
const removePrecisionFloor = (amount, precision = 6) => (amount / (10n ** BigInt(precision)));
|
|
348
|
+
const removePrecisionCeil = (amount, precision = 6) => {
|
|
349
|
+
const multiplier = 10n ** BigInt(precision);
|
|
350
|
+
return (amount + multiplier - 1n) / multiplier;
|
|
351
|
+
};
|
|
352
|
+
//# sourceMappingURL=Transaction.js.map
|
|
@@ -7,4 +7,4 @@ export { Artifact, AbiFunction, AbiInput } from '@cashscript/utils';
|
|
|
7
7
|
export * as utils from '@cashscript/utils';
|
|
8
8
|
export { Utxo, Recipient, SignatureAlgorithm, HashType, Network, } from './interfaces.js';
|
|
9
9
|
export * from './Errors.js';
|
|
10
|
-
export { NetworkProvider,
|
|
10
|
+
export { NetworkProvider, BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
|
|
@@ -2,9 +2,8 @@ import SignatureTemplate from './SignatureTemplate.js';
|
|
|
2
2
|
export { SignatureTemplate };
|
|
3
3
|
export { Contract } from './Contract.js';
|
|
4
4
|
export { Transaction } from './Transaction.js';
|
|
5
|
-
|
|
6
|
-
export { utils_1 as utils };
|
|
5
|
+
export * as utils from '@cashscript/utils';
|
|
7
6
|
export { SignatureAlgorithm, HashType, Network, } from './interfaces.js';
|
|
8
7
|
export * from './Errors.js';
|
|
9
|
-
export {
|
|
8
|
+
export { BitcoinRpcNetworkProvider, ElectrumNetworkProvider, FullStackNetworkProvider, } from './network/index.js';
|
|
10
9
|
//# sourceMappingURL=index.js.map
|