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 };