PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/@stacks/transactions/src

Просмотр файла: builders.ts

import { Buffer, IntegerType, intToBigInt } from '@stacks/common';
import { StacksTransaction } from './transaction';

import { StacksNetwork, StacksMainnet, StacksTestnet } from '@stacks/network';

import {
  createTokenTransferPayload,
  createSmartContractPayload,
  createContractCallPayload,
} from './payload';

import {
  StandardAuthorization,
  SponsoredAuthorization,
  createSingleSigSpendingCondition,
  createMultiSigSpendingCondition,
} from './authorization';

import {
  publicKeyToString,
  createStacksPrivateKey,
  getPublicKey,
  publicKeyToAddress,
  pubKeyfromPrivKey,
  publicKeyFromBuffer,
} from './keys';

import { TransactionSigner } from './signer';

import {
  PostCondition,
  STXPostCondition,
  FungiblePostCondition,
  NonFungiblePostCondition,
  createSTXPostCondition,
  createFungiblePostCondition,
  createNonFungiblePostCondition,
} from './postcondition';

import {
  AddressHashMode,
  AddressVersion,
  FungibleConditionCode,
  NonFungibleConditionCode,
  PostConditionMode,
  PayloadType,
  AnchorMode,
  TransactionVersion,
  TxRejectedReason,
  SingleSigHashMode,
} from './constants';

import { AssetInfo, createLPList, createStandardPrincipal, createContractPrincipal } from './types';

import { cvToHex, parseReadOnlyResponse, omit, validateTxId } from './utils';

import { fetchPrivate } from '@stacks/common';

import { ClarityValue, PrincipalCV } from './clarity';
import { validateContractCall, ClarityAbi } from './contract-abi';
import { c32address } from 'c32check';

/**
 * Lookup the nonce for an address from a core node
 *
 * @param {string} address - the c32check address to look up
 * @param {StacksNetwork} network - the Stacks network to look up address on
 *
 * @return a promise that resolves to an integer
 */
export async function getNonce(address: string, network?: StacksNetwork): Promise<bigint> {
  const defaultNetwork = new StacksMainnet();
  const url = network
    ? network.getAccountApiUrl(address)
    : defaultNetwork.getAccountApiUrl(address);
  const response = await fetchPrivate(url);
  if (!response.ok) {
    let msg = '';
    try {
      msg = await response.text();
    } catch (error) {}
    throw new Error(
      `Error fetching nonce. Response ${response.status}: ${response.statusText}. Attempted to fetch ${url} and failed with the message: "${msg}"`
    );
  }
  const responseText = await response.text();
  const result = JSON.parse(responseText) as { nonce: string };
  return BigInt(result.nonce);
}

/**
 * Estimate the total transaction fee in microstacks for a token transfer
 *
 * @param {StacksTransaction} transaction - the token transfer transaction to estimate fees for
 * @param {StacksNetwork} network - the Stacks network to estimate transaction for
 *
 * @return a promise that resolves to number of microstacks per byte
 */
export async function estimateTransfer(
  transaction: StacksTransaction,
  network?: StacksNetwork
): Promise<bigint> {
  if (transaction.payload.payloadType !== PayloadType.TokenTransfer) {
    throw new Error(
      `Transaction fee estimation only possible with ${
        PayloadType[PayloadType.TokenTransfer]
      } transactions. Invoked with: ${PayloadType[transaction.payload.payloadType]}`
    );
  }

  const requestHeaders = {
    Accept: 'application/text',
  };

  const fetchOptions = {
    method: 'GET',
    headers: requestHeaders,
  };

  const defaultNetwork = new StacksMainnet();
  const url = network
    ? network.getTransferFeeEstimateApiUrl()
    : defaultNetwork.getTransferFeeEstimateApiUrl();
  const response = await fetchPrivate(url, fetchOptions);
  if (!response.ok) {
    let msg = '';
    try {
      msg = await response.text();
    } catch (error) {}
    throw new Error(
      `Error estimating transaction fee. Response ${response.status}: ${response.statusText}. Attempted to fetch ${url} and failed with the message: "${msg}"`
    );
  }
  const feeRateResult = await response.text();
  const txBytes = BigInt(transaction.serialize().byteLength);
  const feeRate = BigInt(feeRateResult);
  return feeRate * txBytes;
}

export type SerializationRejection = {
  error: string;
  reason: TxRejectedReason.Serialization;
  reason_data: {
    message: string;
  };
  txid: string;
};

export type DeserializationRejection = {
  error: string;
  reason: TxRejectedReason.Deserialization;
  reason_data: {
    message: string;
  };
  txid: string;
};

export type SignatureValidationRejection = {
  error: string;
  reason: TxRejectedReason.SignatureValidation;
  reason_data: {
    message: string;
  };
  txid: string;
};

export type BadNonceRejection = {
  error: string;
  reason: TxRejectedReason.BadNonce;
  reason_data: {
    expected: number;
    actual: number;
    is_origin: boolean;
    principal: boolean;
  };
  txid: string;
};

