mirror of
				https://github.com/Instadapp/trustwallet-assets.git
				synced 2024-07-29 22:37:31 +00:00 
			
		
		
		
	 af570af4e1
			
		
	
	
		af570af4e1
		
			
		
	
	
	
	
		
			
			* add_RONIN Added Ronin Blockchain and assets. * Add RONIN as valid token type * Add ronin explorer support * Fixes (sanity and consistency, auto) Co-authored-by: catenocrypt <13562139+catenocrypt@users.noreply.github.com> Co-authored-by: catenocrypt <catenocrypt@users.noreply.github.com>
		
			
				
	
	
		
			494 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			494 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {
 | |
|     allChains,
 | |
|     getChainAssetsList,
 | |
|     getChainAssetsPath,
 | |
|     getChainAssetInfoPath,
 | |
|     getChainInfoPath,
 | |
|     getChainCoinInfoPath
 | |
| } from "./repo-structure";
 | |
| import { isPathExistsSync } from "./filesystem";
 | |
| import { arrayDiff } from "./types";
 | |
| import { isValidJSON, readJsonFile, writeJsonFile } from "../generic/json";
 | |
| import { ActionInterface, CheckStepInterface } from "../generic/interface";
 | |
| import { CoinType } from "@trustwallet/wallet-core";
 | |
| import { isValidStatusValue } from "../generic/status-values";
 | |
| import { isValidTagValues } from "../generic/tag-values";
 | |
| import * as bluebird from "bluebird";
 | |
| 
 | |
| const requiredKeysCoin = ["name", "type", "symbol", "decimals", "description", "website", "explorer", "status"];
 | |
| const requiredKeysToken = [...requiredKeysCoin, "id"];
 | |
| 
 | |
| // Supported keys in links, and their mandatory prefix
 | |
| const linksKeys = {
 | |
|     //"explorer": "",
 | |
|     "github": "https://github.com/",
 | |
|     "whitepaper": "",
 | |
|     "twitter": "https://twitter.com/",
 | |
|     "telegram": "https://t.me/",
 | |
|     "telegram_news": "https://t.me/", // read-only announcement channel
 | |
|     "medium": "", // url contains 'medium.com'
 | |
|     "discord": "https://discord.com/",
 | |
|     "reddit": "https://reddit.com/",
 | |
|     "facebook": "https://facebook.com/",
 | |
|     "youtube": "https://youtube.com/",
 | |
|     "coinmarketcap": "https://coinmarketcap.com/",
 | |
|     "coingecko": "https://coingecko.com/",
 | |
|     "blog": "", // blog, other than medium
 | |
|     "forum": "", // community site
 | |
|     "docs": "",
 | |
|     "source_code": "" // other than github
 | |
| };
 | |
| const linksKeysString = Object.keys(linksKeys).reduce(function (agg, item) { return agg + item + ","; }, '');
 | |
| const linksMediumContains = 'medium.com';
 | |
| 
 | |
| function isAssetInfoHasAllKeys(info: unknown, path: string, isCoin: boolean): [boolean, string] {
 | |
|     const infoKeys = Object.keys(info);
 | |
|     const requiredKeys = isCoin ? requiredKeysCoin : requiredKeysToken;
 | |
| 
 | |
|     const hasAllKeys = requiredKeys.every(k => Object.prototype.hasOwnProperty.call(info, k));
 | |
| 
 | |
|     return [hasAllKeys, `Info at path '${path}' missing next key(s): ${arrayDiff(requiredKeys, infoKeys)}`];
 | |
| }
 | |
| 
 | |
| // return error, warning, and fixed into if applicable
 | |
| function isAssetInfoValid(info: unknown, path: string, address: string, chain: string, isCoin: boolean, checkOnly: boolean): [string, string, unknown?] {
 | |
|     let fixedInfo: unknown|null = null;
 | |
|     const isKeys1CorrectType = 
 | |
|         typeof info['name'] === "string" && info['name'] !== "" &&
 | |
|         typeof info['type'] === "string" && info['type'] !== "" &&
 | |
|         typeof info['symbol'] === "string" && info['symbol'] !== "" &&
 | |
|         typeof info['decimals'] === "number" && //(info['description'] === "-" || info['decimals'] !== 0) &&
 | |
|         typeof info['status'] === "string" && info['status'] !== ""
 | |
|         ;
 | |
|     if (!isKeys1CorrectType) {
 | |
|         return [`Field missing or invalid; name '${info['name']}' type '${info['type']}' symbol '${info['symbol']}' decimals '${info['decimals']}' ${path}`, "", fixedInfo];
 | |
|     }
 | |
|     if (!isCoin) {
 | |
|         const isIdKeyCorrectType = typeof info['id'] === "string" && info['id'] !== "";
 | |
|         if (!isIdKeyCorrectType) {
 | |
|             return [`Field 'id' missing or invalid, '${info['id']}' ${path}`, "", fixedInfo];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // type
 | |
|     if (!isCoin) { // token
 | |
|         if (chainFromAssetType(info['type'].toUpperCase()) !== chain ) {
 | |
|             return [`Incorrect value for type '${info['type']}' '${chain}' ${path}`, "", fixedInfo];
 | |
|         }
 | |
|         if (info['type'] !== info['type'].toUpperCase()) {
 | |
|             // type is correct value, but casing is wrong, fix
 | |
|             if (checkOnly) {
 | |
|                 return [`Type should be ALLCAPS '${info['type'].toUpperCase()}' instead of '${info['type']}' '${chain}' ${path}`, "", fixedInfo];
 | |
|             }
 | |
|             // fix
 | |
|             if (!fixedInfo) { fixedInfo = info; }
 | |
|             fixedInfo['type'] = info['type'].toUpperCase();
 | |
|         }
 | |
|     } else { // coin
 | |
|          const expectedType = 'coin';
 | |
|         if (info['type'] !== expectedType) {
 | |
|             if (checkOnly) {
 | |
|                 return [`Incorrect value for type '${info['type']}', expected '${expectedType}' '${chain}' ${path}`, "", fixedInfo];
 | |
|             }
 | |
|             // fix
 | |
|             if (!fixedInfo) { fixedInfo = info; }
 | |
|             fixedInfo['type'] = expectedType;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     if (!isCoin) {
 | |
|         // id, should match address
 | |
|         if (info['id'] != address) {
 | |
|         if (checkOnly) {
 | |
|                 if (info['id'].toUpperCase() != address.toUpperCase()) {
 | |
|                     return [`Incorrect value for id '${info['id']}' '${chain}' ${path}`, "", fixedInfo];
 | |
|                 }
 | |
|                 // is is correct value, but casing is wrong
 | |
|                 return [`Wrong casing for id '${info['id']}' '${chain}' ${path}`, "", fixedInfo];
 | |
|         }
 | |
|         // fix
 | |
|         if (!fixedInfo) { fixedInfo = info; }
 | |
|             fixedInfo['id'] = address;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // extra checks on decimals
 | |
|     if (info['decimals'] > 30 || info['decimals'] < 0) {
 | |
|         return [`Incorrect value for decimals '${info['decimals']}' '${chain}' ${path}`, "", fixedInfo];
 | |
|     }
 | |
|     if (info['type'] === 'BEP2' && info['decimals'] != 8) {
 | |
|         return [`Incorrect value for decimals, BEP2 tokens have 8 decimals. '${info['decimals']}' '${chain}' ${path}`, "", fixedInfo];
 | |
|     }
 | |
| 
 | |
|     // status
 | |
|     if (!isValidStatusValue(info['status'])) {
 | |
|         return [`Invalid value for status field, '${info['status']}'`, "", fixedInfo];
 | |
|     }
 | |
| 
 | |
|     // tags
 | |
|     if (info['tags']) {
 | |
|         if (!isValidTagValues(info['tags'])) {
 | |
|             return [`Invalid tags, '${info['tags']}'`, "", fixedInfo];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     const isKeys2CorrectType = 
 | |
|         typeof info['description'] === "string" && info['description'] !== "" &&
 | |
|         // website should be set (exception description='-' marks empty infos)
 | |
|         typeof info['website'] === "string" && (info['description'] === "-" || info['website'] !== "") &&
 | |
|         typeof info['explorer'] === "string" && info['explorer'] != "";
 | |
|     if (!isKeys2CorrectType) {
 | |
|         return [`Check keys2 '${info['description']}' '${info['website']}' '${info['explorer']}' ${path}`, "", fixedInfo];
 | |
|     }
 | |
| 
 | |
|     if (info['description'].length > 500) {
 | |
|         const msg = `Description too long, ${info['description'].length}, ${path}`;
 | |
|         return [msg, "", fixedInfo];
 | |
|     }
 | |
| 
 | |
|     return ["", "", fixedInfo];
 | |
| }
 | |
| 
 | |
| // return error, warning
 | |
| function isInfoLinksValid(links: unknown, path: string, address: string, chain: string): [string, string] {
 | |
|     if (!Array.isArray(links)) {
 | |
|         return [`Links must be an array '${JSON.stringify(links)}' '${path}' '${address}' '${chain}'`, ""];
 | |
|     }
 | |
|     for (let idx = 0; idx < links.length; idx++) {
 | |
|         const f = links[idx];
 | |
|         const fname = f['name'];
 | |
|         if (!fname) {
 | |
|             return [`Field name missing '${JSON.stringify(f)}'`, ""];
 | |
|         }
 | |
|         const furl = f['url'];
 | |
|         if (!fname) {
 | |
|             return [`Field url missing '${JSON.stringify(f)}'`, ""];
 | |
|         }
 | |
|         // Check there are no other fields
 | |
|         for (const f2 in f) {
 | |
|             if (f2 !== 'name' && f2 !== 'url') {
 | |
|                 return [`Invalid field '${f2}' in links '${JSON.stringify(f)}', path ${path}`, ""];
 | |
|             }
 | |
|         }
 | |
|         if (!Object.prototype.hasOwnProperty.call(linksKeys, fname)) {
 | |
|             return [`Not supported field in links '${fname}'.  Supported keys: ${linksKeysString}`, ""];
 | |
|         }
 | |
|         const prefix = linksKeys[fname];
 | |
|         if (prefix) {
 | |
|             if (!furl.startsWith(prefix)) {
 | |
|                 return [`Links field '${fname}': '${furl}' must start with '${prefix}'.  Supported keys: ${linksKeysString}`, ""];
 | |
|             }
 | |
|         }
 | |
|         if (!furl.startsWith('https://')) {
 | |
|             return [`Links field '${fname}': '${furl}' must start with 'https://'.  Supported keys: ${linksKeysString}`, ""];
 | |
|         }
 | |
|         // special handling for medium
 | |
|         if (fname === 'medium') {
 | |
|             if (!furl.includes(linksMediumContains)) {
 | |
|                 return [`Links field '${fname}': '${furl}' must include '${linksMediumContains}'.  Supported keys: ${linksKeysString}`, ""];
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return ["", ""];
 | |
| }
 | |
| 
 | |
| export function chainFromAssetType(type: string): string {
 | |
|     switch (type) {
 | |
|         case "ERC20": return "ethereum";
 | |
|         case "BEP2": return "binance";
 | |
|         case "BEP20": return "smartchain";
 | |
|         case "ETC20": return "classic";
 | |
|         case "TRC10":
 | |
|         case "TRC20":
 | |
|             return "tron";
 | |
|         case "WAN20": return "wanchain";
 | |
|         case "TRC21": return "tomochain";
 | |
|         case "TT20": return "thundertoken";
 | |
|         case "SPL": return "solana";
 | |
|         case "EOS": return "eos";
 | |
|         case "GO20": return "gochain";
 | |
|         case "KAVA": return "kava";
 | |
|         case "NEP5": return "neo";
 | |
|         case "NRC20": return "nuls";
 | |
|         case "VET": return "vechain";
 | |
|         case "ONTOLOGY": return "ontology";
 | |
|         case "THETA": return "theta";
 | |
|         case "TOMO": return "tomochain";
 | |
|         case "XDAI": return "xdai";
 | |
|         case "WAVES": return "waves";
 | |
|         case "POA": return "poa";
 | |
|         case "POLYGON": return "polygon";
 | |
|         case "OPTIMISM": return "optimism";
 | |
|         case "AVALANCHE": return "avalanchec";
 | |
|         case "ARBITRUM": return "arbitrum";
 | |
|         case "FANTOM": return "fantom";
 | |
|         case "TERRA": return "terra";
 | |
|         case "RONIN": return "ronin";
 | |
|         default: return "";
 | |
|     }
 | |
| }
 | |
| 
 | |
| export function explorerUrl(chain: string, contract: string): string {
 | |
|     if (contract) {
 | |
|         switch (chain.toLowerCase()) {
 | |
|             case CoinType.name(CoinType.ethereum).toLowerCase():
 | |
|                 return `https://etherscan.io/token/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.tron).toLowerCase():
 | |
|                 if (contract.startsWith("10")) {
 | |
|                     // trc10
 | |
|                     return `https://tronscan.io/#/token/${contract}`;
 | |
|                 }
 | |
|                 // trc20
 | |
|                 return `https://tronscan.io/#/token20/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.binance).toLowerCase():
 | |
|                 return `https://explorer.binance.org/asset/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.smartchain).toLowerCase():
 | |
|             case "smartchain":
 | |
|                 return `https://bscscan.com/token/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.eos).toLowerCase():
 | |
|                 return `https://bloks.io/account/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.neo).toLowerCase():
 | |
|                 return `https://neo.tokenview.com/en/token/0x${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.nuls).toLowerCase():
 | |
|                 return `https://nulscan.io/token/info?contractAddress=${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.wanchain).toLowerCase():
 | |
|                 return `https://www.wanscan.org/token/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.solana).toLowerCase():
 | |
|                 return `https://explorer.solana.com/address/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.tomochain).toLowerCase():
 | |
|                 return `https://scan.tomochain.com/address/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.kava).toLowerCase():
 | |
|                 return "https://www.mintscan.io/kava";
 | |
| 
 | |
|             case CoinType.name(CoinType.ontology).toLowerCase():
 | |
|                 return "https://explorer.ont.io";
 | |
| 
 | |
|             case CoinType.name(CoinType.gochain).toLowerCase():
 | |
|                 return `https://explorer.gochain.io/addr/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.theta).toLowerCase():
 | |
|                 return 'https://explorer.thetatoken.org/';
 | |
| 
 | |
|             case CoinType.name(CoinType.thundertoken).toLowerCase():
 | |
|             case "thundertoken":
 | |
|                 return `https://viewblock.io/thundercore/address/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.classic).toLowerCase():
 | |
|             case "classic":
 | |
|                 return `https://blockscout.com/etc/mainnet/tokens/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.vechain).toLowerCase():
 | |
|             case "vechain":
 | |
|                 return `https://explore.vechain.org/accounts/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.waves).toLowerCase():
 | |
|                 return `https://wavesexplorer.com/assets/${contract}`;
 | |
| 
 | |
|             case "xdai":
 | |
|                 return `https://blockscout.com/xdai/mainnet/tokens/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.poa).toLowerCase():
 | |
|             case "poa":
 | |
|                 return `https://blockscout.com/poa/core/tokens/${contract}`;
 | |
| 
 | |
|             case CoinType.name(CoinType.polygon).toLowerCase():
 | |
|             case "polygon":
 | |
|                 return `https://polygonscan.com/token/${contract}`;
 | |
|             case "optimism":
 | |
|                 return `https://optimistic.etherscan.io/address/${contract}`;
 | |
|             case "avalanchec":
 | |
|                 return `https://cchain.explorer.avax.network/address/${contract}`
 | |
|             case "arbitrum":
 | |
|                 return `https://arbiscan.io/token/${contract}`
 | |
|             case "fantom":
 | |
|                 return `https://ftmscan.com/token/${contract}`
 | |
|             case "terra":
 | |
|                 return `https://finder.terra.money/columbus-4/${contract}`
 | |
|             case "ronin":
 | |
|                 return `https://explorer.roninchain.com/token/ronin:${contract}`
 | |
|             }
 | |
|     }
 | |
|     return "";
 | |
| }
 | |
| 
 | |
| function explorerUrlAlternatives(chain: string, contract: string, name: string): string[] {
 | |
|     const altUrls: string[] = [];
 | |
|     if (name) {
 | |
|         const nameNorm = name.toLowerCase().replace(' ', '').replace(')', '').replace('(', '');
 | |
|         if (chain.toLowerCase() == CoinType.name(CoinType.ethereum)) {
 | |
|             altUrls.push(`https://etherscan.io/token/${nameNorm}`);
 | |
|         }
 | |
|         altUrls.push(`https://explorer.${nameNorm}.io`);
 | |
|         altUrls.push(`https://scan.${nameNorm}.io`);
 | |
|     }
 | |
|     return altUrls;
 | |
| }
 | |
| 
 | |
| // Check the an assets's info.json; for errors/warning.  Also does fixes in certain cases
 | |
| function isAssetInfoOK(chain: string, isCoin: boolean, address: string, errors: string[], warnings: string[], checkOnly: boolean): void {
 | |
|     const assetInfoPath = isCoin ? getChainCoinInfoPath(chain) : getChainAssetInfoPath(chain, address);
 | |
| 
 | |
|     if (!isPathExistsSync(assetInfoPath)) {
 | |
|         // Info file doesn't exist, no need to check
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (!isValidJSON(assetInfoPath)) {
 | |
|         console.log(`JSON at path: '${assetInfoPath}' is invalid`);
 | |
|         errors.push(`JSON at path: '${assetInfoPath}' is invalid`);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     let info: unknown = readJsonFile(assetInfoPath);
 | |
|     let fixedInfo: unknown|null = null;
 | |
| 
 | |
|     const [hasAllKeys, msg1] = isAssetInfoHasAllKeys(info, assetInfoPath, isCoin);
 | |
|     if (!hasAllKeys) {
 | |
|         console.log(msg1);
 | |
|         errors.push(msg1);
 | |
|     }
 | |
| 
 | |
|     const [err2, warn2, fixedInfo2] = isAssetInfoValid(info, assetInfoPath, address, chain, isCoin, checkOnly);
 | |
|     if (err2) {
 | |
|         errors.push(err2);
 | |
|     }
 | |
|     if (warn2) {
 | |
|         warnings.push(warn2);
 | |
|     }
 | |
|     if (fixedInfo2 && !checkOnly) {
 | |
|         info = fixedInfo2;
 | |
|         fixedInfo = fixedInfo2;
 | |
|     }
 | |
| 
 | |
|     if (Object.prototype.hasOwnProperty.call(info, 'links') && info['links']) {
 | |
|         const [err3, warn3] = isInfoLinksValid(info['links'], assetInfoPath, address, chain);
 | |
|         if (err3) {
 | |
|             errors.push(err3);
 | |
|         }
 | |
|         if (warn3) {
 | |
|             warnings.push(warn3);
 | |
|         }
 | |
|     }
 | |
|     // Fields moved to links section:
 | |
|     ['socials', 'source_code', 'whitepaper', 'white_paper'].forEach(f => {
 | |
|         if (Object.prototype.hasOwnProperty.call(info, f)) {
 | |
|             errors.push(`Field ${f} is no longer used, use 'links' section instead. (${chain} ${address})`);
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     if (!isCoin) {
 | |
|         const explorerExpected = explorerUrl(chain, address);
 | |
|         const hasExplorer = Object.prototype.hasOwnProperty.call(info, 'explorer');
 | |
|         const explorerActual = info['explorer'] || '';
 | |
|         const explorerActualLower = explorerActual.toLowerCase();
 | |
|         const explorerExpectedLower = explorerExpected.toLowerCase();
 | |
|         if (checkOnly) {
 | |
|             if (!hasExplorer) {
 | |
|                 errors.push(`Missing explorer key`);
 | |
|             } else {
 | |
|                 if (explorerActualLower !== explorerExpectedLower && explorerExpected) {
 | |
|                     // doesn't match, check for alternatives
 | |
|                     const explorersAlt = explorerUrlAlternatives(chain, address, info['name']);
 | |
|                     if (explorersAlt && explorersAlt.length > 0) {
 | |
|                         let matchCount = 0;
 | |
|                         explorersAlt.forEach(exp => { if (exp.toLowerCase() == explorerActualLower) { ++matchCount; }});
 | |
|                         if (matchCount == 0) {
 | |
|                             // none matches, this is warning/error
 | |
|                             if (chain.toLowerCase() == CoinType.name(CoinType.ethereum) || chain.toLowerCase() == CoinType.name(CoinType.smartchain)) {
 | |
|                                 errors.push(`Incorrect explorer, ${explorerActual} instead of ${explorerExpected} (${explorersAlt.join(', ')})`);
 | |
|                             } else {
 | |
|                                 warnings.push(`Unexpected explorer, ${explorerActual} instead of ${explorerExpected} (${explorersAlt.join(', ')})`);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             // fix: simply replace with expected (case-only deviation is accepted)
 | |
|             if (explorerActualLower !== explorerExpectedLower) {
 | |
|                 if (!fixedInfo) { fixedInfo = info; }
 | |
|                 fixedInfo['explorer'] = explorerExpected;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (fixedInfo && !checkOnly) {
 | |
|         writeJsonFile(assetInfoPath, fixedInfo);
 | |
|         console.log(`Done fixes to info.json, ${assetInfoPath}`);
 | |
|     }    
 | |
| }
 | |
| 
 | |
| export class AssetInfos implements ActionInterface {
 | |
|     getName(): string { return "Asset Infos"; }
 | |
|     
 | |
|     getSanityChecks(): CheckStepInterface[] {
 | |
|         const steps: CheckStepInterface[] = [];
 | |
|         // tokens info.json's
 | |
|         allChains.forEach(chain => {
 | |
|             if (isPathExistsSync(getChainAssetsPath(chain))) {
 | |
|                 steps.push(
 | |
|                     {
 | |
|                         getName: () => { return `Token info.json's for chain ${chain}`;},
 | |
|                         check: async () => {
 | |
|                             const errors: string[] = [];
 | |
|                             const warnings: string[] = [];
 | |
|                             const assetsList = getChainAssetsList(chain);
 | |
|                             //console.log(`     Found ${assetsList.length} assets for chain ${chain}`);
 | |
|                             await bluebird.each(assetsList, async (address) => {
 | |
|                                 isAssetInfoOK(chain, false, address, errors, warnings, true);
 | |
|                             });
 | |
|                             return [errors, warnings];
 | |
|                         }    
 | |
|                     }
 | |
|                 );
 | |
|             }
 | |
|         });
 | |
|         // coin info.json
 | |
|         steps.push(
 | |
|             {
 | |
|                 getName: () => { return `Coin info.json's`;},
 | |
|                 check: async () => {
 | |
|                     const errors: string[] = [];
 | |
|                     const warnings: string[] = [];
 | |
|                     allChains.forEach(chain => {
 | |
|                         if (isPathExistsSync(getChainInfoPath(chain))) {
 | |
|                             isAssetInfoOK(chain, true, '../info', errors, warnings, true);
 | |
|                         }
 | |
|                     });
 | |
|                     return [errors, warnings];
 | |
|                 }
 | |
|             }
 | |
|         );
 | |
|         return steps;
 | |
|     }
 | |
| 
 | |
|     async consistencyFix(): Promise<void> {
 | |
|         bluebird.each(allChains, async chain => {
 | |
|             // only if there is no assets subfolder
 | |
|             if (isPathExistsSync(getChainAssetsPath(chain))) {
 | |
|                 const errors: string[] = [];
 | |
|                 const warnings: string[] = [];
 | |
|                 const assetsList = getChainAssetsList(chain);
 | |
|                 await bluebird.each(assetsList, async (address) => {
 | |
|                     isAssetInfoOK(chain, false, address, errors, warnings, false);
 | |
|                 });
 | |
|             }
 | |
|             if (isPathExistsSync(getChainInfoPath(chain))) {
 | |
|                 const errors: string[] = [];
 | |
|                 const warnings: string[] = [];
 | |
|                 isAssetInfoOK(chain, true, '[COIN]', errors, warnings, false);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| }
 |