129 lines
4.1 KiB
TypeScript
129 lines
4.1 KiB
TypeScript
import { RawTokenJSON, ValidatedTokenJSON, NormalizedTokenJSON } from './types/TokensJson';
|
|
import { Token } from '../shared/types/network';
|
|
interface StrIdx<T> {
|
|
[key: string]: T;
|
|
}
|
|
|
|
function processTokenJson(tokensJson: RawTokenJSON[]): Token[] {
|
|
const normalizedTokens = tokensJson.map(validateTokenJSON).map(normalizeTokenJSON);
|
|
checkForDuplicateAddresses(normalizedTokens);
|
|
return handleDuplicateSymbols(normalizedTokens).map(({ name: _, ...rest }) => rest);
|
|
}
|
|
|
|
function validateTokenJSON(token: RawTokenJSON): ValidatedTokenJSON {
|
|
const isValid = (t: RawTokenJSON): t is ValidatedTokenJSON =>
|
|
!!(t.address && (t.decimals || t.decimals === 0) && t.name && t.symbol);
|
|
|
|
if (isValid(token)) {
|
|
return token;
|
|
}
|
|
throw Error(`Token failed validation, missing part of schema
|
|
Symbol: ${token.symbol}
|
|
Name: ${token.name}
|
|
Address: ${token.address}
|
|
Decimals: ${token.decimals}`);
|
|
}
|
|
|
|
function normalizeTokenJSON(token: ValidatedTokenJSON): NormalizedTokenJSON {
|
|
const { address, decimals, symbol, name } = token;
|
|
const t: NormalizedTokenJSON = { address, symbol, decimal: +decimals, name };
|
|
return t;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @description Checks for any duplicated addresses and halts the program if so
|
|
* @param {NormalizedTokenJSON[]} tokens
|
|
*/
|
|
function checkForDuplicateAddresses(tokens: NormalizedTokenJSON[]) {
|
|
const map: StrIdx<boolean> = {};
|
|
const errors: string[] = [];
|
|
for (const token of tokens) {
|
|
const { address } = token;
|
|
// We might want to strip hex prefixes here, and make all characters lowercase
|
|
if (map[address]) {
|
|
errors.push(`Token ${token.symbol} has a duplicate address of ${token.address}`);
|
|
}
|
|
map[address] = true;
|
|
}
|
|
|
|
if (errors.length) {
|
|
const err = errors.join('\n');
|
|
throw Error(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @description Finds any duplicated names in the fetched token json
|
|
* @param {NormalizedTokenJSON[]} tokens
|
|
* @returns
|
|
*/
|
|
function getDuplicatedNames(tokens: NormalizedTokenJSON[]) {
|
|
const checkedNames: StrIdx<boolean> = {};
|
|
const duplicatedNames: StrIdx<boolean> = {};
|
|
for (const token of tokens) {
|
|
const { name } = token;
|
|
if (checkedNames[name]) {
|
|
duplicatedNames[name] = true;
|
|
}
|
|
checkedNames[name] = true;
|
|
}
|
|
return duplicatedNames;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @description Handles any tokens with duplicated symbols by placing them in a map with each value being a bucket
|
|
* of other tokens with the same symbol, then renaming them appropriately so they do not conflict anymore
|
|
* @param {NormalizedTokenJSON[]} tokens
|
|
* @returns
|
|
*/
|
|
function handleDuplicateSymbols(tokens: NormalizedTokenJSON[]) {
|
|
// start by building a map of symbols => tokens
|
|
const map = new Map<string, NormalizedTokenJSON[]>();
|
|
for (const token of tokens) {
|
|
const { symbol } = token;
|
|
const v = map.get(symbol);
|
|
if (v) {
|
|
map.set(symbol, [...v, token]);
|
|
} else {
|
|
map.set(symbol, [token]);
|
|
}
|
|
}
|
|
const duplicatedNames = getDuplicatedNames(tokens);
|
|
const dedupedTokens: NormalizedTokenJSON[] = [];
|
|
map.forEach(tokenBucket =>
|
|
dedupedTokens.push(...renameSymbolCollisions(tokenBucket, duplicatedNames))
|
|
);
|
|
return dedupedTokens;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @description Any token collisions are handled in this manner:
|
|
* 1) If the name isnt a duplicate, the token symbol is prefixed with the token name
|
|
* 2) if it is a duplicate, then we simply use the token index + 1 (so we dont start at 0)
|
|
* @param {NormalizedTokenJSON[]} tokens
|
|
* @param {StrIdx<boolean>} duplicatedNames
|
|
* @returns
|
|
*/
|
|
function renameSymbolCollisions(tokens: NormalizedTokenJSON[], duplicatedNames: StrIdx<boolean>) {
|
|
const renamedTokens: NormalizedTokenJSON[] = [];
|
|
if (tokens.length === 1) {
|
|
return tokens;
|
|
}
|
|
|
|
return tokens.reduce((prev, curr, idx) => {
|
|
const newName = `${curr.symbol} (${duplicatedNames[curr.name] ? idx + 1 : curr.name})`;
|
|
const tokenToInsert: NormalizedTokenJSON = {
|
|
...curr,
|
|
symbol: newName
|
|
};
|
|
console.warn(`WARN: "${curr.symbol}" has a duplicate symbol, renaming to "${newName}"`);
|
|
return [...prev, tokenToInsert];
|
|
}, renamedTokens);
|
|
}
|
|
|
|
module.exports = { processTokenJson };
|