export type FeeTooLowRejection = {
  error: string;
  reason: TxRejectedReason.FeeTooLow;
  reason_data: {
    expected: number;
    actual: number;
  };
  txid: string;
};

export type NotEnoughFundsRejection = {
  error: string;
  reason: TxRejectedReason.NotEnoughFunds;
  reason_data: {
    expected: string;
    actual: string;
  };
  txid: string;
};

export type NoSuchContractRejection = {
  error: string;
  reason: TxRejectedReason.NoSuchContract;
  reason_data?: undefined;
  txid: string;
};

export type NoSuchPublicFunctionRejection = {
  error: string;
  reason: TxRejectedReason.NoSuchPublicFunction;
  reason_data?: undefined;
  txid: string;
};

export type BadFunctionArgumentRejection = {
  error: string;
  reason: TxRejectedReason.BadFunctionArgument;
  reason_data: {
    message: string;
  };
  txid: string;
};

export type ContractAlreadyExistsRejection = {
  error: string;
  reason: TxRejectedReason.ContractAlreadyExists;
  reason_data: {
    contract_identifier: string;
  };
  txid: string;
};

export type PoisonMicroblocksDoNotConflictRejection = {
  error: string;
  reason: TxRejectedReason.PoisonMicroblocksDoNotConflict;
  reason_data?: undefined;
  txid: string;
};

export type PoisonMicroblockHasUnknownPubKeyHashRejection = {
  error: string;
  reason: TxRejectedReason.PoisonMicroblockHasUnknownPubKeyHash;
  reason_data?: undefined;
  txid: string;
};

export type PoisonMicroblockIsInvalidRejection = {
  error: string;
  reason: TxRejectedReason.PoisonMicroblockIsInvalid;
  reason_data?: undefined;
  txid: string;
};

export type BadAddressVersionByteRejection = {
  error: string;
  reason: TxRejectedReason.BadAddressVersionByte;
  reason_data?: undefined;
  txid: string;
};

export type NoCoinbaseViaMempoolRejection = {
  error: string;
  reason: TxRejectedReason.NoCoinbaseViaMempool;
  reason_data?: undefined;
  txid: string;
};

export type ServerFailureNoSuchChainTipRejection = {
  error: string;
  reason: TxRejectedReason.ServerFailureNoSuchChainTip;
  reason_data?: undefined;
  txid: string;
};

export type ServerFailureDatabaseRejection = {
  error: string;
  reason: TxRejectedReason.ServerFailureDatabase;
  reason_data: {
    message: string;
  };
  txid: string;
};

export type ServerFailureOtherRejection = {
  error: string;
  reason: TxRejectedReason.ServerFailureOther;
  reason_data: {
    message: string;
  };
  txid: string;
};

export type TxBroadcastResultOk = {
  txid: string;
  error?: undefined;
  reason?: undefined;
  reason_data?: undefined;
};

export type TxBroadcastResultRejected =
  | SerializationRejection
  | DeserializationRejection
  | SignatureValidationRejection
  | BadNonceRejection
  | FeeTooLowRejection
  | NotEnoughFundsRejection
  | NoSuchContractRejection
  | NoSuchPublicFunctionRejection
  | BadFunctionArgumentRejection
  | ContractAlreadyExistsRejection
  | PoisonMicroblocksDoNotConflictRejection
  | PoisonMicroblockHasUnknownPubKeyHashRejection
  | PoisonMicroblockIsInvalidRejection
  | BadAddressVersionByteRejection
  | NoCoinbaseViaMempoolRejection
  | ServerFailureNoSuchChainTipRejection
  | ServerFailureDatabaseRejection
  | ServerFailureOtherRejection;

export type TxBroadcastResult = TxBroadcastResultOk | TxBroadcastResultRejected;

/**
 * Broadcast the signed transaction to a core node
 *
 * @param {StacksTransaction} transaction - the token transfer transaction to broadcast
 * @param {StacksNetwork} network - the Stacks network to broadcast transaction to
 *
 * @returns {Promise} that resolves to a response if the operation succeeds
 */
export async function broadcastTransaction(
  transaction: StacksTransaction,
  network: StacksNetwork,
  attachment?: Buffer
): Promise<TxBroadcastResult> {
  const rawTx = transaction.serialize();
  const url = network.getBroadcastApiUrl();

  return broadcastRawTransaction(rawTx, url, attachment);
}

/**
 * Broadcast the signed transaction to a core node
 *
 * @param {Buffer} rawTx - the raw serialized transaction buffer to broadcast
 * @param {string} url - the broadcast endpoint URL
 *
 * @returns {Promise} that resolves to a response if the operation succeeds
 */
