Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cashscript

Package Overview
Dependencies
Maintainers
1
Versions
64
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cashscript - npm Package Compare versions

Comparing version 0.8.0-next.2 to 0.8.0-next.3

6

dist/interfaces.d.ts

@@ -32,2 +32,7 @@ import type { Transaction } from '@bitauth/libauth';

}
export interface NftObject {
category: string;
capability: 'none' | 'mutable' | 'minting';
commitment: string;
}
export interface LibauthOutput {

@@ -54,2 +59,3 @@ lockingBytecode: Uint8Array;

SIGHASH_SINGLE = 3,
SIGHASH_UTXOS = 32,
SIGHASH_ANYONECANPAY = 128

@@ -56,0 +62,0 @@ }

1

dist/interfaces.js

@@ -14,2 +14,3 @@ export function isSignableUtxo(utxo) {

HashType[HashType["SIGHASH_SINGLE"] = 3] = "SIGHASH_SINGLE";
HashType[HashType["SIGHASH_UTXOS"] = 32] = "SIGHASH_UTXOS";
HashType[HashType["SIGHASH_ANYONECANPAY"] = 128] = "SIGHASH_ANYONECANPAY";

@@ -16,0 +17,0 @@ })(HashType || (HashType = {}));

2

dist/SignatureTemplate.js
import { decodePrivateKeyWif, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
import { HashType, SignatureAlgorithm } from './interfaces.js';
export default class SignatureTemplate {
constructor(signer, hashtype = HashType.SIGHASH_ALL, signatureAlgorithm = SignatureAlgorithm.SCHNORR) {
constructor(signer, hashtype = HashType.SIGHASH_ALL | HashType.SIGHASH_UTXOS, signatureAlgorithm = SignatureAlgorithm.SCHNORR) {
this.hashtype = hashtype;

@@ -6,0 +6,0 @@ this.signatureAlgorithm = signatureAlgorithm;

@@ -23,4 +23,4 @@ import { AbiFunction, Script } from '@cashscript/utils';

from(inputs: Utxo[]): this;
experimentalFromP2PKH(input: Utxo, template: SignatureTemplate): this;
experimentalFromP2PKH(inputs: Utxo[], template: SignatureTemplate): this;
fromP2PKH(input: Utxo, template: SignatureTemplate): this;
fromP2PKH(inputs: Utxo[], template: SignatureTemplate): this;
to(to: string, amount: bigint, token?: TokenDetails): this;

@@ -27,0 +27,0 @@ to(outputs: Recipient[]): this;

@@ -1,6 +0,6 @@

import { hexToBin, binToHex, encodeTransaction, addressContentsToLockingBytecode, decodeTransaction, LockingBytecodeType, } from '@bitauth/libauth';
import { hexToBin, binToHex, encodeTransaction, decodeTransaction, } from '@bitauth/libauth';
import delay from 'delay';
import { hash160, hash256, placeholder, scriptToBytecode, } from '@cashscript/utils';
import { hash256, placeholder, scriptToBytecode, } from '@cashscript/utils';
import { isSignableUtxo, } from './interfaces.js';
import { meep, createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, createSighashPreimage, validateRecipient, utxoComparator, cashScriptOutputToLibauthOutput, calculateDust, getOutputSize, } from './utils.js';
import { meep, createInputScript, getInputSize, createOpReturnOutput, getTxSizeWithoutInputs, getPreimageSize, buildError, createSighashPreimage, validateRecipient, utxoComparator, cashScriptOutputToLibauthOutput, calculateDust, getOutputSize, addressToLockScript, publicKeyToP2PKHLockingBytecode, utxoTokenComparator, } from './utils.js';
import SignatureTemplate from './SignatureTemplate.js';

@@ -30,3 +30,3 @@ const bip68 = await import('bip68');

}
experimentalFromP2PKH(inputOrInputs, template) {
fromP2PKH(inputOrInputs, template) {
if (!Array.isArray(inputOrInputs)) {

@@ -86,2 +86,3 @@ inputOrInputs = [inputOrInputs];

const bytecode = scriptToBytecode(this.redeemScript);
const lockingBytecode = addressToLockScript(this.address);
const inputs = this.inputs.map((utxo) => ({

@@ -93,2 +94,11 @@ outpointIndex: utxo.vout,

}));
// Generate source outputs from inputs (for signing with SIGHASH_UTXOS)
const sourceOutputs = this.inputs.map((input) => {
const sourceOutput = {
amount: input.satoshis,
to: isSignableUtxo(input) ? publicKeyToP2PKHLockingBytecode(input.template.getPublicKey()) : lockingBytecode,
token: input.token,
};
return cashScriptOutputToLibauthOutput(sourceOutput);
});
const outputs = this.outputs.map(cashScriptOutputToLibauthOutput);

@@ -106,7 +116,5 @@ const transaction = {

const pubkey = utxo.template.getPublicKey();
const pubkeyHash = hash160(pubkey);
const addressContents = { payload: pubkeyHash, type: LockingBytecodeType.p2pkh };
const prevOutScript = addressContentsToLockingBytecode(addressContents);
const prevOutScript = publicKeyToP2PKHLockingBytecode(pubkey);
const hashtype = utxo.template.getHashType();
const preimage = createSighashPreimage(transaction, this.inputs, i, prevOutScript, hashtype);
const preimage = createSighashPreimage(transaction, sourceOutputs, i, prevOutScript, hashtype);
const sighash = hash256(preimage);

@@ -125,3 +133,3 @@ const signature = utxo.template.generateSignature(sighash);

covenantHashType = arg.getHashType();
const preimage = createSighashPreimage(transaction, this.inputs, i, bytecode, arg.getHashType());
const preimage = createSighashPreimage(transaction, sourceOutputs, i, bytecode, arg.getHashType());
const sighash = hash256(preimage);

@@ -131,3 +139,3 @@ return arg.generateSignature(sighash);

const preimage = this.abiFunction.covenant
? createSighashPreimage(transaction, this.inputs, i, bytecode, covenantHashType)
? createSighashPreimage(transaction, sourceOutputs, i, bytecode, covenantHashType)
: undefined;

@@ -178,63 +186,44 @@ const inputScript = createInputScript(this.redeemScript, completeArgs, this.selector, preimage);

}
// Construct object with total output of fungible tokens by tokenId
const netBalanceTokens = {};
const allUtxos = await this.provider.getUtxos(this.address);
const manualTokenInputs = this.inputs.filter((input) => input.token);
// This will throw if the amount is not enough
if (manualTokenInputs.length > 0) {
selectAllTokenUtxos(manualTokenInputs, this.outputs);
}
const automaticTokenInputs = selectAllTokenUtxos(allUtxos, this.outputs);
const tokenInputs = manualTokenInputs.length > 0 ? manualTokenInputs : automaticTokenInputs;
if (this.tokenChange) {
const tokenChangeOutputs = createTokenChangeOutputs(tokenInputs, this.outputs, this.address);
this.outputs.push(...tokenChangeOutputs);
}
// Construct list with all nfts in inputs
const listNftsInputs = [];
// If inputs are manually selected, add their tokens to balance
for (const input of this.inputs) {
this.inputs.forEach((input) => {
if (!input.token)
continue;
const tokenCategory = input.token.category;
if (!netBalanceTokens[tokenCategory]) {
netBalanceTokens[tokenCategory] = input.token.amount;
}
else {
netBalanceTokens[tokenCategory] += input.token.amount;
}
return;
if (input.token.nft) {
listNftsInputs.push({ ...input.token.nft, category: input.token.category });
}
}
});
// Construct list with all nfts in outputs
let listNftsOutputs = [];
// Subtract all token outputs from the token balances
for (const output of this.outputs) {
this.outputs.forEach((output) => {
if (!output.token)
continue;
const tokenCategory = output.token.category;
if (!netBalanceTokens[tokenCategory]) {
netBalanceTokens[tokenCategory] = -output.token.amount;
}
else {
netBalanceTokens[tokenCategory] -= output.token.amount;
}
return;
if (output.token.nft) {
listNftsOutputs.push({ ...output.token.nft, category: output.token.category });
}
}
});
// If inputs are manually provided, check token balances
if (this.inputs.length > 0) {
for (const [category, balance] of Object.entries(netBalanceTokens)) {
// Add token change outputs if applicable
if (this.tokenChange && balance > 0) {
const tokenDetails = {
category,
amount: balance,
};
const tokenChangeOutput = { to: this.address, amount: BigInt(1000), token: tokenDetails };
this.outputs.push(tokenChangeOutput);
}
// Throw error when token balance is insufficient
if (balance < 0) {
throw new Error(`Insufficient token balance for token with category ${category}.`);
}
}
// Compare nfts in- and outputs, check if inputs have nfts corresponding to outputs
// Keep list of nfts in inputs without matching output
// First check immutable nfts, then mutable & minting nfts together
// this is so the mutable nft in input does not get match to an output nft corresponding to an immutable nft in the inputs
// This is so an immutible input gets matched first and is removed from the list of unused nfts
let unusedNfts = listNftsInputs;
for (const nftInput of listNftsInputs) {
if (nftInput.capability === 'none') {
for (let i = 0; i < listNftsOutputs.length; i++) {
for (let i = 0; i < listNftsOutputs.length; i += 1) {
// Deep equality check token objects

@@ -251,2 +240,3 @@ if (JSON.stringify(listNftsOutputs[i]) === JSON.stringify(nftInput)) {

if (nftInput.capability === 'minting') {
// eslint-disable-next-line max-len
const newListNftsOutputs = listNftsOutputs.filter((nftOutput) => nftOutput.category !== nftInput.category);

@@ -259,3 +249,3 @@ if (newListNftsOutputs !== listNftsOutputs) {

if (nftInput.capability === 'mutable') {
for (let i = 0; i < listNftsOutputs.length; i++) {
for (let i = 0; i < listNftsOutputs.length; i += 1) {
if (listNftsOutputs[i].category === nftInput.category) {

@@ -269,4 +259,10 @@ listNftsOutputs.splice(i, 1);

}
for (const nftOutput of listNftsOutputs) {
const genesisUtxo = getTokenGenesisUtxo(this.inputs, nftOutput.category);
if (genesisUtxo) {
listNftsOutputs = listNftsOutputs.filter((nft) => nft !== nftOutput);
}
}
if (listNftsOutputs.length !== 0) {
throw new Error('Nfts in outputs don\'t have corresponding nfts in inputs!');
throw new Error(`NFT output with token category ${listNftsOutputs[0].category} does not have corresponding input`);
}

@@ -313,7 +309,8 @@ if (this.tokenChange) {

// If inputs are not defined yet, we retrieve the contract's UTXOs and perform selection
const utxos = await this.provider.getUtxos(this.address);
const bchUtxos = allUtxos.filter((utxo) => !utxo.token);
// We sort the UTXOs mainly so there is consistent behaviour between network providers
// even if they report UTXOs in a different order
utxos.sort(utxoComparator).reverse();
for (const utxo of utxos) {
bchUtxos.sort(utxoComparator).reverse();
// Add all automatically added token inputs to the transaction
for (const utxo of automaticTokenInputs) {
this.inputs.push(utxo);

@@ -323,4 +320,10 @@ satsAvailable += addPrecision(utxo.satoshis);

fee += addPrecision(inputSize * this.feePerByte);
}
for (const utxo of bchUtxos) {
if (satsAvailable > amount + fee)
break;
this.inputs.push(utxo);
satsAvailable += addPrecision(utxo.satoshis);
if (!this.hardcodedFee)
fee += addPrecision(inputSize * this.feePerByte);
}

@@ -349,2 +352,48 @@ }

}
const getTokenGenesisUtxo = (utxos, tokenCategory) => {
const creationUtxo = utxos.find((utxo) => utxo.vout === 0 && utxo.txid === tokenCategory);
return creationUtxo;
};
const getTokenCategories = (outputs) => (outputs
.filter((output) => output.token)
.map((output) => output.token.category));
const calculateTotalTokenAmount = (outputs, tokenCategory) => (outputs
.filter((output) => output.token?.category === tokenCategory)
.reduce((acc, output) => acc + output.token.amount, 0n));
const selectTokenUtxos = (utxos, amountNeeded, tokenCategory) => {
const genesisUtxo = getTokenGenesisUtxo(utxos, tokenCategory);
if (genesisUtxo) {
return [genesisUtxo];
}
const tokenUtxos = utxos.filter((utxo) => utxo.token?.category === tokenCategory);
// We sort the UTXOs mainly so there is consistent behaviour between network providers
// even if they report UTXOs in a different order
tokenUtxos.sort(utxoTokenComparator).reverse();
let amountAvailable = 0n;
const selectedUtxos = [];
// Add token UTXOs until we have enough to cover the amount needed (no fee calculation because it's a token)
for (const utxo of tokenUtxos) {
selectedUtxos.push(utxo);
amountAvailable += utxo.token.amount;
if (amountAvailable >= amountNeeded)
break;
}
if (amountAvailable < amountNeeded) {
throw new Error(`Insufficient funds for token ${tokenCategory}: available (${amountAvailable}) < needed (${amountNeeded}).`);
}
return selectedUtxos;
};
const selectAllTokenUtxos = (utxos, outputs) => {
const tokenCategories = getTokenCategories(outputs);
return tokenCategories.flatMap((tokenCategory) => selectTokenUtxos(utxos, calculateTotalTokenAmount(outputs, tokenCategory), tokenCategory));
};
const createTokenChangeOutputs = (utxos, outputs, address) => {
const tokenCategories = getTokenCategories(utxos);
return tokenCategories.map((tokenCategory) => {
const required = calculateTotalTokenAmount(outputs, tokenCategory);
const available = calculateTotalTokenAmount(utxos, tokenCategory);
const change = available - required;
return { to: address, amount: BigInt(1000), token: { category: tokenCategory, amount: change } };
});
};
// Note: the below is a very simple implementation of a "decimal point" system for BigInt numbers

@@ -351,0 +400,0 @@ // It is safe to use for UTXO fee calculations due to its low numbers, but should not be used for other purposes

@@ -16,3 +16,3 @@ import { Transaction } from '@bitauth/libauth';

export declare function createOpReturnOutput(opReturnData: string[]): Output;
export declare function createSighashPreimage(transaction: Transaction, inputs: Utxo[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
export declare function createSighashPreimage(transaction: Transaction, sourceOutputs: LibauthOutput[], inputIndex: number, coveredBytecode: Uint8Array, hashtype: number): Uint8Array;
export declare function buildError(reason: string, meepStr: string): FailedTransactionError;

@@ -22,3 +22,5 @@ export declare function meep(tx: any, utxos: Utxo[], script: Script): string;

export declare function scriptToLockingBytecode(script: Script, addressType: 'p2sh20' | 'p2sh32'): Uint8Array;
export declare function publicKeyToP2PKHLockingBytecode(publicKey: Uint8Array): Uint8Array;
export declare function utxoComparator(a: Utxo, b: Utxo): number;
export declare function utxoTokenComparator(a: Utxo, b: Utxo): number;
/**

@@ -25,0 +27,0 @@ * Helper function to convert an address to a locking script

@@ -122,11 +122,3 @@ import { cashAddressToLockingBytecode, decodeCashAddress, addressContentsToLockingBytecode, lockingBytecodeToCashAddress, binToHex, generateSigningSerializationBCH, utf8ToBin, hexToBin, flattenBinArray, LockingBytecodeType, encodeTransactionOutput, } from '@bitauth/libauth';

}
export function createSighashPreimage(transaction, inputs, inputIndex, coveredBytecode, hashtype) {
const sourceOutputs = inputs.map((input) => {
const sourceOutput = {
amount: input.satoshis,
to: Uint8Array.of(),
token: input.token,
};
return cashScriptOutputToLibauthOutput(sourceOutput);
});
export function createSighashPreimage(transaction, sourceOutputs, inputIndex, coveredBytecode, hashtype) {
const context = { inputIndex, sourceOutputs, transaction };

@@ -179,2 +171,8 @@ const signingSerializationType = new Uint8Array([hashtype]);

}
export function publicKeyToP2PKHLockingBytecode(publicKey) {
const pubkeyHash = hash160(publicKey);
const addressContents = { payload: pubkeyHash, type: LockingBytecodeType.p2pkh };
const lockingBytecode = addressContentsToLockingBytecode(addressContents);
return lockingBytecode;
}
export function utxoComparator(a, b) {

@@ -187,2 +185,13 @@ if (a.satoshis > b.satoshis)

}
export function utxoTokenComparator(a, b) {
if (!a.token || !b.token)
throw new Error('UTXO does not have token data');
if (!a.token.category !== !b.token.category)
throw new Error('UTXO token categories do not match');
if (a.token.amount > b.token.amount)
return 1;
if (a.token.amount < b.token.amount)
return -1;
return 0;
}
/**

@@ -189,0 +198,0 @@ * Helper function to convert an address to a locking script

{
"name": "cashscript",
"version": "0.8.0-next.2",
"version": "0.8.0-next.3",
"description": "Easily write and interact with Bitcoin Cash contracts",

@@ -41,2 +41,3 @@ "keywords": [

"prepare": "yarn build",
"prepublishOnly": "yarn test && yarn lint",
"pretest": "yarn build:test",

@@ -47,3 +48,3 @@ "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest"

"@bitauth/libauth": "^2.0.0-alpha.8",
"@cashscript/utils": "^0.8.0-next.2",
"@cashscript/utils": "^0.8.0-next.3",
"bip68": "^1.0.4",

@@ -62,3 +63,3 @@ "bitcoin-rpc-promise-retry": "^1.3.0",

},
"gitHead": "37bcb8e924fc56ef8da2cb2a2640eb5478de39b2"
"gitHead": "398ff2afab6d731f077002e6be021ed2f1996b4a"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc