/ scripts / update-tokens-utils.ts
update-tokens-utils.ts
  1  import { RawTokenJSON, ValidatedTokenJSON, NormalizedTokenJSON } from './types/TokensJson';
  2  import { Token } from '../shared/types/network';
  3  interface StrIdx<T> {
  4    [key: string]: T;
  5  }
  6  
  7  function processTokenJson(tokensJson: RawTokenJSON[]): Token[] {
  8    const normalizedTokens = tokensJson.map(validateTokenJSON).map(normalizeTokenJSON);
  9    checkForDuplicateAddresses(normalizedTokens);
 10    return handleDuplicateSymbols(normalizedTokens).map(({ name: _, ...rest }) => rest);
 11  }
 12  
 13  function validateTokenJSON(token: RawTokenJSON): ValidatedTokenJSON {
 14    const isValid = (t: RawTokenJSON): t is ValidatedTokenJSON =>
 15      !!(t.address && (t.decimals || t.decimals === 0) && t.name && t.symbol);
 16  
 17    if (isValid(token)) {
 18      return token;
 19    }
 20    throw Error(`Token failed validation, missing part of schema
 21      Symbol: ${token.symbol}
 22      Name: ${token.name}
 23      Address: ${token.address}
 24      Decimals: ${token.decimals}`);
 25  }
 26  
 27  function normalizeTokenJSON(token: ValidatedTokenJSON): NormalizedTokenJSON {
 28    const { address, decimals, symbol, name } = token;
 29    const t: NormalizedTokenJSON = { address, symbol, decimal: +decimals, name };
 30    return t;
 31  }
 32  
 33  /**
 34   *
 35   * @description Checks for any duplicated addresses and halts the program if so
 36   * @param {NormalizedTokenJSON[]} tokens
 37   */
 38  function checkForDuplicateAddresses(tokens: NormalizedTokenJSON[]) {
 39    const map: StrIdx<boolean> = {};
 40    const errors: string[] = [];
 41    for (const token of tokens) {
 42      const { address } = token;
 43      // We might want to strip hex prefixes here, and make all characters lowercase
 44      if (map[address]) {
 45        errors.push(`Token ${token.symbol} has a duplicate address of ${token.address}`);
 46      }
 47      map[address] = true;
 48    }
 49  
 50    if (errors.length) {
 51      const err = errors.join('\n');
 52      throw Error(err);
 53    }
 54  }
 55  
 56  /**
 57   *
 58   * @description Finds any duplicated names in the fetched token json
 59   * @param {NormalizedTokenJSON[]} tokens
 60   * @returns
 61   */
 62  function getDuplicatedNames(tokens: NormalizedTokenJSON[]) {
 63    const checkedNames: StrIdx<boolean> = {};
 64    const duplicatedNames: StrIdx<boolean> = {};
 65    for (const token of tokens) {
 66      const { name } = token;
 67      if (checkedNames[name]) {
 68        duplicatedNames[name] = true;
 69      }
 70      checkedNames[name] = true;
 71    }
 72    return duplicatedNames;
 73  }
 74  
 75  /**
 76   *
 77   * @description Handles any tokens with duplicated symbols by placing them in a map with each value being a bucket
 78   * of other tokens with the same symbol, then renaming them appropriately so they do not conflict anymore
 79   * @param {NormalizedTokenJSON[]} tokens
 80   * @returns
 81   */
 82  function handleDuplicateSymbols(tokens: NormalizedTokenJSON[]) {
 83    // start by building a map of symbols => tokens
 84    const map = new Map<string, NormalizedTokenJSON[]>();
 85    for (const token of tokens) {
 86      const { symbol } = token;
 87      const v = map.get(symbol);
 88      if (v) {
 89        map.set(symbol, [...v, token]);
 90      } else {
 91        map.set(symbol, [token]);
 92      }
 93    }
 94    const duplicatedNames = getDuplicatedNames(tokens);
 95    const dedupedTokens: NormalizedTokenJSON[] = [];
 96    map.forEach(tokenBucket =>
 97      dedupedTokens.push(...renameSymbolCollisions(tokenBucket, duplicatedNames))
 98    );
 99    return dedupedTokens;
100  }
101  
102  /**
103   *
104   * @description Any token collisions are handled in this manner:
105   * 1) If the name isnt a duplicate, the token symbol is prefixed with the token name
106   * 2) if it is a duplicate, then we simply use the token index + 1 (so we dont start at 0)
107   * @param {NormalizedTokenJSON[]} tokens
108   * @param {StrIdx<boolean>} duplicatedNames
109   * @returns
110   */
111  function renameSymbolCollisions(tokens: NormalizedTokenJSON[], duplicatedNames: StrIdx<boolean>) {
112    const renamedTokens: NormalizedTokenJSON[] = [];
113    if (tokens.length === 1) {
114      return tokens;
115    }
116  
117    return tokens.reduce((prev, curr, idx) => {
118      const newName = `${curr.symbol} (${duplicatedNames[curr.name] ? idx + 1 : curr.name})`;
119      const tokenToInsert: NormalizedTokenJSON = {
120        ...curr,
121        symbol: newName
122      };
123      console.warn(`WARN: "${curr.symbol}" has a duplicate symbol, renaming to "${newName}"`);
124      return [...prev, tokenToInsert];
125    }, renamedTokens);
126  }
127  
128  module.exports = { processTokenJson };