export async function broadcastRawTransaction(
  rawTx: Buffer,
  url: string,
  attachment?: Buffer
): Promise<TxBroadcastResult> {
  const options = {
    method: 'POST',
    headers: { 'Content-Type': attachment ? 'application/json' : 'application/octet-stream' },
    body: attachment
      ? JSON.stringify({
          tx: rawTx.toString('hex'),
          attachment: attachment.toString('hex'),
        })
      : rawTx,
  };

  const response = await fetchPrivate(url, options);
  if (!response.ok) {
    try {
      return (await response.json()) as TxBroadcastResult;
    } catch (e) {
      throw Error(`Failed to broadcast transaction: ${(e as Error).message}`);
    }
  }

  const text = await response.text();
  // Replace extra quotes around txid string
  const txid = text.replace(/["]+/g, '');
  const isValidTxId = validateTxId(txid);
  if (isValidTxId) {
    return {
      txid: txid,
    } as TxBroadcastResult;
  } else {
    throw new Error(text);
  }
}

/**
 * Fetch a contract's ABI
 *
 * @param {string} address - the contracts address
 * @param {string} contractName - the contracts name
 * @param {StacksNetwork} network - the Stacks network to broadcast transaction to
 *
 * @returns {Promise} that resolves to a ClarityAbi if the operation succeeds
 */
export async function getAbi(
  address: string,
  contractName: string,
  network: StacksNetwork
): Promise<ClarityAbi> {
  const options = {
    method: 'GET',
  };

  const url = network.getAbiApiUrl(address, contractName);

  const response = await fetchPrivate(url, options);
  if (!response.ok) {
    let msg = '';
    try {
      msg = await response.text();
    } catch (error) {}
    throw new Error(
      `Error fetching contract ABI for contract "${contractName}" at address ${address}. Response ${response.status}: ${response.statusText}. Attempted to fetch ${url} and failed with the message: "${msg}"`
    );
  }

  return JSON.parse(await response.text()) as ClarityAbi;
}

export interface MultiSigOptions {
  numSignatures: number;
  publicKeys: string[];
  signerKeys?: string[];
}

/**
 * STX token transfer transaction options
 */
export interface TokenTransferOptions {
  /** the address of the recipient of the token transfer */
  recipient: string | PrincipalCV;
  /** the amount to be transfered in microstacks */
  amount: IntegerType;
  /** the transaction fee in microstacks */
  fee?: IntegerType;
  /** the transaction nonce, which must be increased monotonically with each new transaction */
  nonce?: IntegerType;
  /** the network that the transaction will ultimately be broadcast to */
  network?: StacksNetwork;
  /** the transaction anchorMode, which specifies whether it should be
   * included in an anchor block or a microblock */
  anchorMode: AnchorMode;
  /** an arbitrary string to include in the transaction, must be less than 34 bytes */
  memo?: string;
  /** the post condition mode, specifying whether or not post-conditions must fully cover all
   * transfered assets */
  postConditionMode?: PostConditionMode;
  /** a list of post conditions to add to the transaction */
  postConditions?: PostCondition[];
  /** set to true if another account is sponsoring the transaction (covering the transaction fee) */
  sponsored?: boolean;
}

export interface UnsignedTokenTransferOptions extends TokenTransferOptions {
  publicKey: string;
}

export interface SignedTokenTransferOptions extends TokenTransferOptions {
  senderKey: string;
}

export interface UnsignedMultiSigTokenTransferOptions extends TokenTransferOptions {
  numSignatures: number;
  publicKeys: string[];
}

export interface SignedMultiSigTokenTransferOptions extends TokenTransferOptions {
  numSignatures: number;
  publicKeys: string[];
  signerKeys: string[];
}

/**
 * Generates an unsigned Stacks token transfer transaction
 *
 * Returns a Stacks token transfer transaction.
 *
 * @param  {UnsignedTokenTransferOptions | UnsignedMultiSigTokenTransferOptions} txOptions - an options object for the token transfer
 *
 * @return {Promis<StacksTransaction>}
 */
export async function makeUnsignedSTXTokenTransfer(
  txOptions: UnsignedTokenTransferOptions | UnsignedMultiSigTokenTransferOptions
): Promise<StacksTransaction> {
  const defaultOptions = {
    fee: BigInt(0),
    nonce: BigInt(0),
    network: new StacksMainnet(),
    postConditionMode: PostConditionMode.Deny,
    memo: '',
    sponsored: false,
  };

  const options = Object.assign(defaultOptions, txOptions);

  const payload = createTokenTransferPayload(options.recipient, options.amount, options.memo);

  let authorization = null;
  let spendingCondition = null;

  if ('publicKey' in options) {
    // single-sig
    spendingCondition = createSingleSigSpendingCondition(
      AddressHashMode.SerializeP2PKH,
      options.publicKey,
      options.nonce,
      options.fee
    );
  } else {
    // multi-sig
    spendingCondition = createMultiSigSpendingCondition(
      AddressHashMode.SerializeP2SH,
      options.numSignatures,
      options.publicKeys,
      options.nonce,
      options.fee
    );
  }

  if (options.sponsored) {
    authorization = new SponsoredAuthorization(spendingCondition);
  } else {
    authorization = new StandardAuthorization(spendingCondition);
  }

  const postConditions: PostCondition[] = [];
  if (options.postConditions && options.postConditions.length > 0) {
    options.postConditions.forEach(postCondition => {
      postConditions.push(postCondition);
    });
  }

  const lpPostConditions = createLPList(postConditions);
  const transaction = new StacksTransaction(
    options.network.version,
    authorization,
    payload,
    lpPostConditions,
    options.postConditionMode,
    options.anchorMode,
    options.network.chainId
  );

  if (txOptions.fee === undefined || txOptions.fee === null) {
    const txFee = await estimateTransfer(transaction, options.network);
    transaction.setFee(txFee);
  }

  if (txOptions.nonce === undefined || txOptions.nonce === null) {
    const addressVersion =
      options.network.version === TransactionVersion.Mainnet
        ? AddressVersion.MainnetSingleSig
        : AddressVersion.TestnetSingleSig;
    const senderAddress = c32address(addressVersion, transaction.auth.spendingCondition!.signer);
    const txNonce = await getNonce(senderAddress, options.network);
    transaction.setNonce(txNonce);
  }

  return transaction;
}

/**
 * Generates a signed Stacks token transfer transaction
 *
 * Returns a signed Stacks token transfer transaction.
 *
 * @param  {SignedTokenTransferOptions | SignedMultiSigTokenTransferOptions} txOptions - an options object for the token transfer
 *
 * @return {StacksTransaction}
 */
export async function makeSTXTokenTransfer(
  txOptions: SignedTokenTransferOptions | SignedMultiSigTokenTransferOptions
): Promise<StacksTransaction> {
  if ('senderKey' in txOptions) {
    const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey)));
    const options = omit(txOptions, 'senderKey');
    const transaction = await makeUnsignedSTXTokenTransfer({ publicKey, ...options });

    const privKey = createStacksPrivateKey(txOptions.senderKey);
    const signer = new TransactionSigner(transaction);
    signer.signOrigin(privKey);

    return transaction;
  } else {
    const options = omit(txOptions, 'signerKeys');
    const transaction = await makeUnsignedSTXTokenTransfer(options);

    const signer = new TransactionSigner(transaction);
    let pubKeys = txOptions.publicKeys;
    for (const key of txOptions.signerKeys) {
      const pubKey = pubKeyfromPrivKey(key);
      pubKeys = pubKeys.filter(pk => pk !== pubKey.data.toString('hex'));
      signer.signOrigin(createStacksPrivateKey(key));
    }

    for (const key of pubKeys) {
      signer.appendOrigin(publicKeyFromBuffer(Buffer.from(key, 'hex')));
    }

    return transaction;
  }
}

