Newer
Older
CrypticOreWallet / src / tenebra / wallets / utils.ts
@BuildTools BuildTools on 9 Jun 2021 3 KB im gay
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of TenebraWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/TenebraWeb2/blob/master/LICENSE.txt
import { useMemo } from "react";

import { useSelector, shallowEqual } from "react-redux";
import { RootState } from "@store";

import { Wallet, WalletNew, WalletMap, WalletFormatName, applyWalletFormat } from ".";
import { makeV2Address } from "@utils/tenebra";

import { localeSort } from "@utils";

/** Finds a wallet in the wallet map by the given Tenebra address. */
export function findWalletByAddress(
  wallets: WalletMap,
  address?: string
): Wallet | null {
  if (!address) return null;

  for (const id in wallets) {
    if (wallets[id].address === address) {
      return wallets[id];
    }
  }

  return null;
}

export type WalletAddressMap = Record<string, Wallet>;

export interface WalletsHookResponse {
  wallets: WalletMap;
  walletAddressMap: WalletAddressMap;

  addressList: string[];
  joinedAddressList: string;
}

/** Hook that fetches the wallets from the Redux store. */
export function useWallets(): WalletsHookResponse {
  const wallets = useSelector((s: RootState) => s.wallets.wallets, shallowEqual);

  const walletAddressMap: WalletAddressMap = {};
  const addressList: string[] = [];

  for (const id in wallets) {
    const wallet = wallets[id];
    const address = wallet.address;

    walletAddressMap[address] = wallet;

    // A cheap address list used for deep comparison. It's totally okay to
    // assume this list will only change when the addresses will change, as
    // since ES2015, object ordering is *basically* consistent:
    // https://stackoverflow.com/a/5525820/1499974
    // https://stackoverflow.com/a/38218582/1499974
    // https://stackoverflow.com/a/23202095/1499974
    addressList.push(address);
  }

  const joinedAddressList = addressList.join(",");

  return { wallets, walletAddressMap, addressList, joinedAddressList };
}

export interface WalletCategoriesHookResponse {
  categories: string[];
  joinedCategoryList: string;
}

export function useWalletCategories(): WalletCategoriesHookResponse {
  const wallets = useSelector((s: RootState) => s.wallets.wallets, shallowEqual);

  const categories = useMemo(() => {
    const cats = [...new Set(Object.values(wallets)
      .filter(w => w.category !== undefined && w.category !== "")
      .map(w => w.category) as string[])];
    localeSort(cats);
    return cats;
  }, [wallets]);

  const joinedCategoryList = categories.join(",");

  return { categories, joinedCategoryList };
}

/** Almost anywhere you'd want to apply a wallet format, you'd also want to
 * calculate the v2 address, so just do both at once! */
export async function calculateAddress(
  addressPrefix: string,
  walletOrFormat: Wallet | WalletNew | WalletFormatName | undefined,
  password: string,
  username?: string
): Promise<{
  privatekey: string;
  address: string;
}> {
  if (walletOrFormat === undefined || typeof walletOrFormat === "string") {
    // We were passed a wallet format (or undefined, which is assumed to be
    // the default format, since it's not a Wallet instance)
    const format: WalletFormatName = walletOrFormat || "tenebrawallet";

    const privatekey = await applyWalletFormat(format || "tenebrawallet", password, username);
    const address = await makeV2Address(addressPrefix, privatekey);
    return { privatekey, address };
  } else {
    // We were passed a Wallet or WalletNew instance
    if (typeof walletOrFormat !== "object" || walletOrFormat === null)
      throw new Error("Missing `walletOrFormat` in `calculateAddress`!");

    const { format, username } = walletOrFormat as WalletNew;

    const privatekey = await applyWalletFormat(format || "tenebrawallet", password, username);
    const address = await makeV2Address(addressPrefix, privatekey);
    return { privatekey, address };
  }
}