0

Is it possible to get the transaction hash before the transaction is sent using Taquito? If yes, could you suggest how?

Question from KStasi dev Slack.

1 Answer 1

0

Do you mean the operation hash? If yes, I assume you mean if it’s possible to get an op hash without actually injecting the operation? The op hash is returned as part of the response object when an operation gets injected, so it’s not possible currently.

But however, we found some indirect workaround:

import {
  Estimate,
  ForgedBytes,
  Forger,
  InvalidEstimateValueError,
  PreparedOperation,
  TaquitoLocalForger,
  TezosToolkit,
  TransferParams,
} from '@taquito/taquito';
import {
  encodeOpHash,
  InvalidAddressError,
  invalidDetail,
  validateAddress,
  ValidationResult,
} from '@taquito/utils';
import { InvalidAmountError } from '@taquito/core';

const MagicByte = new Uint8Array([3]);

export class TransferHelper {
  forger: Forger;
  constructor(private readonly tezos: TezosToolkit) {
    this.forger = tezos.getFactory(TaquitoLocalForger)();
  }

  async getTransferHash(params: TransferParams) {
    const toValidation = validateAddress(params.to);
    if (toValidation !== ValidationResult.VALID) {
      throw new InvalidAddressError(params.to, invalidDetail(toValidation));
    }
    const sourceValidation = validateAddress(params.source ?? '');
    if (params.source && sourceValidation !== ValidationResult.VALID) {
      throw new InvalidAddressError(
        params.source,
        invalidDetail(sourceValidation),
      );
    }
    if (params.amount < 0) {
      throw new InvalidAmountError(params.amount.toString());
    }

    const estimate = await this.estimate(
      params,
      this.tezos.estimate.transfer.bind(this.tezos.estimate),
    );

    const prepared = await this.tezos.prepare.transaction({
      ...params,
      ...estimate,
    });

    const forgedBytesOpBytes = await this.forge(prepared);

    const signedOpBytes = await this.tezos.signer.sign(
      forgedBytesOpBytes,
      MagicByte,
    );
    return encodeOpHash(signedOpBytes.sbytes);
  }

  private async forge({
    opOb: { branch, contents },
  }: PreparedOperation): Promise<ForgedBytes['opbytes']> {
    return await this.forger.forge({ branch, contents });
  }

  private async estimate<
    T extends { fee?: number; gasLimit?: number; storageLimit?: number },
  >(
    { fee, gasLimit, storageLimit, ...rest }: T,
    estimator: (param: T) => Promise<Estimate>,
  ) {
    let calculatedFee = fee;
    let calculatedGas = gasLimit;
    let calculatedStorage = storageLimit;

    if (calculatedFee && calculatedFee % 1 !== 0) {
      throw new InvalidEstimateValueError(
        `Fee value must not be a decimal: ${calculatedFee}`,
      );
    }
    if (calculatedGas && calculatedGas % 1 !== 0) {
      throw new InvalidEstimateValueError(
        `Gas Limit value must not be a decimal: ${calculatedGas}`,
      );
    }
    if (calculatedStorage && calculatedStorage % 1 !== 0) {
      throw new InvalidEstimateValueError(
        `Storage Limit value must not be a decimal: ${calculatedStorage}`,
      );
    }

    if (
      fee === undefined ||
      gasLimit === undefined ||
      storageLimit === undefined
    ) {
      const estimation = await estimator({
        fee,
        gasLimit,
        storageLimit,
        ...(rest as any),
      });
      calculatedFee ??= estimation.suggestedFeeMutez;
      calculatedGas ??= estimation.gasLimit;
      calculatedStorage ??= estimation.storageLimit;
    }

    return {
      fee: calculatedFee,
      gasLimit: calculatedGas,
      storageLimit: calculatedStorage,
    };
  }
}

Response from Davis Sawali & KStasi.

1
  • Hello KStasi, after digging a bit deeper in our code, looks like you figured out the solution after all using encodeOpHash(). Since it’s kind of a common use case at this point, we’ll add a page on our website to highlight this workaround.
    – user9011
    Jul 6, 2023 at 12:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.