/**
 * Contract deploy transaction options
 */
export interface ContractDeployOptions {
  contractName: string;
  /** the Clarity code to be deployed */
  codeBody: string;
  /** a hex string of the private key of the transaction sender */
  senderKey: string;
  /** transaction fee in microstacks */
  fee?: IntegerType;
  /** the transaction nonce, which must be increased monotonically with each new transaction */
  nonce?: IntegerType;
  /** the network that the transaction will ultimately be broadcast to */
  network?: StacksNetwork;
  /** the transaction anchorMode, which specifies whether it should be
   * included in an anchor block or a microblock */
  anchorMode: AnchorMode;
  /** the post condition mode, specifying whether or not post-conditions must fully cover all
   * transfered assets */
  postConditionMode?: PostConditionMode;
  /** a list of post conditions to add to the transaction */
  postConditions?: PostCondition[];
  /** set to true if another account is sponsoring the transaction (covering the transaction fee) */
  sponsored?: boolean;
}

/**
 * Estimate the total transaction fee in microstacks for a contract deploy
 *
 * @param {StacksTransaction} transaction - the token transfer transaction to estimate fees for
 * @param {StacksNetwork} network - the Stacks network to estimate transaction for
 *
 * @return a promise that resolves to number of microstacks per byte
 */
export async function estimateContractDeploy(
  transaction: StacksTransaction,
  network?: StacksNetwork
): Promise<bigint> {
  if (transaction.payload.payloadType !== PayloadType.SmartContract) {
    throw new Error(
      `Contract deploy fee estimation only possible with ${
        PayloadType[PayloadType.SmartContract]
      } transactions. Invoked with: ${PayloadType[transaction.payload.payloadType]}`
    );
  }

  const requestHeaders = {
    Accept: 'application/text',
  };

  const fetchOptions = {
    method: 'GET',
    headers: requestHeaders,
  };

  // Place holder estimate until contract deploy fee estimation is fully implemented on Stacks
  // blockchain core
  const defaultNetwork = new StacksMainnet();
  const url = network
    ? network.getTransferFeeEstimateApiUrl()
    : defaultNetwork.getTransferFeeEstimateApiUrl();

  const response = await fetchPrivate(url, fetchOptions);
  if (!response.ok) {
    let msg = '';
    try {
      msg = await response.text();
    } catch (error) {}
    throw new Error(
      `Error estimating contract deploy fee. Response ${response.status}: ${response.statusText}. Attempted to fetch ${url} and failed with the message: "${msg}"`
    );
  }
  const feeRateResult = await response.text();
  const txBytes = intToBigInt(transaction.serialize().byteLength, false);
  const feeRate = intToBigInt(feeRateResult, false);
  return feeRate * txBytes;
}

