Source

client/erc20/FabricERC20ClientRepository.ts

import { FabricClientRepository } from "../FabricClientRepository";
import { ERC20Token, ERC20Wallet } from "../../contracts/erc20/models";
import { Model, Serializer } from "@decaf-ts/decorator-validation";
import {
  FabricClientAdapter,
  FabricClientContext,
} from "../FabricClientAdapter";
import { ClientSerializer } from "../../shared/ClientSerializer";
import { ContextualArgs, EventIds, Sequence } from "@decaf-ts/core";
import {
  BulkCrudOperationKeys,
  InternalError,
  OperationKeys,
} from "@decaf-ts/db-decorators";
import { Constructor } from "@decaf-ts/decoration";
/**
 * Repository for interacting with ERC20 contracts on a Hyperledger Fabric network.
 * Extends the base FabricClientRepository class and utilizes the ClientSerializer for data serialization.
 */
export class FabricERC20ClientRepository extends FabricClientRepository<ERC20Wallet> {
  private static serializer = new ClientSerializer();

  protected readonly serializer: Serializer<any> =
    FabricERC20ClientRepository.serializer;

  private static decoder = new TextDecoder("utf8");

  /**
   * @description Notifies all observers of an event.
   * @summary Updates all registered observers with information about a database event.
   * @param {string} table - The table name where the event occurred.
   * @param {OperationKeys|BulkCrudOperationKeys|string} event - The type of event that occurred.
   * @param {EventIds} id - The ID or IDs of the affected records.
   * @param {...any[]} args - Additional arguments.
   * @return {Promise<void>} A promise that resolves when all observers have been notified.
   * @throws {InternalError} If the observer handler is not initialized.
   */
  override async updateObservers<M extends Model>(
    table: Constructor<M> | string,
    event: OperationKeys | BulkCrudOperationKeys | string,
    id: EventIds,
    ...args: ContextualArgs<FabricClientContext>
  ): Promise<void> {
    if (!this.observerHandler)
      throw new InternalError(
        "ObserverHandler not initialized. Did you register any observables?"
      );
    const { log, ctxArgs } = this.logCtx(args, this.updateObservers);
    log.verbose(
      `Updating ${this.observerHandler.count()} observers for ${this}`
    );

    table = (
      typeof table === "string" ? Model.get(table) : table
    ) as Constructor<M>;
    let parsedId: string | string[] | undefined;

    if (id === undefined) {
      parsedId = undefined;
    } else if (Array.isArray(id)) {
      parsedId = id.map(
        (i) => Sequence.parseValue(Model.sequenceFor(table).type, i) as string
      );
    } else {
      parsedId = Sequence.parseValue(
        Model.sequenceFor(table).type,
        id
      ) as string;
    }
    await this.observerHandler.updateObservers(
      table,
      event,
      parsedId!,
      ...ctxArgs
    );
  }

  /**
   * Decodes a Uint8Array into a string using the TextDecoder.
   *
   * @param data - The Uint8Array to decode.
   * @returns The decoded string.
   */
  decode(data: Uint8Array): string {
    return FabricERC20ClientRepository.decoder.decode(data);
  }

  constructor(adapter?: FabricClientAdapter) {
    super(adapter, ERC20Wallet);
  }

