PHP WebShell

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

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

import {
  BaseKey,
  BaseTransaction,
  Entry,
  InvalidTransactionError,
  NodeEnvironmentError,
  TransactionType,
} from '@bitgo/sdk-core';
import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs';
import { KeyPair } from './keyPair';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import adaUtils from './utils';

export interface TransactionInput {
  transaction_id: string;
  transaction_index: number;
}

export interface Asset {
  policy_id: string;
  asset_name: string;
  quantity: string;
}

export interface TransactionOutput {
  address: string;
  amount: string;
  multiAssets?: CardanoWasm.MultiAsset;
}

export interface Witness {
  publicKey: string;
  signature: string;
}
export enum CertType {
  StakeKeyRegistration,
  StakeKeyDelegation,
  StakeKeyDeregistration,
  StakePoolRegistration,
  VoteDelegation,
}

export interface Cert {
  type: CertType;
  stakeCredentialHash?: string;
  poolKeyHash?: string;
  dRepId?: string;
}

export interface Withdrawal {
  stakeAddress: string;
  value: string;
}

export type StakeKeyRegistrationCert = Cert;

export type StakeKeyDelegationCert = Cert;

export interface StakePoolRegistrationCert extends Cert {
  vrfKeyHash: string;
  pledge: string;
  cost: string;
  marginNumerator: string;
  marginDenominator: string;
  rewardAccount: string;
  poolOwners: string[];
}

export interface PledgeDetails {
  stakeKeyRegistration?: StakeKeyRegistrationCert;
  stakeKeyDelegation?: StakeKeyDelegationCert;
  stakePoolRegistration: StakePoolRegistrationCert;
}

/**
 * The transaction data returned from the toJson() function of a transaction
 */
export interface TxData {
  id: string;
  type: TransactionType;
  inputs: TransactionInput[];
  outputs: TransactionOutput[];
  witnesses: Witness[];
  certs: Cert[];
  withdrawals: Withdrawal[];
  pledgeDetails?: PledgeDetails;
}

export class Transaction extends BaseTransaction {
  private _transaction: CardanoWasm.Transaction;
  private _fee: string;
  private _pledgeDetails?: PledgeDetails;

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

  get transaction(): CardanoWasm.Transaction {
    return this._transaction;
  }

  set transaction(tx: CardanoWasm.Transaction) {
    this._transaction = tx;
    this._id = Buffer.from(CardanoWasm.hash_transaction(tx.body()).to_bytes()).toString('hex');
  }

  /** @inheritdoc */
  canSign(key: BaseKey): boolean {
    try {
      new KeyPair({ prv: key.key });
      return true;
    } catch {
      return false;
    }
  }

  toBroadcastFormat(): string {
    if (!this._transaction) {
      throw new InvalidTransactionError('Empty transaction data');
    }
    return Buffer.from(this._transaction.to_bytes()).toString('hex');
  }

  /** @inheritdoc */
  toJson(): TxData {
    if (!this._transaction) {
      throw new InvalidTransactionError('Empty transaction data');
    }

    const result: TxData = {
      id: this.id,
      type: this._type as TransactionType,
      inputs: [],
      outputs: [],
      witnesses: [],
      certs: [],
      withdrawals: [],
    };

    for (let i = 0; i < this._transaction.body().inputs().len(); i++) {
      const input = this._transaction.body().inputs().get(i);
      result.inputs.push({
        transaction_id: Buffer.from(input.transaction_id().to_bytes()).toString('hex'),
        transaction_index: input.index(),
      });
    }

    for (let i = 0; i < this._transaction.body().outputs().len(); i++) {
      const output = this._transaction.body().outputs().get(i);
      result.outputs.push({
        address: adaUtils.getAddressString(output.address()),
        amount: output.amount().coin().to_str(),
        multiAssets: output.amount().multiasset() || undefined,
      });
    }

    if (this._transaction.body().certs()) {
      for (let i = 0; i < this._transaction.body().certs()!.len(); i++) {
        const cert = this._transaction.body().certs()!.get(i);
        if (cert.as_stake_registration() !== undefined) {
          const stakeRegistration = cert.as_stake_registration() as CardanoWasm.StakeRegistration;
          result.certs.push({
            type: CertType.StakeKeyRegistration,
            stakeCredentialHash: Buffer.from(stakeRegistration.stake_credential().to_bytes()).toString('hex'),
          });
        }
        if (cert.as_stake_deregistration() !== undefined) {
          const stakeDeregistration = cert.as_stake_deregistration() as CardanoWasm.StakeDeregistration;
          result.certs.push({
            type: CertType.StakeKeyDeregistration,
            stakeCredentialHash: Buffer.from(stakeDeregistration.stake_credential().to_bytes()).toString('hex'),
          });
        }
        if (cert.as_stake_delegation() !== undefined) {
          const stakeDelegation = cert.as_stake_delegation() as CardanoWasm.StakeDelegation;
          result.certs.push({
            type: CertType.StakeKeyDelegation,
            stakeCredentialHash: Buffer.from(stakeDelegation.stake_credential().to_bytes()).toString('hex'),
            poolKeyHash: Buffer.from(stakeDelegation.pool_keyhash().to_bytes()).toString('hex'),
          });
        }
        if (cert.as_pool_registration() !== undefined) {
          const stakePoolRegistration = cert.as_pool_registration() as CardanoWasm.PoolRegistration;
          result.certs.push({
            type: CertType.StakePoolRegistration,
            poolKeyHash: Buffer.from(stakePoolRegistration.pool_params().operator().to_bytes()).toString('hex'),
          });
        }
        if (cert.as_vote_delegation() !== undefined) {
          const voteDelegation = cert.as_vote_delegation() as CardanoWasm.VoteDelegation;
          result.certs.push({
            type: CertType.VoteDelegation,
            stakeCredentialHash: Buffer.from(voteDelegation.stake_credential().to_bytes()).toString('hex'),
            dRepId: adaUtils.getDRepIdFromDRep(voteDelegation.drep()),
          });
        }
      }
    }

    result.pledgeDetails = this._pledgeDetails;

    if (this._transaction.body().withdrawals()) {
      const withdrawals = this._transaction.body().withdrawals() as CardanoWasm.Withdrawals;
      const keys = withdrawals.keys();
      for (let i = 0; i < keys.len(); i++) {
        const rewardAddress = keys.get(i);
        const reward = withdrawals.get(rewardAddress) as CardanoWasm.BigNum;
        result.withdrawals.push({
          stakeAddress: rewardAddress.to_address().to_bytes().toString(),
          value: reward.to_str(),
        });
      }
    }

    if (this._transaction.witness_set().vkeys() !== undefined) {
      const vkeys = this._transaction.witness_set().vkeys() as CardanoWasm.Vkeywitnesses;
      for (let i = 0; i < vkeys.len(); i++) {
        const vkey = (this._transaction.witness_set().vkeys() as CardanoWasm.Vkeywitnesses).get(i);
        result.witnesses.push({
          publicKey: vkey?.vkey().public_key().to_hex(),
          signature: vkey?.signature().to_hex(),
        });
      }
    }
    return result;
  }

  /**
   * Build input and output field for this transaction
   *
   */
  loadInputsAndOutputs(): void {
    const outputs: Entry[] = [];
    const inputs: Entry[] = [];

    const tx_outputs = this._transaction.body().outputs();
    for (let i = 0; i < tx_outputs.len(); i++) {
      const output = tx_outputs.get(i);
      outputs.push({
        address: adaUtils.getAddressString(output.address()),
        value: output.amount().coin().to_str(),
      });
    }

    this._outputs = outputs;
    this._inputs = inputs;
  }

  /** @inheritdoc */
  get signablePayload(): Buffer {
    return Buffer.from(CardanoWasm.hash_transaction(this._transaction.body()).to_bytes());
  }

  /**
   * Sets this transaction payload
   *
   * @param rawTx
   */
  fromRawTransaction(rawTx: string): void {
    if (CardanoWasm.Transaction === undefined) {
      // a temp fix until we solve import problem in webpack
      throw new NodeEnvironmentError('unable to load cardano serialization library');
    }
    const HEX_REGEX = /^[0-9a-fA-F]+$/;
    const bufferRawTransaction = HEX_REGEX.test(rawTx) ? Buffer.from(rawTx, 'hex') : Buffer.from(rawTx, 'base64');
    try {
      const txn = CardanoWasm.Transaction.from_bytes(bufferRawTransaction);
      this._transaction = txn;
      this._id = Buffer.from(CardanoWasm.hash_transaction(txn.body()).to_bytes()).toString('hex');
      this._type = TransactionType.Send;
      if (this._transaction.body().certs()) {
        const certs: CardanoWasm.Certificate[] = [];
        for (let i = 0; i < this._transaction.body().certs()!.len(); i++) {
          const cert = this._transaction.body().certs()!.get(i);
          certs.push(cert);
        }

        if (certs.some((c) => c.as_pool_registration() !== undefined)) {
          this._type = TransactionType.StakingPledge;
          const stakeKeyRegistration = certs.find((c) => c.as_stake_registration() !== undefined);
          const stakeKeyDelegation = certs.find((c) => c.as_stake_delegation() !== undefined);
          const stakePoolRegistration = certs.find((c) => c.as_pool_registration() !== undefined);

          this._pledgeDetails = {
            stakeKeyRegistration: this.loadStakeKeyRegistration(stakeKeyRegistration),
            stakeKeyDelegation: this.loadStakeKeyDelegation(stakeKeyDelegation),
            stakePoolRegistration: this.loadStakePoolRegistration(stakePoolRegistration!),
          };
        } else if (certs.some((c) => c.as_stake_registration() !== undefined)) {
          this._type = TransactionType.StakingActivate;
        } else if (certs.some((c) => c.as_stake_deregistration() !== undefined)) {
          this._type = TransactionType.StakingDeactivate;
        } else if (certs.some((c) => c.as_vote_delegation() !== undefined)) {
          this._type = TransactionType.VoteDelegation;
        }
      }
      if (this._transaction.body().withdrawals()) {
        this._type = TransactionType.StakingWithdraw;
      }

      this._fee = txn.body().fee().to_str();
      this.loadInputsAndOutputs();

      if (this._transaction.witness_set().vkeys()) {
        const vkeys = this._transaction.witness_set().vkeys()! as CardanoWasm.Vkeywitnesses;
        for (let i = 0; i < vkeys.len(); i++) {
          const vkey = vkeys.get(i);
          this._signatures.push(vkey.signature().to_hex());
        }
      }
    } catch (e) {
      throw new InvalidTransactionError('unable to build transaction from raw');
    }
  }

  private loadStakeKeyRegistration(
    certificate: CardanoWasm.Certificate | undefined
  ): StakeKeyRegistrationCert | undefined {
    if (certificate === undefined) {
      return undefined;
    }
    const stakeRegistration = certificate.as_stake_registration();
    if (stakeRegistration !== undefined && stakeRegistration!.stake_credential().to_keyhash() !== undefined) {
      return {
        type: CertType.StakeKeyRegistration,
        stakeCredentialHash: stakeRegistration!.stake_credential().to_keyhash()!.to_hex(),
      };
    } else {
      return undefined;
    }
  }

  private loadStakeKeyDelegation(certificate: CardanoWasm.Certificate | undefined): StakeKeyDelegationCert | undefined {
    if (certificate === undefined) {
      return undefined;
    }
    const stakeDelegation = certificate.as_stake_delegation();
    if (stakeDelegation !== undefined && stakeDelegation!.stake_credential().to_keyhash() !== undefined) {
      return {
        type: CertType.StakeKeyDelegation,
        stakeCredentialHash: stakeDelegation!.stake_credential().to_keyhash()!.to_hex(),
        poolKeyHash: stakeDelegation!.pool_keyhash().to_hex(),
      };
    } else {
      return undefined;
    }
  }

  private loadStakePoolRegistration(certificate: CardanoWasm.Certificate): StakePoolRegistrationCert {
    const poolRegistration = certificate.as_pool_registration();
    const rewardAccount = poolRegistration!.pool_params().reward_account();
    const networkId = rewardAccount.to_address().network_id();
    const owners: string[] = [];
    for (let i = 0; i < poolRegistration!.pool_params().pool_owners().len(); i++) {
      const poolOwner = poolRegistration!.pool_params().pool_owners().get(i);
      const ownerStakeKey = CardanoWasm.Credential.from_keyhash(poolOwner);
      owners.push(CardanoWasm.RewardAddress.new(networkId, ownerStakeKey).to_address().to_bech32());
    }
    return {
      type: CertType.StakePoolRegistration,
      poolKeyHash: poolRegistration!.pool_params().operator().to_hex(),
      vrfKeyHash: poolRegistration!.pool_params().vrf_keyhash().to_hex(),
      pledge: poolRegistration!.pool_params().pledge().to_str(),
      cost: poolRegistration!.pool_params().cost().to_str(),
      marginNumerator: poolRegistration!.pool_params().margin().numerator().to_str(),
      marginDenominator: poolRegistration!.pool_params().margin().denominator().to_str(),
      rewardAccount: rewardAccount.to_address().to_bech32(),
      poolOwners: owners,
    };
  }

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

  /** @inheritdoc */
  explainTransaction(): {
    outputs: { amount: string; address: string }[];
    certificates: Cert[];
    changeOutputs: string[];
    outputAmount: string;
    fee: { fee: string };
    displayOrder: string[];
    id: string;
    changeAmount: string;
    type: string;
    withdrawals: Withdrawal[];
    pledgeDetails?: PledgeDetails;
  } {
    const txJson = this.toJson();
    const displayOrder = ['id', 'outputAmount', 'changeAmount', 'outputs', 'changeOutputs', 'fee', 'type'];
    const amount = txJson.outputs.map((o) => ({ amount: BigInt(o.amount) }));
    const outputAmount = amount.reduce((p, n) => p + BigInt(n.amount), BigInt('0')).toString();
    const type =
      this._type === TransactionType.Send
        ? 'Transfer'
        : this._type === TransactionType.StakingActivate
        ? 'StakingActivate'
        : this._type === TransactionType.StakingWithdraw
        ? 'StakingWithdraw'
        : this._type === TransactionType.StakingDeactivate
        ? 'StakingDeactivate'
        : this._type === TransactionType.StakingPledge
        ? 'StakingPledge'
        : this._type === TransactionType.VoteDelegation
        ? 'VoteDelegation'
        : 'undefined';
    return {
      displayOrder,
      id: txJson.id,
      outputs: txJson.outputs.map((o) => ({ address: o.address, amount: o.amount })),
      outputAmount: outputAmount,
      changeOutputs: [],
      changeAmount: '0',
      fee: { fee: this._fee },
      type,
      certificates: txJson.certs,
      withdrawals: txJson.withdrawals,
      pledgeDetails: this._pledgeDetails,
    };
  }

  getPledgeDetails(): PledgeDetails | undefined {
    return this._pledgeDetails;
  }

  /**
   * Get transaction fee
   */
  get getFee(): string {
    return this._fee;
  }

  /**
   * Set transaction fee
   *
   * @param fee
   */
  fee(fee: string) {
    this._fee = fee;
  }
}

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


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