PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-stx/src/lib

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

import {
  addressToString,
  BufferReader,
  ContractCallPayload,
  createStacksPrivateKey,
  createStacksPublicKey,
  createTransactionAuthField,
  cvToString,
  cvToValue,
  deserializeTransaction,
  isSingleSig,
  MultiSigSpendingCondition,
  PayloadType,
  PubKeyEncoding,
  StacksMessageType,
  StacksTransaction,
  TransactionSigner,
} from '@stacks/transactions';

import { BaseCoin as CoinConfig, StacksNetwork } from '@bitgo/statics';

import {
  BaseKey,
  BaseTransaction,
  InvalidTransactionError,
  NotSupported,
  ParseTransactionError,
  SigningError,
  TransactionType,
} from '@bitgo/sdk-core';

import BigNum from 'bn.js';

import { SignatureData, StacksContractPayload, StacksTransactionPayload, TxData } from './iface';
import { functionArgsToSendParams, getTxSenderAddress, removeHexPrefix, stringifyCv, unpadMemo } from './utils';
import { KeyPair } from './keyPair';
import { FUNCTION_NAME_TRANSFER } from './constants';

export class Transaction extends BaseTransaction {
  private _stxTransaction: StacksTransaction;
  protected _type: TransactionType;
  private _sigHash: string;

  constructor(_coinConfig: Readonly<CoinConfig>) {
    super(_coinConfig);
  }

  /** @inheritdoc */
  canSign(key: BaseKey): boolean {
    return true;
  }

  async sign(keyPair: KeyPair[] | KeyPair, sigHash?: string): Promise<void> {
    const keyPairs = keyPair instanceof Array ? keyPair : [keyPair];
    const signer = new TransactionSigner(this._stxTransaction);
    signer.checkOversign = false;
    signer.sigHash = sigHash ?? this._sigHash ?? this._stxTransaction.verifyBegin();
    for (const kp of keyPairs) {
      const keys = kp.getKeys(kp.getCompressed());
      if (!keys.prv) {
        throw new SigningError('Missing private key');
      }
      const privKey = createStacksPrivateKey(keys.prv);
      signer.signOrigin(privKey);
      this._sigHash = signer.sigHash;
    }
  }

  async appendOrigin(pubKeyString: string[] | string): Promise<void> {
    const pubKeyStrings = pubKeyString instanceof Array ? pubKeyString : [pubKeyString];
    const signer: TransactionSigner = new TransactionSigner(this._stxTransaction);
    pubKeyStrings.forEach((pubKey) => {
      signer.appendOrigin(createStacksPublicKey(pubKey));
    });
  }

  async signWithSignatures(signature: SignatureData[] | SignatureData, isMultiSig: boolean): Promise<void> {
    if (!signature) {
      throw new SigningError('Missing signatures');
    }
    const signatures = signature instanceof Array ? signature : [signature];

    if (!isMultiSig) {
      this._stxTransaction = this._stxTransaction.createTxWithSignature(signatures[0].data);
    } else {
      const authFields = signatures.map((sig) => createTransactionAuthField(PubKeyEncoding.Compressed, sig));
      (this._stxTransaction.auth.spendingCondition as MultiSigSpendingCondition).fields = (
        this._stxTransaction.auth.spendingCondition as MultiSigSpendingCondition
      ).fields.concat(authFields);
    }
    if (signatures.length > 0) {
      this._sigHash = signatures[signatures.length - 1].sigHash;
    }
  }

  get signature(): string[] {
    if (this._stxTransaction && this._stxTransaction.auth.spendingCondition) {
      if (isSingleSig(this._stxTransaction.auth.spendingCondition)) {
        return [this._stxTransaction.auth.spendingCondition.signature.data];
      } else {
        const signatures: string[] = [];
        this._stxTransaction.auth.spendingCondition.fields.forEach((field) => {
          if (field.contents.type === StacksMessageType.MessageSignature) {
            signatures.push(field.contents.data);
          }
        });
        return signatures;
      }
    }
    return [];
  }

  /** @inheritdoc */
  toJson(): TxData {
    if (!this._stxTransaction) {
      throw new ParseTransactionError('Empty transaction');
    }
    const result: TxData = {
      id: this._stxTransaction.txid(),
      fee: this._stxTransaction.auth.getFee().toString(10),
      from: getTxSenderAddress(this._stxTransaction),
      nonce: this.getNonce(),
      payload: this.getPayloadData(),
    };
    return result;
  }

  private getPayloadData(): StacksTransactionPayload | StacksContractPayload {
    if (this._stxTransaction.payload.payloadType === PayloadType.TokenTransfer) {
      const payload = this._stxTransaction.payload;
      const txPayload: StacksTransactionPayload = {
        payloadType: PayloadType.TokenTransfer,
        // result.payload.memo will be padded with \u0000 up to
        // MEMO_MAX_LENGTH_BYTES as defined in @stacks/transactions
        memo: unpadMemo(payload.memo.content),
        to: addressToString({
          type: StacksMessageType.Address,
          version: payload.recipient.address.version,
          hash160: payload.recipient.address.hash160.toString(),
        }),
        amount: payload.amount.toString(),
      };
      return txPayload;
    } else if (this._stxTransaction.payload.payloadType === PayloadType.ContractCall) {
      const payload = this._stxTransaction.payload;
      const contractPayload: StacksContractPayload = {
        payloadType: PayloadType.ContractCall,
        contractAddress: addressToString(payload.contractAddress),
        contractName: payload.contractName.content,
        functionName: payload.functionName.content,
        functionArgs: payload.functionArgs.map(stringifyCv),
      };
      return contractPayload;
    } else {
      throw new NotSupported('payload type not supported');
    }
  }

  /**
   * Return the length of a transaction.  This is needed to calculate
   * the transaction fee.
   *
   * @returns {number} size in bytes of the serialized transaction
   */
  transactionSize(): number {
    return this._stxTransaction.serialize().length;
  }

  toBroadcastFormat(): string {
    if (!this._stxTransaction) {
      throw new ParseTransactionError('Empty transaction');
    }
    return this._stxTransaction.serialize().toString('hex');
  }

  get stxTransaction(): StacksTransaction {
    return this._stxTransaction;
  }

  set stxTransaction(t: StacksTransaction) {
    this._stxTransaction = t;
  }

  private getNonce(): number {
    if (this._stxTransaction.auth.spendingCondition) {
      return Number(this._stxTransaction.auth.spendingCondition.nonce);
    } else {
      throw new InvalidTransactionError('spending condition is null');
    }
  }

  /**
   * Sets this transaction payload
   *
   * @param rawTransaction
   */
  fromRawTransaction(rawTransaction: string): void {
    const raw = removeHexPrefix(rawTransaction);
    try {
      this._stxTransaction = deserializeTransaction(BufferReader.fromBuffer(Buffer.from(raw, 'hex')));
    } catch (e) {
      throw new ParseTransactionError('Error parsing the raw transaction');
    }
    this.loadInputsAndOutputs();
  }

  /**
   * Set the transaction type
   *
   * @param {TransactionType} transactionType The transaction type to be set
   */
  setTransactionType(transactionType: TransactionType): void {
    this._type = transactionType;
  }

  /**
   * Load the input and output data on this transaction using the transaction json
   * if there are outputs.
   */
  loadInputsAndOutputs(): void {
    const txJson = this.toJson();
    if (txJson.payload.payloadType === PayloadType.TokenTransfer) {
      if (txJson.payload.to && txJson.payload.amount) {
        this._outputs = [
          {
            address: txJson.payload.to,
            value: txJson.payload.amount,
            coin: this._coinConfig.name,
          },
        ];

        this._inputs = [
          {
            address: txJson.from,
            value: txJson.payload.amount,
            coin: this._coinConfig.name,
          },
        ];
      }
    } else if (txJson.payload.payloadType === PayloadType.ContractCall) {
      if (txJson.payload.contractAddress === (this._coinConfig.network as StacksNetwork).sendmanymemoContractAddress) {
        const sendParams = functionArgsToSendParams((this.stxTransaction.payload as ContractCallPayload).functionArgs);
        const coin = this._coinConfig.name;
        const sum: BigNum = sendParams.reduce((current, next) => current.add(new BigNum(next.amount)), new BigNum(0));
        this._outputs = sendParams.map((sendParam) => ({ address: sendParam.address, value: sendParam.amount, coin }));
        this._inputs = [{ address: txJson.from, value: sum.toString(), coin }];
      } else if (txJson.payload.functionName === FUNCTION_NAME_TRANSFER && txJson.payload.functionArgs.length >= 3) {
        this._outputs = [
          {
            address: cvToString(txJson.payload.functionArgs[2]),
            value: cvToValue(txJson.payload.functionArgs[0]).toString(),
            coin: this._coinConfig.name,
          },
        ];
        this._inputs = [
          {
            address: cvToString(txJson.payload.functionArgs[1]),
            value: cvToValue(txJson.payload.functionArgs[0]).toString(),
            coin: this._coinConfig.name,
          },
        ];
      } else {
        this._outputs = [
          {
            address: txJson.payload.contractAddress,
            value: '0',
            coin: this._coinConfig.name,
          },
        ];

        this._inputs = [
          {
            address: txJson.from,
            value: '0',
            coin: this._coinConfig.name,
          },
        ];
      }
    }
  }
}

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


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