  /**
   * Retrieves the name of the ERC20 token.
   *
   * @description
   * This function interacts with the blockchain network to fetch the name of the ERC20 token.
   * It calls the "TokenName" transaction on the smart contract and decodes the returned data.
   *
   * @returns {Promise<string>} A promise that resolves with the name of the ERC20 token.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async tokenName(): Promise<string> {
    const name = await this.adapter.evaluateTransaction("TokenName");
    return this.decode(name);
  }

  /**
   * Retrieves the symbol of the ERC20 token.
   *
   * This function interacts with the blockchain network to fetch the symbol of the ERC20 token.
   * It calls the "Symbol" transaction on the smart contract and decodes the returned data.
   *
   * @returns {Promise<string>} A promise that resolves with the symbol of the ERC20 token.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async symbol(): Promise<string> {
    const symbol = await this.adapter.evaluateTransaction("Symbol");
    return this.decode(symbol);
  }

  /**
   * Retrieves the number of decimal places for the ERC20 token.
   *
   * This function interacts with the blockchain network to fetch the number of decimal places for the ERC20 token.
   * It calls the "Decimals" transaction on the smart contract and decodes the returned data.
   *
   * @returns {Promise<number>} A promise that resolves with the number of decimal places for the ERC20 token.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async decimals(): Promise<number> {
    const decimals = await this.adapter.evaluateTransaction("Decimals");
    return Number(this.decode(decimals));
  }

  /**
   * Retrieves the total supply of the ERC20 token.
   *
   * This function interacts with the blockchain network to fetch the total supply of the ERC20 token.
   * It calls the "TotalSupply" transaction on the smart contract and decodes the returned data.
   *
   * @returns {Promise<number>} A promise that resolves with the total supply of the ERC20 token.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async totalSupply(): Promise<number> {
    const total = await this.adapter.evaluateTransaction("TotalSupply");
    return Number(this.decode(total));
  }

  /**
   * Retrieves the balance of the ERC20 token for a specified owner.
   *
   * @description
   * This function interacts with the blockchain network to fetch the balance of the ERC20 token for a given owner.
   * It calls the "BalanceOf" transaction on the smart contract with the provided owner's address as a parameter.
   * The returned data is then decoded and converted to a number.
   *
   * @param owner - The address of the ERC20 token owner.
   *
   * @returns {Promise<number>} A promise that resolves with the balance of the ERC20 token for the specified owner.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async balanceOf(owner: string): Promise<number> {
    const balance = await this.adapter.evaluateTransaction("BalanceOf", [
      owner,
    ]);
    return Number(this.decode(balance));
  }

  /**
   * Transfers a specified amount of ERC20 tokens to a recipient.
   *
   * @description
   * This function interacts with the blockchain network to transfer a specified amount of ERC20 tokens to a recipient.
   * It calls the "Transfer" transaction on the smart contract with the recipient's address and the transfer amount as parameters.
   * The returned data is then decoded and checked to determine if the transfer was successful.
   *
   * @param to - The address of the recipient.
   * @param value - The amount of ERC20 tokens to transfer.
   *
   * @returns {Promise<boolean>} A promise that resolves with `true` if the transfer was successful, and `false` otherwise.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async transfer(to: string, value: number): Promise<boolean> {
    const transferred = await this.adapter.submitTransaction("Transfer", [
      to,
      value.toString(),
    ]);
    return this.decode(transferred) === "true" ? true : false;
  }

  /**
   * Transfers a specified amount of ERC20 tokens from one account to another.
   *
   * @description
   * This function interacts with the blockchain network to transfer a specified amount of ERC20 tokens from one account to another.
   * For this transfer to work the spender ( account that will trigger this function ) need to have the value approved as an allowance by the sender.
   * It calls the "TransferFrom" transaction on the smart contract with the sender's address, recipient's address, and the transfer amount as parameters.
   * The returned data is then decoded and checked to determine if the transfer was successful.
   *
   * @param from - The address of the sender.
   * @param to - The address of the recipient.
   * @param value - The amount of ERC20 tokens to transfer.
   *
   * @returns {Promise<boolean>} A promise that resolves with `true` if the transfer was successful, and `false` otherwise.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async transferFrom(
    from: string,
    to: string,
    value: number
  ): Promise<boolean> {
    const transferred = await this.adapter.submitTransaction("TransferFrom", [
      from,
      to,
      value.toString(),
    ]);

    return this.decode(transferred) === "true" ? true : false;
  }

  /**
   * Approves a specified amount of ERC20 tokens to be spent by a specified spender.
   *
   * This function interacts with the blockchain network to approve a specified amount of ERC20 tokens to be spent by a specified spender.
   * It calls the "Approve" transaction on the smart contract with the spender's address and the approval amount as parameters.
   * The returned data is then decoded and checked to determine if the approval was successful.
   *
   * @param spender - The address of the spender.
   * @param value - The amount of ERC20 tokens to approve for the spender.
   *
   * @returns {Promise<boolean>} A promise that resolves with `true` if the approval was successful, and `false` otherwise.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async approve(spender: string, value: number): Promise<boolean> {
    const approved = await this.adapter.submitTransaction("Approve", [
      spender,
      value.toString(),
    ]);
    return this.decode(approved) === "true" ? true : false;
  }

  /**
   * Retrieves the allowance of ERC20 tokens that the specified owner has approved for a spender.
   *
   * @description
   * This function interacts with the blockchain network to fetch the allowance of ERC20 tokens that the specified owner has approved for a spender.
   * It calls the "Allowance" transaction on the smart contract with the owner's address and the spender's address as parameters.
   * The returned data is then decoded and converted to a number.
   *
   * @param owner - The address of the ERC20 token owner.
   * @param spender - The address of the spender.
   *
   * @returns {Promise<number>} A promise that resolves with the allowance of ERC20 tokens that the specified owner has approved for the spender.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async allowance(owner: string, spender: string): Promise<number> {
    const allowance = await this.adapter.submitTransaction("Allowance", [
      owner,
      spender,
    ]);
    return Number(this.decode(allowance));
  }

  /**
   * Initializes the ERC20 contract with the provided token information.
   *
   * @description
   * This function interacts with the blockchain network to initialize the ERC20 contract with the given token information.
   * It calls the "Initialize" transaction on the smart contract with the serialized token data as a parameter.
   * The returned data is then decoded and checked to determine if the initialization was successful.
   *
   * @param token - The ERC20 token information to initialize the contract with.
   *
   * @returns {Promise<boolean>} A promise that resolves with `true` if the initialization was successful, and `false` otherwise.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async initialize(token: ERC20Token): Promise<boolean> {
    const initiliazed = await this.adapter.submitTransaction("Initialize", [
      FabricERC20ClientRepository.serializer.serialize(token),
    ]);

    return this.decode(initiliazed) === "true" ? true : false;
  }

  /**
   * Checks if the ERC20 contract has been initialized.
   *
   * This function interacts with the blockchain network to verify if the ERC20 contract has been initialized.
   * It calls the "CheckInitialized" transaction on the smart contract, which does not require any parameters.
   *
   * @returns {Promise<void>} A promise that resolves when the initialization check is completed.
   *
   * @throws {Error} If the transaction fails.
   */
  async checkInitialized(): Promise<void> {
    await this.adapter.evaluateTransaction("CheckInitialized");
  }