/**
 * Generates a Clarity smart contract deploy transaction
 *
 * @param  {ContractDeployOptions} txOptions - an options object for the contract deploy
 *
 * Returns a signed Stacks smart contract deploy transaction.
 *
 * @return {StacksTransaction}
 */
export async function makeContractDeploy(
  txOptions: ContractDeployOptions
): Promise<StacksTransaction> {
  const defaultOptions = {
    fee: BigInt(0),
    nonce: BigInt(0),
    network: new StacksMainnet(),
    postConditionMode: PostConditionMode.Deny,
    sponsored: false,
  };

  const options = Object.assign(defaultOptions, txOptions);

  const payload = createSmartContractPayload(options.contractName, options.codeBody);

  const addressHashMode = AddressHashMode.SerializeP2PKH;
  const privKey = createStacksPrivateKey(options.senderKey);
  const pubKey = getPublicKey(privKey);

  let authorization = null;

  const spendingCondition = createSingleSigSpendingCondition(
    addressHashMode,
    publicKeyToString(pubKey),
    options.nonce,
    options.fee
  );

  if (options.sponsored) {
    authorization = new SponsoredAuthorization(spendingCondition);
  } else {
    authorization = new StandardAuthorization(spendingCondition);
  }

  const postConditions: PostCondition[] = [];
  if (options.postConditions && options.postConditions.length > 0) {
    options.postConditions.forEach(postCondition => {
      postConditions.push(postCondition);
    });
  }

  const lpPostConditions = createLPList(postConditions);
  const transaction = new StacksTransaction(
    options.network.version,
    authorization,
    payload,
    lpPostConditions,
    options.postConditionMode,
    options.anchorMode,
    options.network.chainId
  );

  if (txOptions.fee === undefined || txOptions.fee === null) {
    const txFee = await estimateContractDeploy(transaction, options.network);
    transaction.setFee(txFee);
  }

  if (txOptions.nonce === undefined || txOptions.nonce === null) {
    const addressVersion =
      options.network.version === TransactionVersion.Mainnet
        ? AddressVersion.MainnetSingleSig
        : AddressVersion.TestnetSingleSig;
    const senderAddress = publicKeyToAddress(addressVersion, pubKey);
    const txNonce = await getNonce(senderAddress, options.network);
    transaction.setNonce(txNonce);
  }

  if (options.senderKey) {
    const signer = new TransactionSigner(transaction);
    signer.signOrigin(privKey);
  }

  return transaction;
}

/**
 * Contract function call transaction options
 */
export interface ContractCallOptions {
  /** the Stacks address of the contract */
  contractAddress: string;
  contractName: string;
  functionName: string;
  functionArgs: ClarityValue[];
  /** transaction fee in microstacks */
  fee?: IntegerType;
  feeEstimateApiUrl?: string;
  /** the transaction nonce, which must be increased monotonically with each new transaction */
  nonce?: IntegerType;
  /** the Stacks blockchain network that will ultimately be used to broadcast this transaction */
  network?: StacksNetwork;
  /** the transaction anchorMode, which specifies whether it should be
   * included in an anchor block or a microblock */
  anchorMode: AnchorMode;
  /** the post condition mode, specifying whether or not post-conditions must fully cover all
   * transfered assets */
  postConditionMode?: PostConditionMode;
  /** a list of post conditions to add to the transaction */
  postConditions?: PostCondition[];
  /** set to true to validate that the supplied function args match those specified in
   * the published contract */
  validateWithAbi?: boolean | ClarityAbi;
  /** set to true if another account is sponsoring the transaction (covering the transaction fee) */
  sponsored?: boolean;
}

export interface UnsignedContractCallOptions extends ContractCallOptions {
  publicKey: string;
}

export interface SignedContractCallOptions extends ContractCallOptions {
  senderKey: string;
}

export interface UnsignedMultiSigContractCallOptions extends ContractCallOptions {
  numSignatures: number;
  publicKeys: string[];
}

export interface SignedMultiSigContractCallOptions extends ContractCallOptions {
  numSignatures: number;
  publicKeys: string[];
  signerKeys: string[];
}

/**
 * Estimate the total transaction fee in microstacks for a contract function call
 *
 * @param {StacksTransaction} transaction - the token transfer transaction to estimate fees for
 * @param {StacksNetwork} network - the Stacks network to estimate transaction for
 *
 * @return a promise that resolves to number of microstacks per byte
 */