  /**
   * Mints a specified amount of ERC20 tokens.
   *
   * @description
   * This function interacts with the blockchain network to mint a specified amount of ERC20 tokens.
   * It calls the "Mint" transaction on the smart contract with the minting amount as a parameter.
   * The function does not return any value, but it updates the minter's number of tokens.
   *
   * @param amount - The amount of ERC20 tokens to mint.
   *
   * @returns {Promise<void>} A promise that resolves when the minting process is completed.
   *
   * @throws {Error} If the transaction fails.
   */
  async mint(amount: number): Promise<void> {
    await this.adapter.submitTransaction("Mint", [amount.toString()]);
  }

  /**
   * Burns a specified amount of ERC20 tokens from the minter's account.
   *
   * This function interacts with the blockchain network to burn a specified amount of ERC20 tokens.
   * It calls the "Burn" transaction on the smart contract with the burning amount as a parameter.
   * The function does not return any value, but it decreases the minter's number of tokens.
   *
   * @param amount - The amount of ERC20 tokens to burn.
   *
   * @returns {Promise<void>} A promise that resolves when the burning process is completed.
   *
   * @throws {Error} If the transaction fails.
   */
  async burn(amount: number): Promise<void> {
    await this.adapter.submitTransaction("Burn", [amount.toString()]);
  }

  /**
   * Burns a specified amount of ERC20 tokens from a specified account.
   *
   * This function interacts with the blockchain network to burn a specified amount of ERC20 tokens from a given account.
   * It calls the "BurnFrom" transaction on the smart contract with the account's address and the burning amount as parameters.
   * The function does not return any value, but it decreases the specified account's number of tokens.
   *
   * @param account - The address of the account from which to burn the ERC20 tokens.
   * @param amount - The amount of ERC20 tokens to burn.
   *
   * @returns {Promise<void>} A promise that resolves when the burning process is completed.
   *
   * @throws {Error} If the transaction fails.
   */
  async burnFrom(account: string, amount: number): Promise<void> {
    await this.adapter.submitTransaction("BurnFrom", [
      account,
      amount.toString(),
    ]);
  }

  /**
   * Retrieves the balance of ERC20 tokens associated with the client's account.
   *
   * This function interacts with the blockchain network to fetch the balance of ERC20 tokens associated with the client's account.
   * It calls the "ClientAccountBalance" transaction on the smart contract, which does not require any parameters.
   * The returned data is then decoded and converted to a number.
   *
   * @returns {Promise<number>} A promise that resolves with the balance of ERC20 tokens associated with the client's account.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async clientAccountBalance(): Promise<number> {
    const serializedAccountBalance = await this.adapter.evaluateTransaction(
      "ClientAccountBalance"
    );

    return Number(this.decode(serializedAccountBalance));
  }

  /**
   * Retrieves the client's account ID from the blockchain network.
   *
   * This function interacts with the blockchain network to fetch the client's account ID.
   * It calls the "ClientAccountID" transaction on the smart contract, which does not require any parameters.
   * The returned data is then decoded and returned as a string.
   *
   * @returns {Promise<string>} A promise that resolves with the client's account ID.
   *
   * @throws {Error} If the transaction fails or the decoding process fails.
   */
  async clientAccountID(): Promise<string> {
    const clientAccountID =
      await this.adapter.evaluateTransaction("ClientAccountID");

    return this.decode(clientAccountID);
  }
}