export async function estimateContractFunctionCall(
  transaction: StacksTransaction,
  network?: StacksNetwork
): Promise<bigint> {
  if (transaction.payload.payloadType !== PayloadType.ContractCall) {
    throw new Error(
      `Contract call fee estimation only possible with ${
        PayloadType[PayloadType.ContractCall]
      } transactions. Invoked with: ${PayloadType[transaction.payload.payloadType]}`
    );
  }

  const requestHeaders = {
    Accept: 'application/text',
  };

  const fetchOptions = {
    method: 'GET',
    headers: requestHeaders,
  };

  // Place holder estimate until contract call fee estimation is fully implemented on Stacks
  // blockchain core
  const defaultNetwork = new StacksMainnet();
  const url = network
    ? network.getTransferFeeEstimateApiUrl()
    : defaultNetwork.getTransferFeeEstimateApiUrl();

  const response = await fetchPrivate(url, fetchOptions);
  if (!response.ok) {
    let msg = '';
    try {
      msg = await response.text();
    } catch (error) {}
    throw new Error(
      `Error estimating contract call fee. Response ${response.status}: ${response.statusText}. Attempted to fetch ${url} and failed with the message: "${msg}"`
    );
  }
  const feeRateResult = await response.text();
  const txBytes = intToBigInt(transaction.serialize().byteLength, false);
  const feeRate = intToBigInt(feeRateResult, false);
  return feeRate * txBytes;
}

/**
 * Generates an unsigned Clarity smart contract function call transaction
 *
 * @param {UnsignedContractCallOptions | UnsignedMultiSigContractCallOptions} txOptions - an options object for the contract call
 *
 * @returns {Promise<StacksTransaction>}
 */
export async function makeUnsignedContractCall(
  txOptions: UnsignedContractCallOptions | UnsignedMultiSigContractCallOptions
): Promise<StacksTransaction> {
  const defaultOptions = {
    fee: BigInt(0),
    nonce: BigInt(0),
    network: new StacksMainnet(),
    postConditionMode: PostConditionMode.Deny,
    sponsored: false,
  };

  const options = Object.assign(defaultOptions, txOptions);

  const payload = createContractCallPayload(
    options.contractAddress,
    options.contractName,
    options.functionName,
    options.functionArgs
  );

  if (options?.validateWithAbi) {
    let abi: ClarityAbi;
    if (typeof options.validateWithAbi === 'boolean') {
      if (options?.network) {
        abi = await getAbi(options.contractAddress, options.contractName, options.network);
      } else {
        throw new Error('Network option must be provided in order to validate with ABI');
      }
    } else {
      abi = options.validateWithAbi;
    }

    validateContractCall(payload, abi);
  }

  let spendingCondition = null;
  let authorization = null;

  if ('publicKey' in options) {
    // single-sig
    spendingCondition = createSingleSigSpendingCondition(
      AddressHashMode.SerializeP2PKH,
      options.publicKey,
      options.nonce,
      options.fee
    );
  } else {
    // multi-sig
    spendingCondition = createMultiSigSpendingCondition(
      AddressHashMode.SerializeP2SH,
      options.numSignatures,
      options.publicKeys,
      options.nonce,
      options.fee
    );
  }

  if (options.sponsored) {
    authorization = new SponsoredAuthorization(spendingCondition);
  } else {
    authorization = new StandardAuthorization(spendingCondition);
  }

  const postConditions: PostCondition[] = [];
  if (options.postConditions && options.postConditions.length > 0) {
    options.postConditions.forEach(postCondition => {
      postConditions.push(postCondition);
    });
  }

  const lpPostConditions = createLPList(postConditions);
  const transaction = new StacksTransaction(
    options.network.version,
    authorization,
    payload,
    lpPostConditions,
    options.postConditionMode,
    options.anchorMode,
    options.network.chainId
  );

  if (txOptions.fee === undefined || txOptions.fee === null) {
    const txFee = await estimateContractFunctionCall(transaction, options.network);
    transaction.setFee(txFee);
  }

  if (txOptions.nonce === undefined || txOptions.nonce === null) {
    const addressVersion =
      options.network.version === TransactionVersion.Mainnet
        ? AddressVersion.MainnetSingleSig
        : AddressVersion.TestnetSingleSig;
    const senderAddress = c32address(addressVersion, transaction.auth.spendingCondition!.signer);
    const txNonce = await getNonce(senderAddress, options.network);
    transaction.setNonce(txNonce);
  }

  return transaction;
}

/**
 * Generates a Clarity smart contract function call transaction
 *
 * @param  {SignedContractCallOptions | SignedMultiSigContractCallOptions} txOptions - an options object for the contract function call
 *
 * Returns a signed Stacks smart contract function call transaction.
 *
 * @return {StacksTransaction}
 */
export async function makeContractCall(
  txOptions: SignedContractCallOptions | SignedMultiSigContractCallOptions
): Promise<StacksTransaction> {
  if ('senderKey' in txOptions) {
    const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey)));
    const options = omit(txOptions, 'senderKey');
    const transaction = await makeUnsignedContractCall({ publicKey, ...options });

    const privKey = createStacksPrivateKey(txOptions.senderKey);
    const signer = new TransactionSigner(transaction);
    signer.signOrigin(privKey);

    return transaction;
  } else {
    const options = omit(txOptions, 'signerKeys');
    const transaction = await makeUnsignedContractCall(options);

    const signer = new TransactionSigner(transaction);
    let pubKeys = txOptions.publicKeys;
    for (const key of txOptions.signerKeys) {
      const pubKey = pubKeyfromPrivKey(key);
      pubKeys = pubKeys.filter(pk => pk !== pubKey.data.toString('hex'));
      signer.signOrigin(createStacksPrivateKey(key));
    }

    for (const key of pubKeys) {
      signer.appendOrigin(publicKeyFromBuffer(Buffer.from(key, 'hex')));
    }

    return transaction;
  }
}

/**
 * Generates a STX post condition with a standard principal
 *
 * Returns a STX post condition object
 *
 * @param address - the c32check address
 * @param conditionCode - the condition code
 * @param amount - the amount of STX tokens
 */
export function makeStandardSTXPostCondition(
  address: string,
  conditionCode: FungibleConditionCode,
  amount: IntegerType
): STXPostCondition {
  return createSTXPostCondition(createStandardPrincipal(address), conditionCode, amount);
}

/**
 * Generates a STX post condition with a contract principal
 *
 * Returns a STX post condition object
 *
 * @param address - the c32check address of the contract
 * @param contractName - the name of the contract
 * @param conditionCode - the condition code
 * @param amount - the amount of STX tokens
 *
 * @return {STXPostCondition}
 */
export function makeContractSTXPostCondition(
  address: string,
  contractName: string,
  conditionCode: FungibleConditionCode,
  amount: IntegerType
): STXPostCondition {
  return createSTXPostCondition(
    createContractPrincipal(address, contractName),
    conditionCode,
    amount
  );
}

/**
 * Generates a fungible token post condition with a standard principal
 *
 * Returns a fungible token post condition object
 *
 * @param address - the c32check address
 * @param conditionCode - the condition code
 * @param amount - the amount of fungible tokens
 * @param assetInfo - asset info describing the fungible token
 */
export function makeStandardFungiblePostCondition(
  address: string,
  conditionCode: FungibleConditionCode,
  amount: IntegerType,
  assetInfo: string | AssetInfo
): FungiblePostCondition {
  return createFungiblePostCondition(
    createStandardPrincipal(address),
    conditionCode,
    amount,
    assetInfo
  );
}

/**
 * Generates a fungible token post condition with a contract principal
 *
 * Returns a fungible token post condition object
 *
 * @param address - the c32check address
 * @param contractName - the name of the contract
 * @param conditionCode - the condition code
 * @param amount - the amount of fungible tokens
 * @param assetInfo - asset info describing the fungible token
 */
export function makeContractFungiblePostCondition(
  address: string,
  contractName: string,
  conditionCode: FungibleConditionCode,
  amount: IntegerType,
  assetInfo: string | AssetInfo
): FungiblePostCondition {
  return createFungiblePostCondition(
    createContractPrincipal(address, contractName),
    conditionCode,
    amount,
    assetInfo
  );
}

/**
 * Generates a non-fungible token post condition with a standard principal
 *
 * Returns a non-fungible token post condition object
 *
 * @param  {String} address - the c32check address
 * @param  {FungibleConditionCode} conditionCode - the condition code
 * @param  {AssetInfo} assetInfo - asset info describing the non-fungible token
 * @param  {ClarityValue} assetName - asset name describing the non-fungible token
 *
 * @return {NonFungiblePostCondition}
 */
export function makeStandardNonFungiblePostCondition(
  address: string,
  conditionCode: NonFungibleConditionCode,
  assetInfo: string | AssetInfo,
  assetName: ClarityValue
): NonFungiblePostCondition {
  return createNonFungiblePostCondition(
    createStandardPrincipal(address),
    conditionCode,
    assetInfo,
    assetName
  );
}

/**
 * Generates a non-fungible token post condition with a contract principal
 *
 * Returns a non-fungible token post condition object
 *
 * @param  {String} address - the c32check address
 * @param  {String} contractName - the name of the contract
 * @param  {FungibleConditionCode} conditionCode - the condition code
 * @param  {AssetInfo} assetInfo - asset info describing the non-fungible token
 * @param  {ClarityValue} assetName - asset name describing the non-fungible token
 *
 * @return {NonFungiblePostCondition}
 */
export function makeContractNonFungiblePostCondition(
  address: string,
  contractName: string,
  conditionCode: NonFungibleConditionCode,
  assetInfo: string | AssetInfo,
  assetName: ClarityValue
): NonFungiblePostCondition {
  return createNonFungiblePostCondition(
    createContractPrincipal(address, contractName),
    conditionCode,
    assetInfo,
    assetName
  );
}

/**
 * Read only function options
 *
 * @param  {String} contractAddress - the c32check address of the contract
 * @param  {String} contractName - the contract name
 * @param  {String} functionName - name of the function to be called
 * @param  {[ClarityValue]} functionArgs - an array of Clarity values as arguments to the function call
 * @param  {StacksNetwork} network - the Stacks blockchain network this transaction is destined for
 * @param  {String} senderAddress - the c32check address of the sender
 */

export interface ReadOnlyFunctionOptions {
  contractName: string;
  contractAddress: string;
  functionName: string;
  functionArgs: ClarityValue[];
  /** the network that the contract which contains the function is deployed to */
  network?: StacksNetwork;
  /** address of the sender */
  senderAddress: string;
}

/**
 * Calls a read only function from a contract interface
 *
 * @param  {ReadOnlyFunctionOptions} readOnlyFunctionOptions - the options object
 *
 * Returns an object with a status bool (okay) and a result string that is a serialized clarity value in hex format.
 *
 * @return {ClarityValue}
 */
export async function callReadOnlyFunction(
  readOnlyFunctionOptions: ReadOnlyFunctionOptions
): Promise<ClarityValue> {
  const defaultOptions = {
    network: new StacksMainnet(),
  };

  const options = Object.assign(defaultOptions, readOnlyFunctionOptions);

  const { contractName, contractAddress, functionName, functionArgs, network, senderAddress } =
    options;

  const url = network.getReadOnlyFunctionCallApiUrl(contractAddress, contractName, functionName);

  const args = functionArgs.map(arg => cvToHex(arg));

  const body = JSON.stringify({
    sender: senderAddress,
    arguments: args,
  });

  const response = await fetchPrivate(url, {
    method: 'POST',
    body,
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    let msg = '';
    try {
      msg = await response.text();
    } catch (error) {}
    throw new Error(
      `Error calling read-only function. Response ${response.status}: ${response.statusText}. Attempted to fetch ${url} and failed with the message: "${msg}"`
    );
  }

  return response.json().then(responseJson => parseReadOnlyResponse(responseJson));
}

/**
 * Sponsored transaction options
 */
export interface SponsorOptionsOpts {
  /** the origin-signed transaction */
  transaction: StacksTransaction;
  /** the sponsor's private key */
  sponsorPrivateKey: string;
  /** the transaction fee amount to sponsor */
  fee?: IntegerType;
  /** the nonce of the sponsor account */
  sponsorNonce?: IntegerType;
  /** the hashmode of the sponsor's address */
  sponsorAddressHashmode?: AddressHashMode;
  /** the Stacks blockchain network that this transaction will ultimately be broadcast to */
  network?: StacksNetwork;
}

/**
 * Constructs and signs a sponsored transaction as the sponsor
 *
 * @param  {SponsorOptionsOpts} sponsorOptions - the sponsor options object
 *
 * Returns a signed sponsored transaction.
 *
 * @return {ClarityValue}
 */
export async function sponsorTransaction(
  sponsorOptions: SponsorOptionsOpts
): Promise<StacksTransaction> {
  const defaultOptions = {
    fee: 0 as IntegerType,
    sponsorNonce: 0 as IntegerType,
    sponsorAddressHashmode: AddressHashMode.SerializeP2PKH as SingleSigHashMode,
  };

  const options = Object.assign(defaultOptions, sponsorOptions);
  const network =
    sponsorOptions.network ??
    (options.transaction.version === TransactionVersion.Mainnet
      ? new StacksMainnet()
      : new StacksTestnet());
  const sponsorPubKey = pubKeyfromPrivKey(options.sponsorPrivateKey);

  if (sponsorOptions.fee === undefined || sponsorOptions.fee === null) {
    let txFee = BigInt(0);
    switch (options.transaction.payload.payloadType) {
      case PayloadType.TokenTransfer:
        txFee = await estimateTransfer(options.transaction, network);
        break;
      case PayloadType.SmartContract:
        txFee = await estimateContractDeploy(options.transaction, network);
        break;
      case PayloadType.ContractCall:
        txFee = await estimateContractFunctionCall(options.transaction, network);
        break;
      default:
        throw new Error(
          `Sponsored transactions not supported for transaction type ${
            PayloadType[options.transaction.payload.payloadType]
          }`
        );
    }
    options.transaction.setFee(txFee);
    options.fee = txFee;
  }

  if (sponsorOptions.sponsorNonce === undefined || sponsorOptions.sponsorNonce === null) {
    const addressVersion =
      network.version === TransactionVersion.Mainnet
        ? AddressVersion.MainnetSingleSig
        : AddressVersion.TestnetSingleSig;

    const senderAddress = publicKeyToAddress(addressVersion, sponsorPubKey);
    const sponsorNonce = await getNonce(senderAddress, network);
    options.sponsorNonce = sponsorNonce;
  }

  const sponsorSpendingCondition = createSingleSigSpendingCondition(
    options.sponsorAddressHashmode,
    publicKeyToString(sponsorPubKey),
    options.sponsorNonce,
    options.fee
  );

  options.transaction.setSponsor(sponsorSpendingCondition);

  const privKey = createStacksPrivateKey(options.sponsorPrivateKey);
  const signer = TransactionSigner.createSponsorSigner(
    options.transaction,
    sponsorSpendingCondition
  );
  signer.signSponsor(privKey);

  return options.transaction;
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!