mirror of
https://github.com/Instadapp/trustwallet-assets.git
synced 2024-07-29 22:37:31 +00:00
Delete all outdated typescript code (#16752)
This commit is contained in:
parent
1aad5824aa
commit
f49f43f09d
|
|
@ -1,9 +0,0 @@
|
||||||
# don't ever lint node_modules
|
|
||||||
node_modules
|
|
||||||
# don't lint build output (make sure it's set to your correct build folder name)
|
|
||||||
dist
|
|
||||||
# don't lint nyc coverage output
|
|
||||||
coverage
|
|
||||||
|
|
||||||
.eslintrc.js
|
|
||||||
jest.config.js
|
|
||||||
12
.eslintrc.js
12
.eslintrc.js
|
|
@ -1,12 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
plugins: [
|
|
||||||
'@typescript-eslint',
|
|
||||||
],
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
3
.github/workflows/fix.yml
vendored
3
.github/workflows/fix.yml
vendored
|
|
@ -37,9 +37,6 @@ jobs:
|
||||||
if: github.repository_owner == 'trustwallet'
|
if: github.repository_owner == 'trustwallet'
|
||||||
run: go run ./cmd/main.go --config=./.github/assets.config.yaml --script=fixer
|
run: go run ./cmd/main.go --config=./.github/assets.config.yaml --script=fixer
|
||||||
|
|
||||||
# - name: Debug
|
|
||||||
# run: echo "GITHUB_REF " $GITHUB_REF " GITHUB_BASE_REF " $GITHUB_BASE_REF
|
|
||||||
|
|
||||||
- name: Show fix result (diff)
|
- name: Show fix result (diff)
|
||||||
run: |
|
run: |
|
||||||
git status
|
git status
|
||||||
|
|
|
||||||
14
Makefile
14
Makefile
|
|
@ -25,4 +25,16 @@ endif
|
||||||
|
|
||||||
lint: lint-install
|
lint: lint-install
|
||||||
@echo " > Running golint"
|
@echo " > Running golint"
|
||||||
bin/golangci-lint run --timeout=2m
|
bin/golangci-lint run --timeout=2m
|
||||||
|
|
||||||
|
check:
|
||||||
|
go run ./cmd/main.go --script=checker
|
||||||
|
|
||||||
|
fix:
|
||||||
|
go run ./cmd/main.go --script=fixer
|
||||||
|
|
||||||
|
update-auto:
|
||||||
|
go run ./cmd/main.go --script=updater-auto
|
||||||
|
|
||||||
|
update-manual:
|
||||||
|
go run ./cmd/main.go --script=updater-manual
|
||||||
26
README.md
26
README.md
|
|
@ -3,6 +3,7 @@
|
||||||

|

|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Trust Wallet token repository is a comprehensive, up-to-date collection of information about several thousands (!) of crypto tokens.
|
Trust Wallet token repository is a comprehensive, up-to-date collection of information about several thousands (!) of crypto tokens.
|
||||||
|
|
||||||
[Trust Wallet](https://trustwallet.com) uses token logos from this source, alongside a number of other projects.
|
[Trust Wallet](https://trustwallet.com) uses token logos from this source, alongside a number of other projects.
|
||||||
|
|
@ -18,7 +19,7 @@ Such a large collection can be maintained only through a community effort, so _f
|
||||||
|
|
||||||
Please note that __brand new tokens are not accepted__,
|
Please note that __brand new tokens are not accepted__,
|
||||||
the projects have to be sound, with information available, and __non-minimal circulation__
|
the projects have to be sound, with information available, and __non-minimal circulation__
|
||||||
(for limit details see https://developer.trustwallet.com/assets/requirements).
|
(for limit details see <https://developer.trustwallet.com/assets/requirements>).
|
||||||
|
|
||||||
### Assets App
|
### Assets App
|
||||||
|
|
||||||
|
|
@ -31,6 +32,7 @@ Details of the repository structure and contribution guidelines are listed on th
|
||||||
Here is a quick starter summary for the most common use case.
|
Here is a quick starter summary for the most common use case.
|
||||||
|
|
||||||
**Adding an ERC20 token checklist**:
|
**Adding an ERC20 token checklist**:
|
||||||
|
|
||||||
- [ ] Make sure your smartcontract has more than 10000 address holders, otherwise you will be rejected
|
- [ ] Make sure your smartcontract has more than 10000 address holders, otherwise you will be rejected
|
||||||
- [ ] Fork the Github repository
|
- [ ] Fork the Github repository
|
||||||
- [ ] Create folder with name of token smartcontact address in [_checksum format_](https://piyolab.github.io/sushiether/RunScrapboxCode/?web3=1.0.0-beta.33&code=https://scrapbox.io/api/code/sushiether/web3.js_-_Ethereum_のアドレスをチェックサム付きアドレスに変換する/demo.js) `blockchains/ethereum/assets/<token_smartcontract_address>/`.
|
- [ ] Create folder with name of token smartcontact address in [_checksum format_](https://piyolab.github.io/sushiether/RunScrapboxCode/?web3=1.0.0-beta.33&code=https://scrapbox.io/api/code/sushiether/web3.js_-_Ethereum_のアドレスをチェックサム付きアドレスに変換する/demo.js) `blockchains/ethereum/assets/<token_smartcontract_address>/`.
|
||||||
|
|
@ -42,7 +44,7 @@ Here is a quick starter summary for the most common use case.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
For details, see the [Developers site](https://developer.trustwallet.com:
|
For details, see the [Developers site](https://developer.trustwallet.com):
|
||||||
|
|
||||||
- [Contribution guidelines](https://developer.trustwallet.com/assets/repository_details)
|
- [Contribution guidelines](https://developer.trustwallet.com/assets/repository_details)
|
||||||
|
|
||||||
|
|
@ -52,21 +54,18 @@ For details, see the [Developers site](https://developer.trustwallet.com:
|
||||||
|
|
||||||
There are several scripts available for maintainers:
|
There are several scripts available for maintainers:
|
||||||
|
|
||||||
- `npm run check` -- Execute validation checks; also used in continuous integration.
|
- `make check` -- Execute validation checks; also used in continuous integration.
|
||||||
- `npm run check-sanity` -- Strict subset of checks
|
- `make fix` -- Perform automatic fixes where possible
|
||||||
- `npm run fix` -- Perform automatic fixes where possible
|
- `make update-auto` -- Run automatic updates from external sources, executed regularly (GitHub action)
|
||||||
- `npm run fix-sanity` -- Stricter subset
|
- `make update-manual` -- Run manual updates from external sources, for manual use.
|
||||||
- `npm run updateAuto` -- Run automatic updates from external sources, executed regularly (GitHub action)
|
|
||||||
- `npm run update` -- Run manual updates from external sources, for manual use.
|
|
||||||
- `npm test` -- Run script unit tests
|
|
||||||
- `npm lint` -- Run Lint static code check
|
|
||||||
|
|
||||||
## On Checks
|
## On Checks
|
||||||
|
|
||||||
This repo contains a set of scripts for verification of all the information. Implemented as Typescript scripts, available through `npm run check`, and executed in CI build; checks the whole repo.
|
This repo contains a set of scripts for verification of all the information. Implemented as Golang scripts, available through `make check`, and executed in CI build; checks the whole repo.
|
||||||
There are similar check logic implemented:
|
There are similar check logic implemented:
|
||||||
- in assets-management app; for checking changed token files in PRs, or when creating a PR. Implemented as a Typescript library, checks diffs, can be run from browser environment.
|
|
||||||
- in merge-fee-bot, which runs as a GitHub app shows result in PR comment. Also uses library, but executes in a non-browser environment.
|
- in assets-management app; for checking changed token files in PRs, or when creating a PR. Checks diffs, can be run from browser environment.
|
||||||
|
- in merge-fee-bot, which runs as a GitHub app shows result in PR comment. Executes in a non-browser environment.
|
||||||
|
|
||||||
## Trading pair maintenance
|
## Trading pair maintenance
|
||||||
|
|
||||||
|
|
@ -77,6 +76,7 @@ Minimal limit values for trading pair inclusion are set in the `config.ts` file.
|
||||||
There are also options for force-include and force-exclude in the config.
|
There are also options for force-include and force-exclude in the config.
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
Trust Wallet team allows anyone to submit new assets to this repository. However, this does not mean that we are in direct partnership with all of the projects.
|
Trust Wallet team allows anyone to submit new assets to this repository. However, this does not mean that we are in direct partnership with all of the projects.
|
||||||
|
|
||||||
Trust Wallet team will reject projects that are deemed as scam or fraudulent after careful review.
|
Trust Wallet team will reject projects that are deemed as scam or fraudulent after careful review.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
"roots": [
|
|
||||||
"<rootDir>/test"
|
|
||||||
],
|
|
||||||
testMatch: [
|
|
||||||
"**/__tests__/**/*.+(ts|tsx|js)",
|
|
||||||
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
|
||||||
],
|
|
||||||
"transform": {
|
|
||||||
"^.+\\.(ts|tsx)?$": "ts-jest"
|
|
||||||
},
|
|
||||||
"setupFilesAfterEnv": ["jest-expect-message"]
|
|
||||||
}
|
|
||||||
18729
package-lock.json
generated
18729
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
72
package.json
72
package.json
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"name": "assets",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Assets consumed by Trust Wallet",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "jest",
|
|
||||||
"check": "ts-node ./script/entrypoint/check",
|
|
||||||
"check-sanity": "ts-node ./script/entrypoint/check-sanity",
|
|
||||||
"fix": "ts-node ./script/entrypoint/fix",
|
|
||||||
"fix-sanity": "ts-node ./script/entrypoint/fix-sanity",
|
|
||||||
"updateAuto": "ts-node ./script/entrypoint/updateAuto",
|
|
||||||
"update": "ts-node ./script/entrypoint/updateManual",
|
|
||||||
"lint": "npx eslint . --ext .js,.jsx,.ts,.tsx",
|
|
||||||
"lint:fix": "npx eslint . --ext .js,.jsx,.ts,.tsx --fix"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/trustwallet/assets.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"ERC20 token images",
|
|
||||||
"TRC-10 token images",
|
|
||||||
"TRC-20 token images",
|
|
||||||
"BEP-2 token images"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0"
|
|
||||||
},
|
|
||||||
"author": "Trust Wallet",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/trustwallet/assets/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/trustwallet/assets#readme",
|
|
||||||
"devDependencies": {
|
|
||||||
"@trustwallet/wallet-core": "^2.6.12",
|
|
||||||
"@types/jest": "^25.2.3",
|
|
||||||
"@types/jest-expect-message": "^1.0.3",
|
|
||||||
"@types/node": "^13.13.52",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^4.29.0",
|
|
||||||
"@typescript-eslint/parser": "^4.29.0",
|
|
||||||
"axios": "^0.21.1",
|
|
||||||
"bignumber.js": "^9.0.0",
|
|
||||||
"bip44-constants": "^8.0.103",
|
|
||||||
"bluebird": "^3.7.2",
|
|
||||||
"chalk": "^4.1.2",
|
|
||||||
"eslint": "^7.32.0",
|
|
||||||
"ethereum-checksum-address": "0.0.6",
|
|
||||||
"eztz-lib": "^0.1.2",
|
|
||||||
"image-size": "^0.8.3",
|
|
||||||
"is-image": "^3.0.0",
|
|
||||||
"jest": "^26.6.3",
|
|
||||||
"jest-expect-message": "^1.0.2",
|
|
||||||
"meow": "^10.1.1",
|
|
||||||
"nested-property": "^2.0.2",
|
|
||||||
"sharp": "^0.28.3",
|
|
||||||
"tinify": "^1.6.0-beta.2",
|
|
||||||
"ts-jest": "^26.5.6",
|
|
||||||
"ts-node": "^9.1.1",
|
|
||||||
"typescript": "^4.3.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"codecov": "^3.8.3",
|
|
||||||
"jsondiffpatch": "^0.4.1"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"setupFilesAfterEnv": [
|
|
||||||
"jest-expect-message"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
||||||
import axios from "axios";
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import * as path from "path";
|
|
||||||
import * as chalk from 'chalk';
|
|
||||||
import * as config from "../config";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { Binance } from "../generic/blockchains";
|
|
||||||
import { readDirSync } from "../generic/filesystem";
|
|
||||||
import { readJsonFile, writeJsonFile } from "../generic/json";
|
|
||||||
import { TokenItem, Pair, createTokensList, writeToFileWithUpdate } from "../generic/tokenlists";
|
|
||||||
import {
|
|
||||||
getChainAssetLogoPath,
|
|
||||||
getChainAssetsPath,
|
|
||||||
getChainAssetInfoPath,
|
|
||||||
getChainTokenlistPath
|
|
||||||
} from "../generic/repo-structure";
|
|
||||||
import { CoinType } from "@trustwallet/wallet-core";
|
|
||||||
import { toSatoshis } from "../generic/numbers";
|
|
||||||
import { assetIdSymbol, logoURI, tokenType } from "../generic/asset";
|
|
||||||
import { TokenType } from "../generic/tokentype";
|
|
||||||
import { explorerUrl } from "../generic/asset-infos";
|
|
||||||
|
|
||||||
const binanceChain = "binance";
|
|
||||||
const binanceUrlTokenAssets = config.binanceUrlTokenAssets;
|
|
||||||
let cachedAssets = [];
|
|
||||||
|
|
||||||
export class BinanceTokenInfo {
|
|
||||||
asset: string
|
|
||||||
name: string
|
|
||||||
assetImg: string
|
|
||||||
mappedAsset: string
|
|
||||||
decimals: number
|
|
||||||
}
|
|
||||||
|
|
||||||
async function retrieveBep2AssetList(): Promise<BinanceTokenInfo[]> {
|
|
||||||
console.log(`Retrieving token asset infos from: ${binanceUrlTokenAssets}`);
|
|
||||||
const { assetInfoList } = await axios.get(binanceUrlTokenAssets)
|
|
||||||
.then(r => r.data)
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log(JSON.stringify(error))
|
|
||||||
});
|
|
||||||
console.log(`Retrieved ${assetInfoList.length} token asset infos`);
|
|
||||||
return assetInfoList
|
|
||||||
}
|
|
||||||
|
|
||||||
async function retrieveAssets(): Promise<unknown[]> {
|
|
||||||
// cache results because of rate limit, used more than once
|
|
||||||
if (cachedAssets.length == 0) {
|
|
||||||
console.log(`Retrieving token infos`);
|
|
||||||
const bep2assets = await axios.get(`${config.binanceDexURL}/v1/tokens?limit=1000`);
|
|
||||||
const bep8assets = await axios.get(`${config.binanceDexURL}/v1/mini/tokens?limit=1000`);
|
|
||||||
cachedAssets = bep2assets.data.concat(bep8assets.data);
|
|
||||||
}
|
|
||||||
console.log(`Using ${cachedAssets.length} assets`);
|
|
||||||
return cachedAssets;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function retrieveAssetSymbols(): Promise<string[]> {
|
|
||||||
const assets = await retrieveAssets();
|
|
||||||
const symbols = assets.map(({ symbol }) => symbol);
|
|
||||||
return symbols;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchImage(url) {
|
|
||||||
return axios.get(url, { responseType: "stream" })
|
|
||||||
.then(r => r.data)
|
|
||||||
.catch(err => {
|
|
||||||
throw `Error fetchImage: ${url} ${err.message}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return: array with images to fetch; {asset, assetImg}
|
|
||||||
export function findImagesToFetch(assetInfoList: BinanceTokenInfo[]): BinanceTokenInfo[] {
|
|
||||||
const toFetch: BinanceTokenInfo[] = [];
|
|
||||||
console.log(`Checking for asset images to be fetched`);
|
|
||||||
assetInfoList.forEach((tokenInfo) => {
|
|
||||||
process.stdout.write(`.${tokenInfo.asset} `);
|
|
||||||
// pick assets only if img and decimals is present
|
|
||||||
if (tokenInfo.assetImg && tokenInfo.decimals) {
|
|
||||||
const imagePath = getChainAssetLogoPath(binanceChain, tokenInfo.asset);
|
|
||||||
if (!fs.existsSync(imagePath)) {
|
|
||||||
console.log(chalk.red(`Missing image: ${tokenInfo.asset}`));
|
|
||||||
toFetch.push(tokenInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log();
|
|
||||||
console.log(`${toFetch.length} asset image(s) to be fetched`);
|
|
||||||
return toFetch;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createInfoJson(tokenInfo: BinanceTokenInfo): Promise<void> {
|
|
||||||
//console.log(tokenInfo);
|
|
||||||
const info = {
|
|
||||||
name: tokenInfo.name,
|
|
||||||
type: "BEP2",
|
|
||||||
symbol: tokenInfo.mappedAsset,
|
|
||||||
decimals: tokenInfo.decimals,
|
|
||||||
website: '',
|
|
||||||
description: '-',
|
|
||||||
explorer: explorerUrl(binanceChain, tokenInfo.asset),
|
|
||||||
status: 'active',
|
|
||||||
id: tokenInfo.asset
|
|
||||||
};
|
|
||||||
const infoPath = getChainAssetInfoPath(binanceChain, tokenInfo.asset);
|
|
||||||
writeJsonFile(infoPath, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMissingImages(toFetch: BinanceTokenInfo[]): Promise<string[]> {
|
|
||||||
console.log(`Attempting to fetch ${toFetch.length} asset image(s)`);
|
|
||||||
const fetchedAssets: string[] = [];
|
|
||||||
await bluebird.each(toFetch, async (tokenInfo) => {
|
|
||||||
if (tokenInfo && tokenInfo.asset && tokenInfo.assetImg) {
|
|
||||||
const imagePath = getChainAssetLogoPath(binanceChain, tokenInfo.asset);
|
|
||||||
fs.mkdir(path.dirname(imagePath), err => {
|
|
||||||
if (err && err.code != `EEXIST`) throw err;
|
|
||||||
});
|
|
||||||
const buffer = await fetchImage(tokenInfo.assetImg);
|
|
||||||
await buffer.pipe(fs.createWriteStream(imagePath));
|
|
||||||
await createInfoJson(tokenInfo);
|
|
||||||
fetchedAssets.push(tokenInfo.asset)
|
|
||||||
console.log(`Token ${tokenInfo.asset} ${tokenInfo.mappedAsset}: Fetched image, created info.json (${tokenInfo.assetImg})`)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log();
|
|
||||||
return fetchedAssets;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BinanceAction implements ActionInterface {
|
|
||||||
getName(): string { return "Binance chain"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Binance chain; assets must exist on chain"},
|
|
||||||
check: async () => {
|
|
||||||
const errors = [];
|
|
||||||
const tokenSymbols = await retrieveAssetSymbols();
|
|
||||||
const assets = readDirSync(getChainAssetsPath(Binance));
|
|
||||||
assets.forEach(asset => {
|
|
||||||
if (!(tokenSymbols.indexOf(asset) >= 0)) {
|
|
||||||
errors.push(`Asset ${asset} missing on chain`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(` ${assets.length} assets checked.`);
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateAuto(): Promise<void> {
|
|
||||||
// retrieve missing token images; BEP2 (bep8 not supported)
|
|
||||||
const bep2InfoList = await retrieveBep2AssetList();
|
|
||||||
if (bep2InfoList.length < 5) {
|
|
||||||
console.log(`ERROR: No Binance token info is returned! ${bep2InfoList.length}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const toFetch = findImagesToFetch(bep2InfoList);
|
|
||||||
const fetchedAssets = await fetchMissingImages(toFetch);
|
|
||||||
|
|
||||||
if (fetchedAssets.length > 0) {
|
|
||||||
console.log(`Fetched ${fetchedAssets.length} asset(s):`);
|
|
||||||
fetchedAssets.forEach(asset => console.log(` ${asset}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// binance chain list
|
|
||||||
const tokenList = await generateBinanceTokensList();
|
|
||||||
if (tokenList.length < 5) {
|
|
||||||
console.log(`ERROR: no token pair info available from Binance DEX! ${tokenList.length}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const list = createTokensList("BNB", tokenList,
|
|
||||||
"2020-10-03T12:37:57.000+00:00", // use constants here to prevent changing time every time
|
|
||||||
0, 1, 0);
|
|
||||||
if (tokenList.length > 0) {
|
|
||||||
writeToFileWithUpdate(getChainTokenlistPath(Binance), list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BinanceMarket {
|
|
||||||
base_asset_symbol: string
|
|
||||||
quote_asset_symbol: string
|
|
||||||
lot_size: string
|
|
||||||
tick_size: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateBinanceTokensList(): Promise<TokenItem[]> {
|
|
||||||
const decimals = CoinType.decimals(CoinType.binance)
|
|
||||||
const BNBSymbol = CoinType.symbol(CoinType.binance)
|
|
||||||
const markets: BinanceMarket[] = await axios.get(`${config.binanceDexURL}/v1/markets?limit=10000`)
|
|
||||||
.then(r => r.data)
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log(JSON.stringify(error))
|
|
||||||
});
|
|
||||||
if (markets.length < 5) {
|
|
||||||
console.log(`ERROR: No markets info is returned from Binance DEX! ${markets.length}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const tokens = await axios.get(`${config.binanceDexURL}/v1/tokens?limit=10000`)
|
|
||||||
.then(r => r.data)
|
|
||||||
.catch(function (error) {
|
|
||||||
console.log(JSON.stringify(error))
|
|
||||||
});
|
|
||||||
if (tokens.length < 5) {
|
|
||||||
console.log(`ERROR: No tokens info is returned from Binance DEX! ${tokens.length}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const tokensMap = Object.assign({}, ...tokens.map(s => ({[s.symbol]: s})));
|
|
||||||
const pairsMap = {}
|
|
||||||
const pairsList = new Set();
|
|
||||||
|
|
||||||
markets.forEach(market => {
|
|
||||||
const key = market.quote_asset_symbol
|
|
||||||
|
|
||||||
function pair(market: BinanceMarket): Pair {
|
|
||||||
return new Pair(
|
|
||||||
assetIdSymbol(market.base_asset_symbol, BNBSymbol, CoinType.binance),
|
|
||||||
toSatoshis(market.lot_size, decimals),
|
|
||||||
toSatoshis(market.tick_size, decimals)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pairsMap[key]) {
|
|
||||||
const newList = pairsMap[key]
|
|
||||||
newList.push(pair(market))
|
|
||||||
pairsMap[key] = newList
|
|
||||||
} else {
|
|
||||||
pairsMap[key] = [
|
|
||||||
pair(market)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
pairsList.add(market.base_asset_symbol)
|
|
||||||
pairsList.add(market.quote_asset_symbol)
|
|
||||||
})
|
|
||||||
|
|
||||||
const list = <string[]>Array.from(pairsList.values())
|
|
||||||
return <TokenItem[]>list.map(item => {
|
|
||||||
const token = tokensMap[item]
|
|
||||||
return new TokenItem (
|
|
||||||
assetIdSymbol(token.symbol, BNBSymbol, CoinType.binance),
|
|
||||||
tokenType(token.symbol, BNBSymbol, TokenType.BEP2),
|
|
||||||
token.symbol,
|
|
||||||
token.name,
|
|
||||||
token.original_symbol,
|
|
||||||
decimals,
|
|
||||||
logoURI(token.symbol, 'binance', BNBSymbol),
|
|
||||||
pairsMap[token.symbol] || []
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import { Cosmos } from "../generic/blockchains";
|
|
||||||
import { getChainValidatorsAssets } from "../generic/repo-structure";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { isLowerCase } from "../generic/types";
|
|
||||||
|
|
||||||
export class CosmosAction implements ActionInterface {
|
|
||||||
getName(): string { return "Cosmos chain"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Cosmos validator assets must have correct format"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const assets = getChainValidatorsAssets(Cosmos);
|
|
||||||
const prefix = "cosmosvaloper1";
|
|
||||||
const expLength = 52;
|
|
||||||
assets.forEach(addr => {
|
|
||||||
if (!(addr.startsWith(prefix))) {
|
|
||||||
errors.push(`Address ${addr} should start with '${prefix}'`);
|
|
||||||
}
|
|
||||||
if (addr.length != expLength) {
|
|
||||||
errors.push(`Address ${addr} should have length ${expLength}`);
|
|
||||||
}
|
|
||||||
if (!isLowerCase(addr)) {
|
|
||||||
errors.push(`Address ${addr} should be in lowercase`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import {
|
|
||||||
checkTradingPair,
|
|
||||||
getTradingPairs,
|
|
||||||
PairInfo,
|
|
||||||
primaryTokenIndex,
|
|
||||||
TokenInfo
|
|
||||||
} from "../generic/subgraph";
|
|
||||||
import { Ethereum } from "../generic/blockchains";
|
|
||||||
import {
|
|
||||||
parseForceList,
|
|
||||||
rebuildTokenlist,
|
|
||||||
TokenItem
|
|
||||||
} from "../generic/tokenlists";
|
|
||||||
import { toChecksum } from "../generic/eth-address";
|
|
||||||
import { assetID, logoURI } from "../generic/asset";
|
|
||||||
import * as config from "../config"
|
|
||||||
|
|
||||||
const PrimaryTokens: string[] = ["WETH", "ETH"];
|
|
||||||
|
|
||||||
// Retrieve trading pairs from Uniswap
|
|
||||||
async function retrieveUniswapPairs(): Promise<PairInfo[]> {
|
|
||||||
console.log(`Retrieving pairs from Uniswap, limit liquidity USD ${config.Uniswap_MinLiquidity} volume ${config.Uniswap_MinVol24} txcount ${config.Uniswap_MinTxCount24}`);
|
|
||||||
|
|
||||||
console.log(` forceIncludeList: ${config.Uniswap_ForceInclude}`);
|
|
||||||
const includeList = parseForceList(config.Uniswap_ForceInclude);
|
|
||||||
|
|
||||||
const pairs = await getTradingPairs(config.Uniswap_TradingPairsUrl, config.Uniswap_TradingPairsQuery);
|
|
||||||
const filtered: PairInfo[] = [];
|
|
||||||
pairs.forEach(x => {
|
|
||||||
try {
|
|
||||||
if (typeof(x) === "object") {
|
|
||||||
const pairInfo = x as PairInfo;
|
|
||||||
if (pairInfo) {
|
|
||||||
if (checkTradingPair(pairInfo, config.Uniswap_MinLiquidity, config.Uniswap_MinVol24, config.Uniswap_MinTxCount24, PrimaryTokens, includeList)) {
|
|
||||||
filtered.push(pairInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Exception:", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Retrieved & filtered", filtered.length, "pairs:");
|
|
||||||
filtered.forEach(p => {
|
|
||||||
console.log(`pair: ${p.token0.symbol} -- ${p.token1.symbol} \t USD ${Math.round(p.reserveUSD)} ${Math.round(p.volumeUSD)} ${p.txCount}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenInfoFromSubgraphToken(token: TokenInfo): TokenItem {
|
|
||||||
const idChecksum = toChecksum(token.id);
|
|
||||||
return new TokenItem(
|
|
||||||
assetID(60, idChecksum),
|
|
||||||
"ERC20",
|
|
||||||
idChecksum, token.name, token.symbol, parseInt(token.decimals.toString()),
|
|
||||||
logoURI(idChecksum, "ethereum", "--"),
|
|
||||||
[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve trading pairs from PancakeSwap
|
|
||||||
async function generateTokenlist(): Promise<void> {
|
|
||||||
// note: if [] is returned here for some reason, all pairs will be *removed*. In case of error (e.g. timeout) it should throw
|
|
||||||
const tradingPairs = await retrieveUniswapPairs();
|
|
||||||
// convert
|
|
||||||
const pairs2: [TokenItem, TokenItem][] = [];
|
|
||||||
tradingPairs.forEach(p => {
|
|
||||||
let tokenItem0 = tokenInfoFromSubgraphToken(p.token0);
|
|
||||||
let tokenItem1 = tokenInfoFromSubgraphToken(p.token1);
|
|
||||||
if (primaryTokenIndex(p, PrimaryTokens) == 2) {
|
|
||||||
// reverse
|
|
||||||
const tmp = tokenItem1; tokenItem1 = tokenItem0; tokenItem0 = tmp;
|
|
||||||
}
|
|
||||||
pairs2.push([tokenItem0, tokenItem1]);
|
|
||||||
});
|
|
||||||
await rebuildTokenlist(Ethereum, pairs2, "Ethereum", config.Uniswap_ForceExclude);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EthereumAction implements ActionInterface {
|
|
||||||
getName(): string { return "Ethereum"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] { return []; }
|
|
||||||
|
|
||||||
async updateManual(): Promise<void> {
|
|
||||||
await generateTokenlist();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import { Kava } from "../generic/blockchains";
|
|
||||||
import { getChainValidatorsAssets } from "../generic/repo-structure";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { isLowerCase } from "../generic/types";
|
|
||||||
|
|
||||||
export class KavaAction implements ActionInterface {
|
|
||||||
getName(): string { return "Kava chain"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Kava validator assets must have correct format"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const assets = getChainValidatorsAssets(Kava);
|
|
||||||
const prefix = "kavavaloper1";
|
|
||||||
const expLength = 50;
|
|
||||||
assets.forEach(addr => {
|
|
||||||
if (!(addr.startsWith(prefix))) {
|
|
||||||
errors.push(`Address ${addr} should start with '${prefix}'`);
|
|
||||||
}
|
|
||||||
if (addr.length != expLength) {
|
|
||||||
errors.push(`Address ${addr} should have length ${expLength}`);
|
|
||||||
}
|
|
||||||
if (!isLowerCase(addr)) {
|
|
||||||
errors.push(`Address ${addr} should be in lowercase`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import {
|
|
||||||
checkTradingPair,
|
|
||||||
getTradingPairsBitquery,
|
|
||||||
PairInfoBitquery,
|
|
||||||
PairInfo,
|
|
||||||
primaryTokenIndex,
|
|
||||||
TokenInfo,
|
|
||||||
TokenInfoBitquery
|
|
||||||
} from "../generic/subgraph";
|
|
||||||
import { Polygon } from "../generic/blockchains";
|
|
||||||
import {
|
|
||||||
parseForceList,
|
|
||||||
rebuildTokenlist,
|
|
||||||
TokenItem
|
|
||||||
} from "../generic/tokenlists";
|
|
||||||
import { toChecksum } from "../generic/eth-address";
|
|
||||||
import { assetID, logoURI } from "../generic/asset";
|
|
||||||
import * as config from "../config"
|
|
||||||
|
|
||||||
const PrimaryTokens: string[] = ["WMATIC", "MATIC", "WETH", "USDC", "USDT", "DAI"];
|
|
||||||
|
|
||||||
function convertTokenInfo(token: TokenInfoBitquery): TokenInfo {
|
|
||||||
return {
|
|
||||||
id: token.address,
|
|
||||||
symbol: token.symbol,
|
|
||||||
name: token.name,
|
|
||||||
decimals: token.decimals
|
|
||||||
} as TokenInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertPairInfo(pair: PairInfoBitquery): PairInfo {
|
|
||||||
return {
|
|
||||||
id: '?',
|
|
||||||
reserveUSD: 1,
|
|
||||||
volumeUSD: pair.tradeAmount,
|
|
||||||
txCount: pair.trade,
|
|
||||||
token0: convertTokenInfo(pair.buyCurrency),
|
|
||||||
token1: convertTokenInfo(pair.sellCurrency)
|
|
||||||
} as PairInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the date 1,5 days ago in the form 2021-10-06
|
|
||||||
function dateOfYesterday() {
|
|
||||||
const now = new Date();
|
|
||||||
const yesterday = new Date(now.getTime() - 36*3600*1000);
|
|
||||||
return yesterday.toISOString().substr(0, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function retrievePolygonSwapPairs(): Promise<PairInfo[]> {
|
|
||||||
console.log(`Retrieving pairs from Polygon, limit volume ${config.PolygonSwap_MinVol24} txcount ${config.PolygonSwap_MinTxCount24}`);
|
|
||||||
|
|
||||||
console.log(` forceIncludeList: ${config.PolygonSwap_ForceInclude}`);
|
|
||||||
const includeList = parseForceList(config.PolygonSwap_ForceInclude);
|
|
||||||
|
|
||||||
const query = config.PolygonSwap_TradingPairsQuery;
|
|
||||||
const date = dateOfYesterday();
|
|
||||||
const queryReplaced = query.replace("$DATE$", date);
|
|
||||||
|
|
||||||
const pairs = await getTradingPairsBitquery(config.PolygonSwap_TradingPairsUrl, queryReplaced);
|
|
||||||
const filtered: PairInfo[] = [];
|
|
||||||
pairs.forEach(x => {
|
|
||||||
try {
|
|
||||||
if (typeof(x) === "object") {
|
|
||||||
const pairInfo = convertPairInfo(x as PairInfoBitquery);
|
|
||||||
if (pairInfo) {
|
|
||||||
if (checkTradingPair(pairInfo, 0, config.PolygonSwap_MinVol24, config.PolygonSwap_MinTxCount24, PrimaryTokens, includeList)) {
|
|
||||||
filtered.push(pairInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Exception:", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Retrieved & filtered", filtered.length, "pairs:");
|
|
||||||
filtered.forEach(p => {
|
|
||||||
console.log(`pair: ${p.token0.symbol} -- ${p.token1.symbol} \t USD ${Math.round(p.reserveUSD)} ${Math.round(p.volumeUSD)} ${p.txCount}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenInfoFromSubgraphToken(token: TokenInfo): TokenItem {
|
|
||||||
const idChecksum = toChecksum(token.id);
|
|
||||||
return new TokenItem(
|
|
||||||
assetID(966, idChecksum),
|
|
||||||
"POLYGON",
|
|
||||||
idChecksum, token.name, token.symbol, parseInt(token.decimals.toString()),
|
|
||||||
logoURI(idChecksum, "polygon", "--"),
|
|
||||||
[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve trading pairs from Polygon
|
|
||||||
async function generateTokenlist(): Promise<void> {
|
|
||||||
// note: if [] is returned here for some reason, all pairs will be *removed*. In case of error (e.g. timeout) it should throw
|
|
||||||
const tradingPairs = await retrievePolygonSwapPairs();
|
|
||||||
// convert
|
|
||||||
const pairs2: [TokenItem, TokenItem][] = [];
|
|
||||||
tradingPairs.forEach(p => {
|
|
||||||
let tokenItem0 = tokenInfoFromSubgraphToken(p.token0);
|
|
||||||
let tokenItem1 = tokenInfoFromSubgraphToken(p.token1);
|
|
||||||
if (primaryTokenIndex(p, PrimaryTokens) == 2) {
|
|
||||||
// reverse
|
|
||||||
const tmp = tokenItem1; tokenItem1 = tokenItem0; tokenItem0 = tmp;
|
|
||||||
}
|
|
||||||
pairs2.push([tokenItem0, tokenItem1]);
|
|
||||||
});
|
|
||||||
await rebuildTokenlist(Polygon, pairs2, "Polygon", config.PolygonSwap_ForceExclude);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PolygonAction implements ActionInterface {
|
|
||||||
getName(): string { return "Polygon"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] { return []; }
|
|
||||||
|
|
||||||
async updateManual(): Promise<void> {
|
|
||||||
await generateTokenlist();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import {
|
|
||||||
checkTradingPair,
|
|
||||||
getTradingPairs,
|
|
||||||
PairInfo,
|
|
||||||
primaryTokenIndex,
|
|
||||||
TokenInfo
|
|
||||||
} from "../generic/subgraph";
|
|
||||||
import { SmartChain } from "../generic/blockchains";
|
|
||||||
import {
|
|
||||||
parseForceList,
|
|
||||||
rebuildTokenlist,
|
|
||||||
TokenItem
|
|
||||||
} from "../generic/tokenlists";
|
|
||||||
import { toChecksum } from "../generic/eth-address";
|
|
||||||
import { assetID, logoURI } from "../generic/asset";
|
|
||||||
import * as config from "../config"
|
|
||||||
|
|
||||||
const PrimaryTokens: string[] = ["WBNB", "BNB"];
|
|
||||||
|
|
||||||
async function retrievePancakeSwapPairs(): Promise<PairInfo[]> {
|
|
||||||
console.log(`Retrieving pairs from PancakeSwap, limit liquidity USD ${config.PancakeSwap_MinLiquidity} volume ${config.PancakeSwap_MinVol24} txcount ${config.PancakeSwap_MinTxCount24}`);
|
|
||||||
|
|
||||||
console.log(` forceIncludeList: ${config.PancakeSwap_ForceInclude}`);
|
|
||||||
const includeList = parseForceList(config.PancakeSwap_ForceInclude);
|
|
||||||
|
|
||||||
const pairs = await getTradingPairs(config.PancakeSwap_TradingPairsUrl, config.PancakeSwap_TradingPairsQuery);
|
|
||||||
const filtered: PairInfo[] = [];
|
|
||||||
pairs.forEach(x => {
|
|
||||||
try {
|
|
||||||
if (typeof(x) === "object") {
|
|
||||||
const pairInfo = x as PairInfo;
|
|
||||||
if (pairInfo) {
|
|
||||||
if (checkTradingPair(pairInfo, config.PancakeSwap_MinLiquidity, config.PancakeSwap_MinVol24, config.PancakeSwap_MinTxCount24, PrimaryTokens, includeList)) {
|
|
||||||
filtered.push(pairInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Exception:", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Retrieved & filtered", filtered.length, "pairs:");
|
|
||||||
filtered.forEach(p => {
|
|
||||||
console.log(`pair: ${p.token0.symbol} -- ${p.token1.symbol} \t USD ${Math.round(p.reserveUSD)} ${Math.round(p.volumeUSD)} ${p.txCount}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenInfoFromSubgraphToken(token: TokenInfo): TokenItem {
|
|
||||||
const idChecksum = toChecksum(token.id);
|
|
||||||
return new TokenItem(
|
|
||||||
assetID(20000714, idChecksum),
|
|
||||||
"BEP20",
|
|
||||||
idChecksum, token.name, token.symbol, parseInt(token.decimals.toString()),
|
|
||||||
logoURI(idChecksum, "smartchain", "--"),
|
|
||||||
[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve trading pairs from PancakeSwap
|
|
||||||
async function generateTokenlist(): Promise<void> {
|
|
||||||
// note: if [] is returned here for some reason, all pairs will be *removed*. In case of error (e.g. timeout) it should throw
|
|
||||||
const tradingPairs = await retrievePancakeSwapPairs();
|
|
||||||
// convert
|
|
||||||
const pairs2: [TokenItem, TokenItem][] = [];
|
|
||||||
tradingPairs.forEach(p => {
|
|
||||||
let tokenItem0 = tokenInfoFromSubgraphToken(p.token0);
|
|
||||||
let tokenItem1 = tokenInfoFromSubgraphToken(p.token1);
|
|
||||||
if (primaryTokenIndex(p, PrimaryTokens) == 2) {
|
|
||||||
// reverse
|
|
||||||
const tmp = tokenItem1; tokenItem1 = tokenItem0; tokenItem0 = tmp;
|
|
||||||
}
|
|
||||||
pairs2.push([tokenItem0, tokenItem1]);
|
|
||||||
});
|
|
||||||
await rebuildTokenlist(SmartChain, pairs2, "Smart Chain", config.PancakeSwap_ForceExclude);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SmartchainAction implements ActionInterface {
|
|
||||||
getName(): string { return "Binance Smartchain"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] { return []; }
|
|
||||||
|
|
||||||
async updateManual(): Promise<void> {
|
|
||||||
await generateTokenlist();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
import { Terra } from "../generic/blockchains";
|
|
||||||
import { getChainValidatorsAssets } from "../generic/repo-structure";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { isLowerCase } from "../generic/types";
|
|
||||||
|
|
||||||
export class TerraAction implements ActionInterface {
|
|
||||||
getName(): string { return "Terra chain"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Terra validator assets must have correct format"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const assets = getChainValidatorsAssets(Terra);
|
|
||||||
const prefix = "terravaloper1";
|
|
||||||
const expLength = 51;
|
|
||||||
assets.forEach(addr => {
|
|
||||||
if (!(addr.startsWith(prefix))) {
|
|
||||||
errors.push(`Address ${addr} should start with '${prefix}'`);
|
|
||||||
}
|
|
||||||
if (addr.length != expLength) {
|
|
||||||
errors.push(`Address ${addr} should have length ${expLength}`);
|
|
||||||
}
|
|
||||||
if (!isLowerCase(addr)) {
|
|
||||||
errors.push(`Address ${addr} should be in lowercase`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import * as eztz from "eztz-lib";
|
|
||||||
import { getChainValidatorsAssets } from "../generic/repo-structure";
|
|
||||||
import { Tezos } from "../generic/blockchains";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
|
|
||||||
export class TezosAction implements ActionInterface {
|
|
||||||
getName(): string { return "Tezos"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Tezos validator assets must have correct format"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const assets = getChainValidatorsAssets(Tezos);
|
|
||||||
assets.forEach(addr => {
|
|
||||||
if (!(eztz.crypto.checkAddress(addr))) {
|
|
||||||
errors.push(`Address ${addr} must be valid Tezos address'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { getChainAssetsPath } from "../generic/repo-structure";
|
|
||||||
import { Tron } from "../generic/blockchains";
|
|
||||||
import { readDirSync } from "../generic/filesystem";
|
|
||||||
import { getChainValidatorsAssets } from "../generic/repo-structure";
|
|
||||||
import { isLowerCase, isUpperCase } from "../generic/types";
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
|
|
||||||
export function isTRC10(str: string): boolean {
|
|
||||||
return (/^\d+$/.test(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isTRC20(address: string): boolean {
|
|
||||||
return address.length == 34 &&
|
|
||||||
address.startsWith("T") &&
|
|
||||||
isLowerCase(address) == false &&
|
|
||||||
isUpperCase(address) == false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TronAction implements ActionInterface {
|
|
||||||
getName(): string { return "Tron chain"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Tron assets should be TRC10 or TRC20, logo of correct size"; },
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const path = getChainAssetsPath(Tron);
|
|
||||||
const assets = readDirSync(path);
|
|
||||||
await bluebird.each(assets, async (asset) => {
|
|
||||||
if (!isTRC10(asset) && !isTRC20(asset)) {
|
|
||||||
errors.push(`Asset ${asset} at path '${path}' is not TRC10 nor TRC20`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getName: () => { return "Tron validator assets must have correct format"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const assets = getChainValidatorsAssets(Tron);
|
|
||||||
assets.forEach(addr => {
|
|
||||||
if (!(isTRC20(addr))) {
|
|
||||||
errors.push(`Address ${addr} should be TRC20 address'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { Waves } from "../generic/blockchains";
|
|
||||||
import { getChainValidatorsAssets } from "../generic/repo-structure";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { isLowerCase, isUpperCase } from "../generic/types";
|
|
||||||
|
|
||||||
export function isWavesAddress(address: string): boolean {
|
|
||||||
return address.length == 35 &&
|
|
||||||
address.startsWith("3P") &&
|
|
||||||
isLowerCase(address) == false &&
|
|
||||||
isUpperCase(address) == false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WavesAction implements ActionInterface {
|
|
||||||
getName(): string { return "Waves chain"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Waves validator assets must have correct format"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const assets = getChainValidatorsAssets(Waves);
|
|
||||||
assets.forEach(addr => {
|
|
||||||
if (!(isWavesAddress(addr))) {
|
|
||||||
errors.push(`Address ${addr} should be a Waves address'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
export const imageMaxLogoWidth = 512;
|
|
||||||
export const imageMaxLogoHeight = 512;
|
|
||||||
export const imageMinLogoWidth = 64;
|
|
||||||
export const imageMinLogoHeight = 64;
|
|
||||||
export const imageMaxLogoSizeKb = 100;
|
|
||||||
export const foldersRootdirAllowedFiles: string[] = [".github", "blockchains", "dapps", "media", "node_modules", "script-old", "script", "test", ".gitignore", "azure-pipelines.yml", "jest.config.js", "LICENSE", "package-lock.json", "package.json", "README.md", ".git", ".eslintignore", ".eslintrc.js", "cmd", "go.mod", "go.sum"];
|
|
||||||
export const binanceUrlTokenAssets = "https://explorer.binance.org/api/v1/assets?page=1&rows=1000";
|
|
||||||
export const binanceDexURL = 'https://dex-atlantic.binance.org/api'
|
|
||||||
export const assetsURL = 'https://assets.trustwalletapp.com'
|
|
||||||
|
|
||||||
// Force include & exclude config: list of token symbols, or symbol pairs (e.g. ["Cake", "DAI-WBNB"]).
|
|
||||||
export const PancakeSwap_ForceInclude: string[] = ["Cake", "DAI", "ETH", "TWT", "VAI", "USDT", "BLINK", "BTCB", "ALPHA", "INJ", "CTK", "UNI", "XVS", "BUSD", "HARD", "BIFI", "FRONT"];
|
|
||||||
export const PancakeSwap_ForceExclude: string[] = [];
|
|
||||||
export const PancakeSwap_TradingPairsUrl = "https://api.bscgraph.org/subgraphs/name/cakeswap";
|
|
||||||
export const PancakeSwap_TradingPairsQuery = `
|
|
||||||
query pairs {
|
|
||||||
pairs(first: 300, orderBy: reserveUSD, orderDirection: desc) {
|
|
||||||
id reserveUSD volumeUSD txCount __typename
|
|
||||||
token0 {
|
|
||||||
id symbol name decimals __typename
|
|
||||||
}
|
|
||||||
token1 {
|
|
||||||
id symbol name decimals __typename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export const PancakeSwap_MinLiquidity = 1000000;
|
|
||||||
export const PancakeSwap_MinVol24 = 500000;
|
|
||||||
export const PancakeSwap_MinTxCount24 = 288;
|
|
||||||
|
|
||||||
// Force include & exclude config: list of token symbols, or symbol pairs (e.g. ["BAT", "YFI-WETH"]).
|
|
||||||
export const Uniswap_ForceInclude: string[] = ["TUSD", "STAKE", "YFI", "BAT", "MANA", "1INCH", "REP", "KP3R", "UNI", "WBTC", "HEX", "CREAM", "SLP", "REN", "XOR", "Link", "sUSD", "HEGIC", "RLC", "DAI", "SUSHI", "FYZ", "DYT", "AAVE", "LEND", "UBT", "DIA", "RSR", "SXP", "OCEAN", "MKR", "USDC", "CEL", "BAL", "BAND", "COMP", "SNX", "OMG", "AMPL", "USDT", "KNC", "ZRX", "AXS", "ENJ", "STMX", "DPX", "FTT", "DPI", "PAX"];
|
|
||||||
export const Uniswap_ForceExclude: string[] = ["STARL", "UFO"];
|
|
||||||
export const Uniswap_TradingPairsUrl = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2"; // see https://thegraph.com/explorer/subgraph/uniswap/uniswap-v2
|
|
||||||
export const Uniswap_TradingPairsQuery = `
|
|
||||||
query pairs {
|
|
||||||
pairs(first: 800, orderBy: reserveUSD, orderDirection: desc) {
|
|
||||||
id reserveUSD trackedReserveETH volumeUSD txCount untrackedVolumeUSD __typename
|
|
||||||
token0 {
|
|
||||||
id symbol name decimals __typename
|
|
||||||
}
|
|
||||||
token1 {
|
|
||||||
id symbol name decimals __typename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export const Uniswap_MinLiquidity = 2000000;
|
|
||||||
export const Uniswap_MinVol24 = 1000000;
|
|
||||||
export const Uniswap_MinTxCount24 = 480;
|
|
||||||
|
|
||||||
// Force include & exclude config: list of token symbols, or symbol pairs (e.g. ["Cake", "DAI-WBNB"]).
|
|
||||||
export const PolygonSwap_ForceInclude: string[] = [];
|
|
||||||
export const PolygonSwap_ForceExclude: string[] = [];
|
|
||||||
export const PolygonSwap_TradingPairsUrl = "https://graphql.bitquery.io";
|
|
||||||
export const PolygonSwap_TradingPairsQuery = `
|
|
||||||
{
|
|
||||||
ethereum(network: matic) {
|
|
||||||
dexTrades(date: {is: "$DATE$"}) {
|
|
||||||
sellCurrency {address symbol name decimals}
|
|
||||||
buyCurrency {address symbol name decimals}
|
|
||||||
trade: count
|
|
||||||
tradeAmount(in: USD)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
//export const PolygonSwap_MinLiquidity = 1000000;
|
|
||||||
export const PolygonSwap_MinVol24 = 500000;
|
|
||||||
export const PolygonSwap_MinTxCount24 = 288;
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import { sanityCheckAll } from "../generic/update-all";
|
|
||||||
|
|
||||||
export async function main(): Promise<void> {
|
|
||||||
try {
|
|
||||||
// warnings ignored
|
|
||||||
const [errors] = await sanityCheckAll();
|
|
||||||
process.exit(errors.length);
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { sanityCheckAll, consistencyCheckAll } from "../generic/update-all";
|
|
||||||
|
|
||||||
export async function main(): Promise<void> {
|
|
||||||
let returnCode = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// warnings ignored
|
|
||||||
const [errors1] = await sanityCheckAll();
|
|
||||||
if (errors1.length > 0) {
|
|
||||||
returnCode = errors1.length;
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
returnCode = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// warnings ignored
|
|
||||||
const [errors1] = await consistencyCheckAll();
|
|
||||||
if (errors1.length > 0) {
|
|
||||||
returnCode = errors1.length;
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
returnCode = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(returnCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { sanityFixAll } from "../generic/update-all";
|
|
||||||
|
|
||||||
export async function main(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await sanityFixAll();
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { sanityFixAll, consistencyFixAll } from "../generic/update-all";
|
|
||||||
|
|
||||||
export async function main(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await sanityFixAll();
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await consistencyFixAll();
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { updateAutoAll } from "../generic/update-all";
|
|
||||||
|
|
||||||
export async function main(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await updateAutoAll();
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import { updateManualAll } from "../generic/update-all";
|
|
||||||
|
|
||||||
export async function main(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await updateManualAll();
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
|
|
@ -1,492 +0,0 @@
|
||||||
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";
|
|
||||||
case "CELO": return "celo";
|
|
||||||
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://snowtrace.io/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}`
|
|
||||||
case "celo":
|
|
||||||
return `https://explorer.bitquery.io/celo_rc1/token/${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 error
|
|
||||||
errors.push(`Incorrect 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import axios from "axios";
|
|
||||||
import { TokenType } from "../generic/tokentype";
|
|
||||||
import * as config from "../config";
|
|
||||||
|
|
||||||
// Asset ID from coin symbol (diff between native and token coins)
|
|
||||||
export function assetIdSymbol(tokenId: string, nativeCoinId: string, coinType: number): string {
|
|
||||||
if (tokenId == nativeCoinId) {
|
|
||||||
return assetID(coinType)
|
|
||||||
}
|
|
||||||
return assetID(coinType, tokenId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asset ID from coin number and ID
|
|
||||||
export function assetID(coinType: number, tokenId = ``): string {
|
|
||||||
if (tokenId.length > 0) {
|
|
||||||
return `c${coinType}_t${tokenId}`
|
|
||||||
}
|
|
||||||
return `c${coinType}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token type from token symbol (diff between native and token coins)
|
|
||||||
export function tokenType(symbol: string, nativeCoinSymbol: string, tokenType: string): string {
|
|
||||||
if (symbol == nativeCoinSymbol) {
|
|
||||||
return TokenType.COIN
|
|
||||||
}
|
|
||||||
return tokenType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Github logo URL for coin.
|
|
||||||
export function logoURI(id: string, githubChainFolder: string, nativeCoinSymbol: string): string {
|
|
||||||
if (id == nativeCoinSymbol) {
|
|
||||||
return `${config.assetsURL}/blockchains/${githubChainFolder}/info/logo.png`
|
|
||||||
}
|
|
||||||
return `${config.assetsURL}/blockchains/${githubChainFolder}/assets/${id}/logo.png`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token info from TW api
|
|
||||||
// e.g. {"name":"Binance-Peg Cosmos","symbol":"ATOM","type":"BEP20","decimals":18,"asset_id":"c20000714_t0x0Eb3..."}
|
|
||||||
export class TokenTwInfo {
|
|
||||||
name: string;
|
|
||||||
symbol: string;
|
|
||||||
type: string;
|
|
||||||
decimals: number;
|
|
||||||
asset_id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function tokenInfoFromTwApi(assetID: string): Promise<TokenTwInfo> {
|
|
||||||
try {
|
|
||||||
const apiUrl = `https://api.trustwallet.com/v1/assets/${assetID}`;
|
|
||||||
const result = await axios.get(apiUrl).then(r => r.data);
|
|
||||||
if (!result || !result.asset_id) {
|
|
||||||
console.log("Unexpected result", result);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const info = result as TokenTwInfo;
|
|
||||||
return info;
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Exception:", err);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import { CoinType } from "@trustwallet/wallet-core";
|
|
||||||
|
|
||||||
export const getChainName = (id: CoinType): string => CoinType.id(id); // 60 => ethereum
|
|
||||||
export const Binance = getChainName(CoinType.binance);
|
|
||||||
export const Callisto = getChainName(CoinType.callisto);
|
|
||||||
export const Classic = getChainName(CoinType.classic);
|
|
||||||
export const Cosmos = getChainName(CoinType.cosmos);
|
|
||||||
export const EOS = getChainName(CoinType.eos);
|
|
||||||
export const Ethereum = getChainName(CoinType.ethereum);
|
|
||||||
export const GoChain = getChainName(CoinType.gochain);
|
|
||||||
export const IoTeX = getChainName(CoinType.iotex);
|
|
||||||
export const NEO = getChainName(CoinType.neo);
|
|
||||||
export const NULS = getChainName(CoinType.nuls);
|
|
||||||
export const Ontology = getChainName(CoinType.ontology);
|
|
||||||
export const POA = getChainName(CoinType.poa);
|
|
||||||
export const Tezos = getChainName(CoinType.tezos);
|
|
||||||
export const ThunderCore = getChainName(CoinType.thundertoken);
|
|
||||||
export const Terra = getChainName(CoinType.terra);
|
|
||||||
export const Theta = getChainName(CoinType.theta);
|
|
||||||
export const TomoChain = getChainName(CoinType.tomochain);
|
|
||||||
export const Tron = getChainName(CoinType.tron);
|
|
||||||
export const Kava = getChainName(CoinType.kava);
|
|
||||||
export const Vechain = getChainName(CoinType.vechain);
|
|
||||||
export const Wanchain = getChainName(CoinType.wanchain);
|
|
||||||
export const Waves = getChainName(CoinType.waves);
|
|
||||||
export const Solana = getChainName(CoinType.solana);
|
|
||||||
export const SmartChain = getChainName(CoinType.smartchain);
|
|
||||||
export const Polygon = getChainName(CoinType.polygon);
|
|
||||||
export const Optimism = "optimism";
|
|
||||||
export const xDAI = "xdai";
|
|
||||||
export const Avalanche = "avalanchec";
|
|
||||||
export const Arbitrum = "arbitrum";
|
|
||||||
export const Fantom = "fantom";
|
|
||||||
|
|
||||||
export const ethForkChains = [
|
|
||||||
Ethereum,
|
|
||||||
Classic,
|
|
||||||
POA,
|
|
||||||
TomoChain,
|
|
||||||
GoChain,
|
|
||||||
Wanchain,
|
|
||||||
ThunderCore,
|
|
||||||
SmartChain,
|
|
||||||
Polygon,
|
|
||||||
Optimism,
|
|
||||||
xDAI,
|
|
||||||
Avalanche,
|
|
||||||
Arbitrum,
|
|
||||||
Fantom,
|
|
||||||
];
|
|
||||||
export const stakingChains = [
|
|
||||||
Tezos,
|
|
||||||
Cosmos,
|
|
||||||
IoTeX,
|
|
||||||
Tron,
|
|
||||||
Waves,
|
|
||||||
Kava,
|
|
||||||
Terra,
|
|
||||||
Binance
|
|
||||||
];
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import { toChecksumAddress, checkAddressChecksum } from "ethereum-checksum-address";
|
|
||||||
import { reverseCase } from "./types";
|
|
||||||
|
|
||||||
export const isChecksumEthereum = (address: string): boolean => checkAddressChecksum(address);
|
|
||||||
export const toChecksumEthereum = (address: string): string => toChecksumAddress(address);
|
|
||||||
|
|
||||||
export function toChecksum(address: string, chain = "ethereum"): string {
|
|
||||||
const checksumEthereum = toChecksumEthereum(address);
|
|
||||||
|
|
||||||
// special handling for Wanchain
|
|
||||||
if (chain.toLowerCase() === "wanchain") {
|
|
||||||
const checksumWanchain = reverseCase(checksumEthereum).replace("X", "x");
|
|
||||||
return checksumWanchain;
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksumEthereum;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isChecksum(address: string, chain = "ethereum"): boolean {
|
|
||||||
// special handling for Wanchain
|
|
||||||
if (chain.toLowerCase() === "wanchain") {
|
|
||||||
const addressEthereum = reverseCase(address).replace("X", "x");
|
|
||||||
return isChecksumEthereum(addressEthereum);
|
|
||||||
}
|
|
||||||
|
|
||||||
return isChecksumEthereum(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isEthereumAddress(address: string): boolean {
|
|
||||||
if (!(address.length == 40 || (address.length == 42 && address.substring(0, 2) === '0x'))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const check1 = toChecksum(address);
|
|
||||||
if (toChecksum(check1) !== check1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
import { ethForkChains } from "../generic/blockchains";
|
|
||||||
import {
|
|
||||||
getChainAssetsPath,
|
|
||||||
getChainAssetsList,
|
|
||||||
getChainAssetPath,
|
|
||||||
getChainAssetFilesList,
|
|
||||||
logoName,
|
|
||||||
logoExtension,
|
|
||||||
logoFullName,
|
|
||||||
} from "../generic/repo-structure";
|
|
||||||
import {
|
|
||||||
getFileName,
|
|
||||||
getFileExt,
|
|
||||||
gitMove,
|
|
||||||
readDirSync,
|
|
||||||
isPathExistsSync,
|
|
||||||
} from "../generic/filesystem";
|
|
||||||
import { toChecksum } from "../generic/eth-address";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
|
|
||||||
function checkAddressChecksum(assetsFolderPath: string, address: string, chain: string) {
|
|
||||||
const checksumAddress = toChecksum(address, chain);
|
|
||||||
if (checksumAddress !== address) {
|
|
||||||
gitMove(assetsFolderPath, address, checksumAddress);
|
|
||||||
console.log(`Renamed to checksum format ${checksumAddress}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkAddressChecksums() {
|
|
||||||
console.log(`Checking for checksum formats ...`);
|
|
||||||
await bluebird.each(ethForkChains, async (chain) => {
|
|
||||||
const assetsPath = getChainAssetsPath(chain);
|
|
||||||
|
|
||||||
await bluebird.each(readDirSync(assetsPath), async (address) => {
|
|
||||||
await bluebird.each(getChainAssetFilesList(chain, address), async (file) => {
|
|
||||||
if (getFileName(file) == logoName && getFileExt(file) !== logoExtension) {
|
|
||||||
console.log(`Renaming incorrect asset logo extension ${file} ...`);
|
|
||||||
gitMove(getChainAssetPath(chain, address), file, logoFullName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
checkAddressChecksum(assetsPath, address, chain);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EthForks implements ActionInterface {
|
|
||||||
getName(): string { return "Ethereum forks"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
const steps: CheckStepInterface[] = [];
|
|
||||||
ethForkChains.forEach(chain => {
|
|
||||||
steps.push(
|
|
||||||
{
|
|
||||||
getName: () => { return `Folder structure for chain ${chain} (ethereum fork)`;},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const assetsFolder = getChainAssetsPath(chain);
|
|
||||||
if (!isPathExistsSync(assetsFolder)) {
|
|
||||||
console.log(` Found 0 assets for chain ${chain}`);
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
const assetsList = getChainAssetsList(chain);
|
|
||||||
console.log(` Found ${assetsList.length} assets for chain ${chain}`);
|
|
||||||
await bluebird.each(assetsList, async (address) => {
|
|
||||||
const assetPath = `${assetsFolder}/${address}`;
|
|
||||||
if (!isPathExistsSync(assetPath)) {
|
|
||||||
errors.push(`Expect directory at path: ${assetPath}`);
|
|
||||||
}
|
|
||||||
const inChecksum = toChecksum(address, chain);
|
|
||||||
if (address !== inChecksum) {
|
|
||||||
errors.push(`Expect asset at path ${assetPath} in checksum: '${inChecksum}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sanityFix(): Promise<void> {
|
|
||||||
await checkAddressChecksums();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import * as fs from "fs";
|
|
||||||
import * as path from "path";
|
|
||||||
import { execSync } from "child_process";
|
|
||||||
|
|
||||||
export const getFileName = (name: string): string => path.basename(name, path.extname(name))
|
|
||||||
export const getFileExt = (name: string): string => name.slice((Math.max(0, name.lastIndexOf(".")) || Infinity) + 1)
|
|
||||||
|
|
||||||
export const readFileSync = (path: string): string => fs.readFileSync(path, 'utf8');
|
|
||||||
export const writeFileSync = (path: string, data: unknown): void => fs.writeFileSync(path, data);
|
|
||||||
export const readDirSync = (path: string): string[] => fs.readdirSync(path);
|
|
||||||
export const isPathExistsSync = (path: string): boolean => fs.existsSync(path);
|
|
||||||
export const getFileSizeInKilobyte = (path: string): number => fs.statSync(path).size / 1000;
|
|
||||||
|
|
||||||
function execRename(command: string, cwd: string) {
|
|
||||||
console.log(`Running command ${command}`);
|
|
||||||
execSync(command, {encoding: "utf-8", cwd: cwd});
|
|
||||||
}
|
|
||||||
|
|
||||||
function gitMoveCommand(oldName: string, newName: string): string {
|
|
||||||
return `git mv ${oldName} ${newName}-temp && git mv ${newName}-temp ${newName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function gitMove(path: string, oldName: string, newName: string): void {
|
|
||||||
console.log(`Renaming file or folder at path ${path}: ${oldName} => ${newName}`);
|
|
||||||
execRename(gitMoveCommand(oldName, newName), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findFiles(base: string, ext: string, files: string[] = [], result: string[] = []): string[] {
|
|
||||||
files = fs.readdirSync(base) || files;
|
|
||||||
result = result || result;
|
|
||||||
|
|
||||||
files.forEach(file => {
|
|
||||||
const newbase = path.join(base, file);
|
|
||||||
if (fs.statSync(newbase).isDirectory()) {
|
|
||||||
result = findFiles(newbase, ext, fs.readdirSync(newbase), result);
|
|
||||||
} else {
|
|
||||||
if (file.substr(-1*(ext.length+1)) == '.' + ext) {
|
|
||||||
result.push(newbase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
import {
|
|
||||||
readDirSync,
|
|
||||||
isPathExistsSync
|
|
||||||
} from "../generic/filesystem";
|
|
||||||
import { CheckStepInterface, ActionInterface } from "../generic/interface";
|
|
||||||
import {
|
|
||||||
allChains,
|
|
||||||
dappsPath,
|
|
||||||
getChainLogoPath,
|
|
||||||
getChainAssetInfoPath,
|
|
||||||
getChainAssetsPath,
|
|
||||||
getChainAssetPath,
|
|
||||||
getChainAssetLogoPath,
|
|
||||||
assetFolderAllowedFiles,
|
|
||||||
getChainFolderFilesList,
|
|
||||||
chainFolderAllowedFiles,
|
|
||||||
rootDirAllowedFiles
|
|
||||||
} from "../generic/repo-structure";
|
|
||||||
import { isLogoOK } from "../generic/image";
|
|
||||||
import { isLowerCase } from "../generic/types";
|
|
||||||
import { readJsonFile } from "../generic/json";
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
|
|
||||||
export class FoldersFiles implements ActionInterface {
|
|
||||||
getName(): string { return "Folders and Files"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Repository root dir"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const dirActualFiles = readDirSync(".");
|
|
||||||
dirActualFiles.forEach(file => {
|
|
||||||
if (!(rootDirAllowedFiles.indexOf(file) >= 0)) {
|
|
||||||
errors.push(`File "${file}" should not be in root or added to predifined list`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getName: () => { return "Chain folders are lowercase, contain only predefined list of files"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
allChains.forEach(chain => {
|
|
||||||
if (!isLowerCase(chain)) {
|
|
||||||
errors.push(`Chain folder must be in lowercase "${chain}"`);
|
|
||||||
}
|
|
||||||
getChainFolderFilesList(chain).forEach(file => {
|
|
||||||
if (!(chainFolderAllowedFiles.indexOf(file) >= 0)) {
|
|
||||||
errors.push(`File '${file}' not allowed in chain folder: ${chain}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getName: () => { return "Chain folders have logo, and correct size"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
await bluebird.each(allChains, async (chain) => {
|
|
||||||
const chainLogoPath = getChainLogoPath(chain);
|
|
||||||
if (!isPathExistsSync(chainLogoPath)) {
|
|
||||||
errors.push(`File missing at path "${chainLogoPath}"`);
|
|
||||||
}
|
|
||||||
const [isOk, error1] = await isLogoOK(chainLogoPath);
|
|
||||||
if (!isOk) {
|
|
||||||
errors.push(error1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getName: () => { return "Asset folders contain logo and info"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const warnings: string[] = [];
|
|
||||||
allChains.forEach(chain => {
|
|
||||||
const assetsPath = getChainAssetsPath(chain);
|
|
||||||
if (isPathExistsSync(assetsPath)) {
|
|
||||||
readDirSync(assetsPath).forEach(address => {
|
|
||||||
const logoFullPath = getChainAssetLogoPath(chain, address);
|
|
||||||
const logoExists = isPathExistsSync(logoFullPath);
|
|
||||||
const infoFullPath = getChainAssetInfoPath(chain, address);
|
|
||||||
const infoExists = isPathExistsSync(infoFullPath);
|
|
||||||
// Assets should have a logo and an info file. Exceptions:
|
|
||||||
// - status=spam tokens may have no logo
|
|
||||||
// - on some chains some valid tokens have no info (should be fixed)
|
|
||||||
if (!infoExists || !logoExists) {
|
|
||||||
if (!infoExists && logoExists) {
|
|
||||||
const msg = `Missing info file for asset '${chain}/${address}' -- ${infoFullPath}`;
|
|
||||||
// enforce that info must be present
|
|
||||||
console.log(msg);
|
|
||||||
errors.push(msg);
|
|
||||||
}
|
|
||||||
if (!logoExists && infoExists) {
|
|
||||||
// logo must be present, with some exceptions
|
|
||||||
const info: unknown = readJsonFile(infoFullPath);
|
|
||||||
if (!info['status'] || !(info['status'] == 'spam' || info['status'] == 'abandoned')) {
|
|
||||||
const msg = `Missing logo file for non-spam/non-abandoned asset '${chain}/${address}' -- ${logoFullPath}`;
|
|
||||||
console.log(msg);
|
|
||||||
errors.push(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, warnings];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
getName: () => { return "Asset folders contain info.json"},
|
|
||||||
check: async () => {
|
|
||||||
const warnings: string[] = [];
|
|
||||||
allChains.forEach(chain => {
|
|
||||||
const assetsPath = getChainAssetsPath(chain);
|
|
||||||
if (isPathExistsSync(assetsPath)) {
|
|
||||||
readDirSync(assetsPath).forEach(address => {
|
|
||||||
const infoFullPath = getChainAssetInfoPath(chain, address);
|
|
||||||
if (!isPathExistsSync(infoFullPath)) {
|
|
||||||
warnings.push(`Missing info file for asset '${chain}/${address}' -- ${infoFullPath}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [[], warnings];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
getName: () => { return "Asset folders contain only predefined set of files"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
allChains.forEach(chain => {
|
|
||||||
const assetsPath = getChainAssetsPath(chain);
|
|
||||||
if (isPathExistsSync(assetsPath)) {
|
|
||||||
readDirSync(assetsPath).forEach(address => {
|
|
||||||
const assetFiles = getChainAssetPath(chain, address);
|
|
||||||
readDirSync(assetFiles).forEach(assetFolderFile => {
|
|
||||||
if (!(assetFolderAllowedFiles.indexOf(assetFolderFile) >= 0)) {
|
|
||||||
errors.push(`File '${assetFolderFile}' not allowed at this path: ${assetsPath}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getName: () => { return "Dapps folders contain only .png files, with all lowercase names"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
if (isPathExistsSync(dappsPath)) {
|
|
||||||
readDirSync(dappsPath).forEach(filename => {
|
|
||||||
if (!filename.endsWith('.png')) {
|
|
||||||
errors.push(`File '${filename}' has invalid extension; ${dappsPath}`);
|
|
||||||
}
|
|
||||||
if (filename.toLowerCase() != filename) {
|
|
||||||
errors.push(`File '${filename}' is not all-lowercase; ${dappsPath}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
import * as sharp from "sharp";
|
|
||||||
import * as tinify from "tinify";
|
|
||||||
import * as image_size from "image-size";
|
|
||||||
import {
|
|
||||||
writeFileSync,
|
|
||||||
getFileSizeInKilobyte
|
|
||||||
} from "./filesystem";
|
|
||||||
import * as chalk from 'chalk';
|
|
||||||
import * as config from "../config";
|
|
||||||
|
|
||||||
export const minLogoWidth = config.imageMinLogoWidth;
|
|
||||||
export const minLogoHeight = config.imageMinLogoHeight;
|
|
||||||
export const maxLogoWidth = config.imageMaxLogoWidth;
|
|
||||||
export const maxLogoHeight = config.imageMaxLogoHeight;
|
|
||||||
export const maxLogoSizeInKilobyte = config.imageMaxLogoSizeKb;
|
|
||||||
|
|
||||||
export function isDimensionTooLarge(width: number, height: number): boolean {
|
|
||||||
return (width > maxLogoWidth) || (height > maxLogoHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDimensionOK(width: number, height: number): boolean {
|
|
||||||
return (width <= maxLogoWidth) && (height <= maxLogoHeight) &&
|
|
||||||
(width >= minLogoWidth) && (height >= minLogoHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateTargetSize(srcWidth: number, srcHeight: number, targetWidth: number, targetHeight: number): {width: number, height: number} {
|
|
||||||
if (srcWidth == 0 || srcHeight == 0) {
|
|
||||||
return {width: targetWidth, height: targetHeight};
|
|
||||||
}
|
|
||||||
const ratio = Math.min(targetWidth / srcWidth, targetHeight / srcHeight);
|
|
||||||
return {
|
|
||||||
width: Math.round(srcWidth * ratio),
|
|
||||||
height: Math.round(srcHeight * ratio)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// check logo dimensions (pixel) and size (kilobytes)
|
|
||||||
export async function isLogoOK(path: string): Promise<[boolean, string]> {
|
|
||||||
let [isOK, msg] = await isLogoDimensionOK(path);
|
|
||||||
if (!isOK) {
|
|
||||||
return [false, msg];
|
|
||||||
}
|
|
||||||
[isOK, msg] = await isLogoSizeOK(path);
|
|
||||||
if (!isOK) {
|
|
||||||
return [false, msg];
|
|
||||||
}
|
|
||||||
return [true, ""];
|
|
||||||
}
|
|
||||||
|
|
||||||
const getImageDimensions = (path: string) => image_size.imageSize(path);
|
|
||||||
|
|
||||||
async function isLogoDimensionOK(path: string): Promise<[boolean, string]> {
|
|
||||||
const { width, height } = getImageDimensions(path)
|
|
||||||
if (isDimensionOK(width, height)) {
|
|
||||||
return [true, ""];
|
|
||||||
}
|
|
||||||
return [false, `Image at path ${path} must have dimensions: min: ${minLogoWidth}x${minLogoHeight} and max: ${maxLogoWidth}x${maxLogoHeight} instead ${width}x${height}`];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function compressTinyPNG(path: string) {
|
|
||||||
console.log(`Compressing image via tinypng at path ${path}`);
|
|
||||||
const source = await tinify.fromFile(path);
|
|
||||||
await source.toFile(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function isLogoSizeOK(path: string): Promise<[boolean, string]> {
|
|
||||||
const sizeKilobyte = getFileSizeInKilobyte(path);
|
|
||||||
if (sizeKilobyte > maxLogoSizeInKilobyte) {
|
|
||||||
return [false, `Logo ${path} is too large, ${sizeKilobyte} kB instead of ${maxLogoSizeInKilobyte}`];
|
|
||||||
}
|
|
||||||
return [true, ''];
|
|
||||||
}
|
|
||||||
|
|
||||||
// return if image if too large, and if image has been updated
|
|
||||||
export async function checkResizeIfTooLarge(path: string, checkOnly: boolean): Promise<[boolean, boolean]> {
|
|
||||||
let tooLarge = false;
|
|
||||||
let updated = false;
|
|
||||||
|
|
||||||
const { width: srcWidth, height: srcHeight } = getImageDimensions(path);
|
|
||||||
|
|
||||||
if (!isDimensionOK(srcWidth, srcHeight)) {
|
|
||||||
tooLarge = true; // may be too small as well
|
|
||||||
console.log(`Wrong image dimensions, ${srcWidth}x${srcHeight}, ${path}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDimensionTooLarge(srcWidth, srcHeight)) {
|
|
||||||
tooLarge = true;
|
|
||||||
console.log(`Image too large, ${srcWidth}x${srcHeight}, ${path}`);
|
|
||||||
if (!checkOnly) {
|
|
||||||
// resize
|
|
||||||
const { width, height } = calculateTargetSize(srcWidth, srcHeight, maxLogoWidth, maxLogoHeight);
|
|
||||||
console.log(`Resizing image at ${path} from ${srcWidth}x${srcHeight} => ${width}x${height}`)
|
|
||||||
await sharp(path).resize(width, height).toBuffer()
|
|
||||||
.then(data => {
|
|
||||||
writeFileSync(path, data);
|
|
||||||
updated = true;
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(chalk.red(e.message));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If file size > max limit, compress with tinypng
|
|
||||||
const sizeKilobyte = getFileSizeInKilobyte(path);
|
|
||||||
if (sizeKilobyte > maxLogoSizeInKilobyte) {
|
|
||||||
tooLarge = true;
|
|
||||||
console.log(`Image too big, ${sizeKilobyte} kb, ${path}`);
|
|
||||||
if (!checkOnly) {
|
|
||||||
console.log(`Resizing image at path ${path} from ${sizeKilobyte} kB`);
|
|
||||||
await compressTinyPNG(path)
|
|
||||||
.then(() => {
|
|
||||||
updated = true;
|
|
||||||
console.log(`Resized image at path ${path} from ${sizeKilobyte} kB => ${getFileSizeInKilobyte(path)} kB`);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(chalk.red(e.message));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [tooLarge, updated];
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// A single check step
|
|
||||||
export interface CheckStepInterface {
|
|
||||||
getName(): string;
|
|
||||||
// return [errors, warnings]
|
|
||||||
check(): Promise<[string[], string[]]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// An action for a check, fix, or update, or a combination.
|
|
||||||
export interface ActionInterface {
|
|
||||||
getName(): string;
|
|
||||||
// return check steps for sanity check (0, 1, or more)
|
|
||||||
getSanityChecks(): CheckStepInterface[];
|
|
||||||
// return check steps for consistenct check (0, 1, or more)
|
|
||||||
getConsistencyChecks?(): CheckStepInterface[];
|
|
||||||
sanityFix?(): Promise<void>;
|
|
||||||
consistencyFix?(): Promise<void>;
|
|
||||||
updateAuto?(): Promise<void>; // For regular automatic updates (from external source)
|
|
||||||
updateManual?(): Promise<void>; // For occasional manual updates (from external source)
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum FixCheckMode {
|
|
||||||
CheckSanityOnly = 1,
|
|
||||||
CheckAll,
|
|
||||||
FixSanityOnly,
|
|
||||||
FixAll
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import { chainsPath } from "../generic/repo-structure";
|
|
||||||
import { findFiles } from "../generic/filesystem";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { formatJsonFile, isValidJSON } from "../generic/json";
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
|
|
||||||
async function formatInfos(): Promise<void> {
|
|
||||||
console.log(`Formatting json files...`);
|
|
||||||
|
|
||||||
const files = [
|
|
||||||
...findFiles(chainsPath, 'json'),
|
|
||||||
];
|
|
||||||
let count1 = 0;
|
|
||||||
let count2 = 0;
|
|
||||||
await bluebird.each(files, async (file) => {
|
|
||||||
if (formatJsonFile(file)) {
|
|
||||||
++count2;
|
|
||||||
}
|
|
||||||
++count1;
|
|
||||||
});
|
|
||||||
console.log(`Formatted ${count2} json files (total ${count1})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JsonAction implements ActionInterface {
|
|
||||||
getName(): string { return "Json files"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Check all JSON files to have valid content"},
|
|
||||||
check: async () => {
|
|
||||||
const errors: string[] = [];
|
|
||||||
const files = [
|
|
||||||
...findFiles(chainsPath, 'json'),
|
|
||||||
];
|
|
||||||
|
|
||||||
await bluebird.each(files, async file => {
|
|
||||||
if (!isValidJSON(file)) {
|
|
||||||
errors.push(`${file} path contains invalid JSON`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async sanityFix(): Promise<void> {
|
|
||||||
await formatInfos();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import {
|
|
||||||
readFileSync,
|
|
||||||
writeFileSync
|
|
||||||
} from "./filesystem";
|
|
||||||
import { sortElements } from "./types";
|
|
||||||
|
|
||||||
export function isValidJSON(path: string): boolean {
|
|
||||||
try {
|
|
||||||
const rawdata = readFileSync(path);
|
|
||||||
JSON.parse(rawdata);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatJson(content: unknown): string {
|
|
||||||
return JSON.stringify(content, null, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatSortJson(content: unknown[]): string {
|
|
||||||
return JSON.stringify(sortElements(content), null, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return if updated
|
|
||||||
export function formatJsonFile(filename: string): boolean {
|
|
||||||
const origText: string = readFileSync(filename);
|
|
||||||
const newText: string = formatJson(JSON.parse(origText));
|
|
||||||
if (newText == origText) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
writeFileSync(filename, newText);
|
|
||||||
console.log(`Formatted json file ${filename}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatSortJsonFile(filename: string): void {
|
|
||||||
writeFileSync(filename, formatSortJson(JSON.parse(readFileSync(filename))));
|
|
||||||
console.log(`Formatted json file ${filename}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readJsonFile(path: string): unknown {
|
|
||||||
return JSON.parse(readFileSync(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function writeJsonFile(path: string, data: unknown): void {
|
|
||||||
writeFileSync(path, JSON.stringify(data, null, 4));
|
|
||||||
}
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
import {
|
|
||||||
allChains,
|
|
||||||
getChainLogoPath,
|
|
||||||
getChainAssetsPath,
|
|
||||||
getChainAssetLogoPath,
|
|
||||||
getChainValidatorsListPath,
|
|
||||||
getChainValidatorAssetLogoPath,
|
|
||||||
dappsPath
|
|
||||||
} from "../generic/repo-structure";
|
|
||||||
import {
|
|
||||||
readDirSync,
|
|
||||||
readFileSync,
|
|
||||||
isPathExistsSync
|
|
||||||
} from "../generic/filesystem";
|
|
||||||
import { checkResizeIfTooLarge } from "../generic/image";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
|
|
||||||
// return name of large logo, or empty
|
|
||||||
async function checkDownsize(chains: string[], checkOnly: boolean): Promise<string[]> {
|
|
||||||
console.log(`Checking all logos for size ...`);
|
|
||||||
let totalCountChecked = 0;
|
|
||||||
let totalCountTooLarge = 0;
|
|
||||||
let totalCountUpdated = 0;
|
|
||||||
const largePaths: string[] = [];
|
|
||||||
|
|
||||||
// Check asset logos, under given chains
|
|
||||||
await bluebird.map(chains, async chain => {
|
|
||||||
let countChecked = 0;
|
|
||||||
let countTooLarge = 0;
|
|
||||||
let countUpdated = 0;
|
|
||||||
|
|
||||||
const path = getChainLogoPath(chain);
|
|
||||||
countChecked++;
|
|
||||||
const [tooLarge, updated] = await checkResizeIfTooLarge(path, checkOnly);
|
|
||||||
if (tooLarge) { largePaths.push(path); }
|
|
||||||
countTooLarge += tooLarge ? 1 : 0;
|
|
||||||
countUpdated += updated ? 1 : 0;
|
|
||||||
|
|
||||||
// Check and resize if needed chain assets
|
|
||||||
const assetsPath = getChainAssetsPath(chain);
|
|
||||||
if (isPathExistsSync(assetsPath)) {
|
|
||||||
await bluebird.mapSeries(readDirSync(assetsPath), async asset => {
|
|
||||||
const path = getChainAssetLogoPath(chain, asset);
|
|
||||||
if (isPathExistsSync(path)) {
|
|
||||||
countChecked++;
|
|
||||||
const [tooLarge, updated] = await checkResizeIfTooLarge(path, checkOnly);
|
|
||||||
if (tooLarge) { largePaths.push(path); }
|
|
||||||
countTooLarge += tooLarge ? 1 : 0;
|
|
||||||
countUpdated += updated ? 1 : 0;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check and resize if needed chain validators image
|
|
||||||
const chainValidatorsList = getChainValidatorsListPath(chain);
|
|
||||||
if (isPathExistsSync(chainValidatorsList)) {
|
|
||||||
const validatorsList = JSON.parse(readFileSync(getChainValidatorsListPath(chain)));
|
|
||||||
await bluebird.mapSeries(validatorsList, async ({ id }) => {
|
|
||||||
const path = getChainValidatorAssetLogoPath(chain, id);
|
|
||||||
countChecked++;
|
|
||||||
const [tooLarge, updated] = await checkResizeIfTooLarge(path, checkOnly);
|
|
||||||
if (tooLarge) { largePaths.push(path); }
|
|
||||||
countTooLarge += tooLarge ? 1 : 0;
|
|
||||||
countUpdated += updated ? 1 : 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
totalCountChecked += countChecked;
|
|
||||||
totalCountTooLarge += countTooLarge;
|
|
||||||
totalCountUpdated += countUpdated;
|
|
||||||
if (countTooLarge > 0 || countUpdated > 0) {
|
|
||||||
console.log(`Checking logos on chain ${chain} completed, ${countChecked} checked, ${countTooLarge} too large, ${largePaths}, ${countUpdated} logos updated`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check dapps logos
|
|
||||||
if (isPathExistsSync(dappsPath)) {
|
|
||||||
let countChecked = 0;
|
|
||||||
let countTooLarge = 0;
|
|
||||||
let countUpdated = 0;
|
|
||||||
|
|
||||||
await bluebird.mapSeries(readDirSync(dappsPath), async filename => {
|
|
||||||
const path = dappsPath + `/` + filename;
|
|
||||||
countChecked++;
|
|
||||||
const [tooLarge, updated] = await checkResizeIfTooLarge(path, checkOnly);
|
|
||||||
if (tooLarge) { largePaths.push(path); }
|
|
||||||
countTooLarge += tooLarge ? 1 : 0;
|
|
||||||
countUpdated += updated ? 1 : 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
totalCountChecked += countChecked;
|
|
||||||
totalCountTooLarge += countTooLarge;
|
|
||||||
totalCountUpdated += countUpdated;
|
|
||||||
if (countTooLarge > 0 || countUpdated > 0) {
|
|
||||||
console.log(`Checking dapps logos completed, ${countChecked} checked, ${countTooLarge} too large, ${largePaths}, ${countUpdated} logos updated`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Checking logos completed, ${totalCountChecked} logos checked, ${totalCountTooLarge} too large, ${totalCountUpdated} logos updated`);
|
|
||||||
return largePaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LogoSize implements ActionInterface {
|
|
||||||
getName(): string { return "Logo sizes"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
getName: () => { return "Check that logos are not too large"},
|
|
||||||
check: async () => {
|
|
||||||
const largePaths = await checkDownsize(allChains, true);
|
|
||||||
const errors: string[] = largePaths.map(p => `Logo too large: ${p}`);
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async sanityFix(): Promise<void> {
|
|
||||||
await checkDownsize(allChains, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import BigNumber from "bignumber.js";
|
|
||||||
|
|
||||||
export function toSatoshis(value: string, decimals: number): string {
|
|
||||||
return new BigNumber(value).multipliedBy(new BigNumber(10).exponentiatedBy(decimals)).toFixed()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fromSatoshis(value: string, decimals: number): string {
|
|
||||||
return new BigNumber(value).dividedBy(new BigNumber(10).exponentiatedBy(decimals)).toFixed()
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import * as path from "path";
|
|
||||||
import {
|
|
||||||
isPathExistsSync,
|
|
||||||
readDirSync
|
|
||||||
} from "./filesystem";
|
|
||||||
import * as config from "../config";
|
|
||||||
|
|
||||||
export const logoName = `logo`;
|
|
||||||
export const infoName = `info`;
|
|
||||||
export const listName = `list`
|
|
||||||
export const logoExtension = "png";
|
|
||||||
export const jsonExtension = "json";
|
|
||||||
export const logoFullName = `${logoName}.${logoExtension}`;
|
|
||||||
export const infoFullName = `${infoName}.${jsonExtension}`;
|
|
||||||
const tokenList = `tokenlist.${jsonExtension}`;
|
|
||||||
export const validatorsList = `${listName}.${jsonExtension}`
|
|
||||||
|
|
||||||
export const assetFolderAllowedFiles = [logoFullName, infoFullName];
|
|
||||||
export const chainFolderAllowedFiles = [
|
|
||||||
"assets",
|
|
||||||
tokenList,
|
|
||||||
"validators",
|
|
||||||
infoName
|
|
||||||
]
|
|
||||||
export const chainsPath: string = path.join(process.cwd(), '/blockchains');
|
|
||||||
export const getChainPath = (chain: string): string => `${chainsPath}/${chain}`;
|
|
||||||
export const allChains = readDirSync(chainsPath);
|
|
||||||
export const getChainInfoPath = (chain: string): string => `${getChainPath(chain)}/info`;
|
|
||||||
export const getChainLogoPath = (chain: string): string => `${getChainInfoPath(chain)}/${logoFullName}`;
|
|
||||||
export const getChainCoinInfoPath = (chain: string): string => `${getChainInfoPath(chain)}/${infoFullName}`;
|
|
||||||
export const getChainAssetsPath = (chain: string): string => `${getChainPath(chain)}/assets`;
|
|
||||||
export const getChainAssetPath = (chain: string, asset: string): string => `${getChainAssetsPath(chain)}/${asset}`;
|
|
||||||
export const getChainAssetLogoPath = (chain: string, asset: string): string => `${getChainAssetPath(chain, asset)}/${logoFullName}`;
|
|
||||||
export const getChainAssetInfoPath = (chain: string, asset: string): string => `${getChainAssetPath(chain, asset)}/${infoFullName}`;
|
|
||||||
export const getChainTokenlistPath = (chain: string): string => `${getChainPath(chain)}/${tokenList}`;
|
|
||||||
export const pricingFolderPath = path.join(process.cwd(), '/pricing');
|
|
||||||
|
|
||||||
export const getChainValidatorsPath = (chain: string): string => `${getChainPath(chain)}/validators`;
|
|
||||||
export const getChainValidatorsListPath = (chain: string): string => `${getChainValidatorsPath(chain)}/${validatorsList}`;
|
|
||||||
export const getChainValidatorsAssetsPath = (chain: string): string => `${getChainValidatorsPath(chain)}/assets`
|
|
||||||
export const getChainValidatorAssetLogoPath = (chain: string, asset: string): string => `${getChainValidatorsAssetsPath(chain)}/${asset}/${logoFullName}`
|
|
||||||
|
|
||||||
export const isChainAssetInfoExistSync = (chain: string, address: string): boolean => isPathExistsSync(getChainAssetInfoPath(chain, address));
|
|
||||||
|
|
||||||
export const getChainFolderFilesList = (chain: string): string[] => readDirSync(getChainPath(chain))
|
|
||||||
export const getChainAssetsList = (chain: string): string[] => readDirSync(getChainAssetsPath(chain));
|
|
||||||
export const getChainAssetFilesList = (chain: string, address: string): string[] => readDirSync(getChainAssetPath(chain, address));
|
|
||||||
export const getChainValidatorsAssets = (chain: string): string[] => readDirSync(getChainValidatorsAssetsPath(chain));
|
|
||||||
|
|
||||||
export const dappsPath: string = path.join(process.cwd(), '/dapps');
|
|
||||||
|
|
||||||
export const rootDirAllowedFiles = config.foldersRootdirAllowedFiles;
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// see https://developer.trustwallet.com/add_new_asset
|
|
||||||
const StatusValues: string[] = [
|
|
||||||
'active',
|
|
||||||
'spam',
|
|
||||||
'abandoned'
|
|
||||||
];
|
|
||||||
|
|
||||||
export function isValidStatusValue(value: string): boolean {
|
|
||||||
if (!value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!StatusValues.find(e => e === value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
// Interfacing with TheGraph subgraph APIs
|
|
||||||
|
|
||||||
import axios from "axios";
|
|
||||||
import { ForceListPair, matchPairToForceList, TokenItem } from "./tokenlists";
|
|
||||||
|
|
||||||
export interface TokenInfo {
|
|
||||||
id: string;
|
|
||||||
symbol: string;
|
|
||||||
name: string;
|
|
||||||
decimals: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PairInfo {
|
|
||||||
id: string;
|
|
||||||
reserveUSD: number;
|
|
||||||
volumeUSD: number;
|
|
||||||
txCount: number;
|
|
||||||
token0: TokenInfo;
|
|
||||||
token1: TokenInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TokenInfoBitquery {
|
|
||||||
address: string;
|
|
||||||
symbol: string;
|
|
||||||
name: string;
|
|
||||||
decimals: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PairInfoBitquery {
|
|
||||||
sellCurrency: TokenInfoBitquery;
|
|
||||||
buyCurrency: TokenInfoBitquery;
|
|
||||||
trade: number; // trade count
|
|
||||||
tradeAmount: number; // trade volume usd
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTradingPairs(apiUrl: string, subgraphQuery: string): Promise<unknown[]> {
|
|
||||||
// compact the query string
|
|
||||||
const compactQuery = subgraphQuery.replace(/(?:\r\n|\r|\n)/g, ' ').replace(/\s[\s]+/g, ' ');
|
|
||||||
const postData = '{"operationName":"pairs", "variables":{}, "query":"' + compactQuery + '"}';
|
|
||||||
|
|
||||||
console.log(`Retrieving trading pair infos from: ${apiUrl}`);
|
|
||||||
try {
|
|
||||||
const result = await axios.post(apiUrl, postData).then(r => r.data);
|
|
||||||
if (!result || !result.data || !result.data.pairs) {
|
|
||||||
throw `Unexpected result: ${JSON.stringify(result)}`;
|
|
||||||
}
|
|
||||||
const pairs = result.data.pairs;
|
|
||||||
console.log(`Retrieved ${pairs.length} trading pair infos`);
|
|
||||||
return pairs;
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Exception from graph api:", err, apiUrl, JSON.stringify(postData));
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTradingPairsBitquery(apiUrl: string, subgraphQuery: string): Promise<unknown[]> {
|
|
||||||
// compact the query string
|
|
||||||
const compactQuery = subgraphQuery.replace(/(?:\r\n|\r|\n)/g, ' ').replace(/\s[\s]+/g, ' ');
|
|
||||||
const postData = {"query": compactQuery, "variables":{}};
|
|
||||||
|
|
||||||
console.log(`Retrieving trading pair infos from: ${apiUrl}`);
|
|
||||||
try {
|
|
||||||
const result = await axios.post(apiUrl, postData).then(r => r.data);
|
|
||||||
if (!result || !result.data || !result.data.ethereum || !result.data.ethereum.dexTrades) {
|
|
||||||
throw `Unexpected result: ${JSON.stringify(result)}`;
|
|
||||||
}
|
|
||||||
const pairs = result.data.ethereum.dexTrades;
|
|
||||||
console.log(`Retrieved ${pairs.length} trading pair infos`);
|
|
||||||
return pairs;
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Exception from graph api:", err, apiUrl, JSON.stringify(postData));
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTokenPrimary(token: TokenInfo, primaryTokens: string[]): boolean {
|
|
||||||
return (primaryTokens.find(t => (t === token.symbol.toUpperCase())) != undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check which token of the pair is the primary token, 1st, or 2nd, or 0 for none
|
|
||||||
export function primaryTokenIndex(pair: PairInfo, primaryTokens: string[]): number {
|
|
||||||
if (isTokenPrimary(pair.token0, primaryTokens)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (isTokenPrimary(pair.token1, primaryTokens)) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenItemFromInfo(tokenInfo: TokenInfo): TokenItem {
|
|
||||||
return new TokenItem(tokenInfo.id, '', tokenInfo.id, tokenInfo.name, tokenInfo.symbol, tokenInfo.decimals, '', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify a trading pair, whether we support the tokens, has enough liquidity, etc.
|
|
||||||
export function checkTradingPair(pair: PairInfo, minLiquidity: number, minVol24: number, minTxCount24: number, primaryTokens: string[], forceIncludeList: ForceListPair[]): boolean {
|
|
||||||
if (!pair.id || !pair.reserveUSD || !pair.volumeUSD || !pair.txCount || !pair.token0 || !pair.token1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (primaryTokenIndex(pair, primaryTokens) == 0) {
|
|
||||||
console.log("pair with no primary coin:", pair.token0.symbol, "--", pair.token1.symbol);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchPairToForceList(tokenItemFromInfo(pair.token0), tokenItemFromInfo(pair.token1), forceIncludeList)) {
|
|
||||||
console.log("pair included due to FORCE INCLUDE:", pair.token0.symbol, "--", pair.token1.symbol, " ", Math.round(pair.reserveUSD));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pair.reserveUSD < minLiquidity) {
|
|
||||||
console.log("pair with low liquidity:", pair.token0.symbol, "--", pair.token1.symbol, " ", Math.round(pair.reserveUSD));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pair.volumeUSD < minVol24) {
|
|
||||||
console.log("pair with low volume:", pair.token0.symbol, "--", pair.token1.symbol, " ", Math.round(pair.volumeUSD));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pair.txCount < minTxCount24) {
|
|
||||||
console.log("pair with low tx count:", pair.token0.symbol, "--", pair.token1.symbol, " ", pair.txCount);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//console.log("pair:", pair.token0.symbol, "--", pair.token1.symbol, " ", pair.reserveUSD);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { TagValues } from "../tags-config";
|
|
||||||
|
|
||||||
export function isValidTagValue(value: string): boolean {
|
|
||||||
//console.log(`isValidTagValue ${value}`);
|
|
||||||
if (!value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const tag = TagValues.find(t => t.id === value);
|
|
||||||
if (!tag) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//console.log(`TAG ${tag.name}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isValidTagValues(values: string[]): boolean {
|
|
||||||
return values.reduce((accum: boolean, value: string) => accum && isValidTagValue(value), true);
|
|
||||||
}
|
|
||||||
|
|
@ -1,343 +0,0 @@
|
||||||
// Handling of tokenlist.json files, tokens and trading pairs.
|
|
||||||
|
|
||||||
import { readJsonFile, writeJsonFile } from "../generic/json";
|
|
||||||
import { diff } from "jsondiffpatch";
|
|
||||||
import { tokenInfoFromTwApi, TokenTwInfo } from "../generic/asset";
|
|
||||||
import {
|
|
||||||
getChainAssetLogoPath,
|
|
||||||
getChainTokenlistPath,
|
|
||||||
} from "../generic/repo-structure";
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
import { isPathExistsSync } from "../generic/filesystem";
|
|
||||||
|
|
||||||
class Version {
|
|
||||||
major: number
|
|
||||||
minor: number
|
|
||||||
patch: number
|
|
||||||
|
|
||||||
constructor(major: number, minor: number, patch: number) {
|
|
||||||
this.major = major
|
|
||||||
this.minor = minor
|
|
||||||
this.patch = patch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class List {
|
|
||||||
name: string
|
|
||||||
logoURI: string
|
|
||||||
timestamp: string
|
|
||||||
tokens: TokenItem[]
|
|
||||||
pairs: Pair[]
|
|
||||||
version: Version
|
|
||||||
|
|
||||||
constructor(name: string, logoURI: string, timestamp: string, tokens: TokenItem[], version: Version) {
|
|
||||||
this.name = name
|
|
||||||
this.logoURI = logoURI
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
this.tokens = tokens
|
|
||||||
this.version = version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TokenItem {
|
|
||||||
asset: string;
|
|
||||||
type: string;
|
|
||||||
address: string;
|
|
||||||
name: string;
|
|
||||||
symbol: string;
|
|
||||||
decimals: number;
|
|
||||||
logoURI: string;
|
|
||||||
pairs: Pair[];
|
|
||||||
|
|
||||||
constructor(asset: string, type: string, address: string, name: string, symbol: string, decimals: number, logoURI: string, pairs: Pair[]) {
|
|
||||||
this.asset = asset
|
|
||||||
this.type = type
|
|
||||||
this.address = address
|
|
||||||
this.name = name;
|
|
||||||
this.symbol = symbol
|
|
||||||
this.decimals = decimals
|
|
||||||
this.logoURI = logoURI
|
|
||||||
this.pairs = pairs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Pair {
|
|
||||||
base: string;
|
|
||||||
lotSize?: string;
|
|
||||||
tickSize?: string;
|
|
||||||
|
|
||||||
constructor(base: string, lotSize?: string, tickSize?: string) {
|
|
||||||
this.base = base
|
|
||||||
this.lotSize = lotSize
|
|
||||||
this.tickSize = tickSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///// Exclude/Include list token/pair matching
|
|
||||||
|
|
||||||
// A token or pair in the force exclude/include list
|
|
||||||
export class ForceListPair {
|
|
||||||
token1: string;
|
|
||||||
// second is optional, if empty --> token only, if set --> pair
|
|
||||||
token2: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseForceListEntry(rawForceListEntry: string): ForceListPair {
|
|
||||||
const pair: ForceListPair = new ForceListPair();
|
|
||||||
const tokens: string[] = rawForceListEntry.split("-");
|
|
||||||
pair.token1 = tokens[0];
|
|
||||||
pair.token2 = "";
|
|
||||||
if (tokens.length >= 2) {
|
|
||||||
pair.token2 = tokens[1];
|
|
||||||
}
|
|
||||||
return pair;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseForceList(rawForceList: string[]): ForceListPair[] {
|
|
||||||
return rawForceList.map(e => parseForceListEntry(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function matchTokenToForceListEntry(token: TokenItem, forceListEntry: string): boolean {
|
|
||||||
if (forceListEntry.toLowerCase() === token.symbol.toLowerCase() ||
|
|
||||||
forceListEntry.toLowerCase() === token.asset.toLowerCase() ||
|
|
||||||
forceListEntry.toLowerCase() === token.name.toLowerCase()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function matchPairToForceListEntry(token1: TokenItem, token2: TokenItem, forceListEntry: ForceListPair): boolean {
|
|
||||||
if (!forceListEntry.token2) {
|
|
||||||
// entry is token only
|
|
||||||
if (matchTokenToForceListEntry(token1, forceListEntry.token1) ||
|
|
||||||
(token2 && matchTokenToForceListEntry(token2, forceListEntry.token1))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// entry is pair
|
|
||||||
if (!token2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (matchTokenToForceListEntry(token1, forceListEntry.token1) && matchTokenToForceListEntry(token2, forceListEntry.token2)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// reverse
|
|
||||||
if (matchTokenToForceListEntry(token1, forceListEntry.token2) && matchTokenToForceListEntry(token2, forceListEntry.token1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function matchTokenToForceList(token: TokenItem, forceList: ForceListPair[]): boolean {
|
|
||||||
let matched = false;
|
|
||||||
forceList.forEach(e => {
|
|
||||||
if (matchTokenToForceListEntry(token, e.token1)) {
|
|
||||||
matched = true;
|
|
||||||
}
|
|
||||||
if (matchTokenToForceListEntry(token, e.token2)) {
|
|
||||||
matched = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return matched;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function matchPairToForceList(token1: TokenItem, token2: TokenItem, forceList: ForceListPair[]): boolean {
|
|
||||||
let matched = false;
|
|
||||||
forceList.forEach(p => {
|
|
||||||
if (matchPairToForceListEntry(token1, token2, p)) {
|
|
||||||
matched = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return matched;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////
|
|
||||||
|
|
||||||
export function createTokensList(titleCoin: string, tokens: TokenItem[], time: string, versionMajor: number, versionMinor = 1, versionPatch = 0): List {
|
|
||||||
if (!time) {
|
|
||||||
time = (new Date()).toISOString();
|
|
||||||
}
|
|
||||||
const list = new List(
|
|
||||||
`Trust Wallet: ${titleCoin}`,
|
|
||||||
"https://trustwallet.com/assets/images/favicon.png",
|
|
||||||
time,
|
|
||||||
tokens,
|
|
||||||
new Version(versionMajor, versionMinor, versionPatch)
|
|
||||||
);
|
|
||||||
sort(list);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
function totalPairs(list: List): number {
|
|
||||||
let c = 0;
|
|
||||||
list.tokens.forEach(t => c += (t.pairs || []).length);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function writeToFile(filename: string, list: List): void {
|
|
||||||
writeJsonFile(filename, list);
|
|
||||||
console.log(`Tokenlist: list with ${list.tokens.length} tokens and ${totalPairs(list)} pairs written to ${filename}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write out to file, updating version+timestamp if there was change
|
|
||||||
export function writeToFileWithUpdate(filename: string, list: List): void {
|
|
||||||
let listOld: List = undefined;
|
|
||||||
try {
|
|
||||||
listOld = readJsonFile(filename) as List;
|
|
||||||
} catch (err) {
|
|
||||||
listOld = undefined;
|
|
||||||
}
|
|
||||||
if (listOld !== undefined) {
|
|
||||||
list.version = listOld.version; // take over
|
|
||||||
list.timestamp = listOld.timestamp; // take over
|
|
||||||
const diffs = diffTokenlist(list, listOld);
|
|
||||||
if (diffs != undefined) {
|
|
||||||
//console.log("List has Changed", JSON.stringify(diffs));
|
|
||||||
list.version = new Version(list.version.major + 1, 0, 0);
|
|
||||||
list.timestamp = (new Date()).toISOString();
|
|
||||||
console.log(`Version and timestamp updated, ${list.version.major}.${list.version.minor}.${list.version.patch} timestamp ${list.timestamp}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeToFile(filename, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addTokenIfNeeded(token: TokenItem, list: List): Promise<void> {
|
|
||||||
if (list.tokens.map(t => t.address.toLowerCase()).includes(token.address.toLowerCase())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
token = await updateTokenInfo(token);
|
|
||||||
list.tokens.push(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update/fix token info, with properties retrieved from TW API
|
|
||||||
async function updateTokenInfo(token: TokenItem): Promise<TokenItem> {
|
|
||||||
const tokenInfo: TokenTwInfo = await tokenInfoFromTwApi(token.asset);
|
|
||||||
if (tokenInfo) {
|
|
||||||
if (token.name && token.name != tokenInfo.name) {
|
|
||||||
console.log(`Token name adjusted: '${token.name}' -> '${tokenInfo.name}'`);
|
|
||||||
token.name = tokenInfo.name;
|
|
||||||
}
|
|
||||||
if (token.symbol && token.symbol != tokenInfo.symbol) {
|
|
||||||
console.log(`Token symbol adjusted: '${token.symbol}' -> '${tokenInfo.symbol}'`);
|
|
||||||
token.symbol = tokenInfo.symbol;
|
|
||||||
}
|
|
||||||
if (token.decimals && token.decimals != tokenInfo.decimals) {
|
|
||||||
console.log(`Token decimals adjusted: '${token.decimals}' -> '${tokenInfo.decimals}'`);
|
|
||||||
token.decimals = parseInt(tokenInfo.decimals.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPairToToken(pairToken: TokenItem, token: TokenItem, list: List): void {
|
|
||||||
const tokenInList = list.tokens.find(t => t.address === token.address);
|
|
||||||
if (!tokenInList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!tokenInList.pairs) {
|
|
||||||
tokenInList.pairs = [];
|
|
||||||
}
|
|
||||||
if (tokenInList.pairs.map(p => p.base).includes(pairToken.asset)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tokenInList.pairs.push(new Pair(pairToken.asset));
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkTokenExists(id: string, chainName: string): boolean {
|
|
||||||
const logoPath = getChainAssetLogoPath(chainName, id);
|
|
||||||
if (!isPathExistsSync(logoPath)) {
|
|
||||||
//console.log("logo file missing", logoPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addPairIfNeeded(token0: TokenItem, token1: TokenItem, list: List): Promise<void> {
|
|
||||||
await addTokenIfNeeded(token0, list);
|
|
||||||
await addTokenIfNeeded(token1, list);
|
|
||||||
addPairToToken(token1, token0, list);
|
|
||||||
// reverse direction not needed addPairToToken(token0, token1, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort(list: List) {
|
|
||||||
list.tokens.sort((t1, t2) => {
|
|
||||||
const t1pairs = (t1.pairs || []).length;
|
|
||||||
const t2pairs = (t2.pairs || []).length;
|
|
||||||
if (t1pairs != t2pairs) { return t2pairs - t1pairs; }
|
|
||||||
return t1.address.localeCompare(t2.address);
|
|
||||||
});
|
|
||||||
list.tokens.forEach(t => {
|
|
||||||
t.pairs.sort((p1, p2) => p1.base.localeCompare(p2.base));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeAllPairs(list: List) {
|
|
||||||
// remove all pairs
|
|
||||||
list.pairs = [];
|
|
||||||
list.tokens.forEach(t => t.pairs = []);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearUnimportantFields(list: List) {
|
|
||||||
list.timestamp = "";
|
|
||||||
list.version = new Version(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function diffTokenlist(listOrig1: List, listOrig2: List): unknown {
|
|
||||||
// deep copy, to avoid changes
|
|
||||||
const list1 = JSON.parse(JSON.stringify(listOrig1));
|
|
||||||
const list2 = JSON.parse(JSON.stringify(listOrig2));
|
|
||||||
clearUnimportantFields(list1);
|
|
||||||
clearUnimportantFields(list2);
|
|
||||||
sort(list1);
|
|
||||||
sort(list2);
|
|
||||||
// compare
|
|
||||||
const diffs = diff(list1, list2);
|
|
||||||
return diffs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function rebuildTokenlist(chainName: string, pairs: [TokenItem, TokenItem][], listName: string, forceExcludeList: string[]): Promise<void> {
|
|
||||||
// sanity check, prevent deletion of many pairs
|
|
||||||
if (!pairs || pairs.length < 5) {
|
|
||||||
console.log(`Warning: Only ${pairs.length} pairs returned, ignoring`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const excludeList = parseForceList(forceExcludeList);
|
|
||||||
// filter out pairs with missing and excluded tokens
|
|
||||||
// prepare phase
|
|
||||||
const pairs2: [TokenItem, TokenItem][] = [];
|
|
||||||
pairs.forEach(p => {
|
|
||||||
if (!checkTokenExists(p[0].address, chainName)) {
|
|
||||||
console.log("pair with unsupported 1st coin:", p[0].symbol, "--", p[1].symbol);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!checkTokenExists(p[1].address, chainName)) {
|
|
||||||
console.log("pair with unsupported 2nd coin:", p[0].symbol, "--", p[1].symbol);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (matchPairToForceList(p[0], p[1], excludeList)) {
|
|
||||||
console.log("pair excluded due to FORCE EXCLUDE:", p[0].symbol, "--", p[1].symbol);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pairs2.push(p);
|
|
||||||
});
|
|
||||||
const filteredCount: number = pairs.length - pairs2.length;
|
|
||||||
console.log(`${filteredCount} unsupported tokens filtered out, ${pairs2.length} pairs`);
|
|
||||||
|
|
||||||
const tokenlistFile = getChainTokenlistPath(chainName);
|
|
||||||
const json = readJsonFile(tokenlistFile);
|
|
||||||
const list: List = json as List;
|
|
||||||
console.log(`Tokenlist original: ${list.tokens.length} tokens`);
|
|
||||||
removeAllPairs(list);
|
|
||||||
|
|
||||||
await bluebird.each(pairs2, async (p) => {
|
|
||||||
await addPairIfNeeded(p[0], p[1], list);
|
|
||||||
});
|
|
||||||
console.log(`Tokenlist updated: ${list.tokens.length} tokens`);
|
|
||||||
|
|
||||||
const newList = createTokensList(listName, list.tokens,
|
|
||||||
"2021-01-29T01:02:03.000+00:00", // use constant here to prevent changing time every time
|
|
||||||
0, 1, 0);
|
|
||||||
writeToFileWithUpdate(tokenlistFile, newList);
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export enum TokenType {
|
|
||||||
COIN = 'coin',
|
|
||||||
BEP2 = 'BEP2',
|
|
||||||
ERC20 = 'ERC20'
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
export const isLowerCase = (str: string): boolean => str.toLowerCase() === str;
|
|
||||||
export const isUpperCase = (str: string): boolean => str.toUpperCase() === str;
|
|
||||||
|
|
||||||
// Sort: treat numbers as number, strings as case-insensitive
|
|
||||||
export function sortElements (arr: unknown[]): unknown[] {
|
|
||||||
arr.sort((a, b) => {
|
|
||||||
if (typeof a === "number" && typeof b == "number") {
|
|
||||||
// numerical comparison
|
|
||||||
return a - b;
|
|
||||||
}
|
|
||||||
if ((typeof a === 'string' || a instanceof String) && (typeof b === 'string' || b instanceof String)) {
|
|
||||||
if (!isNaN(Number(a)) && !isNaN(Number(b))) {
|
|
||||||
// numerical comparison
|
|
||||||
return Number(a) - Number(b);
|
|
||||||
}
|
|
||||||
return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeUnique(arr: string[]): string[] {
|
|
||||||
return Array.from(new Set(arr));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from set A elements of set B.
|
|
||||||
export function arrayDiff(a: string[], b: string[]): string[] {
|
|
||||||
const setB = new Set(b);
|
|
||||||
return a.filter(e => !setB.has(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from set A elements of set B, case insensitive
|
|
||||||
export function arrayDiffNocase(a: string[], b: string[]): string[] {
|
|
||||||
const setB = new Set(b.map(e => e.toLowerCase()));
|
|
||||||
return a.filter(e => !setB.has(e.toLowerCase()));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findDuplicates(list: string[]): string[] {
|
|
||||||
const m = new Map<string, number>();
|
|
||||||
const duplicates: string[] = [];
|
|
||||||
list.forEach(val => {
|
|
||||||
if (m.has(val.toLowerCase())) {
|
|
||||||
duplicates.push(val);
|
|
||||||
} else {
|
|
||||||
m.set(val.toLowerCase(), 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return makeUnique(duplicates);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that two lists have no common elements, and no duplicates in either.
|
|
||||||
// Do a single check: checking for duplicates in the concatenated list.
|
|
||||||
export function findCommonElementsOrDuplicates(list1: string[], list2: string[]): string[] {
|
|
||||||
return findDuplicates(list1.concat(list2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare two arrays, order does not matter
|
|
||||||
export function arrayEqual(a1: string[], a2: string[]): boolean {
|
|
||||||
if (a1.length != a2.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(arrayDiff(a1, a2).length == 0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(arrayDiff(a2, a1).length == 0)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reverseCase(s: string): string {
|
|
||||||
const n = s.length;
|
|
||||||
let out = "";
|
|
||||||
for (let i = 0; i < n; ++i) {
|
|
||||||
const c = s[i];
|
|
||||||
if (isLowerCase(c)) {
|
|
||||||
out += c.toUpperCase();
|
|
||||||
} else if (isUpperCase(s[i])) {
|
|
||||||
out += c.toLowerCase();
|
|
||||||
} else {
|
|
||||||
out += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
import { BinanceAction } from "../blockchain/binance";
|
|
||||||
import { SmartchainAction } from "../blockchain/smartchain";
|
|
||||||
import { EthereumAction } from "../blockchain/ethereum";
|
|
||||||
import { CosmosAction } from "../blockchain/cosmos";
|
|
||||||
import { AssetInfos } from "../generic/asset-infos";
|
|
||||||
import { EthForks } from "../generic/eth-forks";
|
|
||||||
import { FoldersFiles } from "../generic/folders-and-files";
|
|
||||||
import { JsonAction } from "../generic/json-format";
|
|
||||||
import { KavaAction } from "../blockchain/kava";
|
|
||||||
import { LogoSize } from "../generic/logo-size";
|
|
||||||
import { TerraAction } from "../blockchain/terra";
|
|
||||||
import { TezosAction } from "../blockchain/tezos";
|
|
||||||
import { TronAction } from "../blockchain/tron";
|
|
||||||
import { Validators } from "../generic/validators";
|
|
||||||
import { WavesAction } from "../blockchain/waves";
|
|
||||||
import { PolygonAction } from "../blockchain/polygon";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import * as chalk from 'chalk';
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
|
|
||||||
const actionList: ActionInterface[] = [
|
|
||||||
new FoldersFiles(),
|
|
||||||
new AssetInfos(),
|
|
||||||
new EthForks(),
|
|
||||||
new LogoSize(),
|
|
||||||
new Validators(),
|
|
||||||
new JsonAction(),
|
|
||||||
// chains:
|
|
||||||
new BinanceAction(),
|
|
||||||
new SmartchainAction(),
|
|
||||||
new EthereumAction(),
|
|
||||||
new CosmosAction(),
|
|
||||||
new KavaAction(),
|
|
||||||
new PolygonAction(),
|
|
||||||
new TerraAction(),
|
|
||||||
new TezosAction(),
|
|
||||||
new TronAction(),
|
|
||||||
new WavesAction()
|
|
||||||
];
|
|
||||||
|
|
||||||
const maxErrosFromOneCheck = 5;
|
|
||||||
|
|
||||||
const markerError = chalk.red('XXX');
|
|
||||||
const markerWarning = chalk.yellow('!!');
|
|
||||||
const markerOK = chalk.green('✓');
|
|
||||||
|
|
||||||
async function checkStepList(steps: CheckStepInterface[]): Promise<[string[], string[]]> {
|
|
||||||
const errorsAll: string[] = [];
|
|
||||||
const warningsAll: string[] = [];
|
|
||||||
await bluebird.each(steps, async (step) => {
|
|
||||||
try {
|
|
||||||
//console.log(` Running check step '${step.getName()}'...`);
|
|
||||||
const [errors, warnings] = await step.check();
|
|
||||||
if (errors && errors.length > 0) {
|
|
||||||
console.log(`- ${markerError} '${step.getName()}': ${errors.length} errors`);
|
|
||||||
let cnt = 0;
|
|
||||||
errors.forEach(err => {
|
|
||||||
if (cnt < maxErrosFromOneCheck) {
|
|
||||||
console.log(` ${markerError} '${err}'`);
|
|
||||||
errorsAll.push(err);
|
|
||||||
} else if (cnt == maxErrosFromOneCheck) {
|
|
||||||
console.log(` ${markerError} ${errors.length} errors in total, omitting rest ...`);
|
|
||||||
}
|
|
||||||
cnt++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (warnings && warnings.length > 0) {
|
|
||||||
console.log(`- ${markerWarning} '${step.getName()}': ${warnings.length} warnings`);
|
|
||||||
let cnt = 0;
|
|
||||||
warnings.forEach(warn => {
|
|
||||||
if (cnt < maxErrosFromOneCheck) {
|
|
||||||
console.log(` ${markerWarning} '${warn}'`);
|
|
||||||
warningsAll.push(warn);
|
|
||||||
} else if (cnt == maxErrosFromOneCheck) {
|
|
||||||
console.log(` ${markerWarning} ${warnings.length} warnings in total, omitting rest ...`);
|
|
||||||
}
|
|
||||||
cnt++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errors.length == 0 && warnings.length == 0) {
|
|
||||||
console.log(`- ${markerOK} '${step.getName()}' OK`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`- ${markerError} '${step.getName()}': Caught error: ${error.message}`);
|
|
||||||
errorsAll.push(`${step.getName()}: Exception: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [errorsAll, warningsAll];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sanityCheckByActionList(actions: ActionInterface[]): Promise<[string[], string[]]> {
|
|
||||||
console.log("Running sanity checks...");
|
|
||||||
const errors: string[] = [];
|
|
||||||
const warnings: string[] = [];
|
|
||||||
await bluebird.each(actions, async (action) => {
|
|
||||||
try {
|
|
||||||
if (action.getSanityChecks) {
|
|
||||||
const steps = action.getSanityChecks();
|
|
||||||
if (steps && steps.length > 0) {
|
|
||||||
console.log(` Action '${action.getName()}' has ${steps.length} check steps`);
|
|
||||||
const [errors1, warnings1] = await checkStepList(steps);
|
|
||||||
if (errors1.length > 0) {
|
|
||||||
errors1.forEach(e => errors.push(e));
|
|
||||||
}
|
|
||||||
if (warnings1.length > 0) {
|
|
||||||
warnings1.forEach(w => warnings.push(w));
|
|
||||||
}
|
|
||||||
if (errors1.length == 0 && warnings1.length == 0) {
|
|
||||||
console.log(`- ${markerOK} Action '${action.getName()}' OK, all ${steps.length} steps`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`- ${markerError} '${action.getName()}' Caught error: ${error.message}`);
|
|
||||||
errors.push(`${action.getName()}: Exception: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(`All sanity checks done, found ${errors.length} errors, ${warnings.length} warnings`);
|
|
||||||
return [errors, warnings];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function consistencyCheckByActionList(actions: ActionInterface[]): Promise<[string[], string[]]> {
|
|
||||||
console.log("Running consistency checks...");
|
|
||||||
const errors: string[] = [];
|
|
||||||
const warnings: string[] = [];
|
|
||||||
await bluebird.each(actions, async (action) => {
|
|
||||||
try {
|
|
||||||
if (action.getConsistencyChecks) {
|
|
||||||
const steps = action.getConsistencyChecks();
|
|
||||||
if (steps && steps.length > 0) {
|
|
||||||
console.log(` Action '${action.getName()}' has ${steps.length} check steps`);
|
|
||||||
const [errors1, warnings1] = await checkStepList(steps);
|
|
||||||
if (errors1.length > 0) {
|
|
||||||
errors1.forEach(e => errors.push(e));
|
|
||||||
}
|
|
||||||
if (warnings1.length > 0) {
|
|
||||||
warnings1.forEach(w => warnings.push(w));
|
|
||||||
}
|
|
||||||
if (errors1.length == 0 && warnings1.length == 0) {
|
|
||||||
console.log(`- ${markerOK} Action '${action.getName()}' OK, all ${steps.length} steps`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`- ${markerError} '${action.getName()}' Caught error: ${error.message}`);
|
|
||||||
errors.push(`${action.getName()}: Exception: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(`All consistency checks done, found ${errors.length} errors, ${warnings.length} warnings`);
|
|
||||||
return [errors, warnings];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sanityFixByList(actions: ActionInterface[]) {
|
|
||||||
console.log("Running sanity fixes...");
|
|
||||||
await bluebird.each(actions, async (action) => {
|
|
||||||
try {
|
|
||||||
if (action.sanityFix) {
|
|
||||||
console.log(`Sanity fix '${action.getName()}':`);
|
|
||||||
await action.sanityFix();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Caught error: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log("All sanity fixes done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function consistencyFixByList(actions: ActionInterface[]) {
|
|
||||||
console.log("Running consistency fixes...");
|
|
||||||
await bluebird.each(actions, async (action) => {
|
|
||||||
try {
|
|
||||||
if (action.consistencyFix) {
|
|
||||||
console.log(`Consistency fix '${action.getName()}':`);
|
|
||||||
await action.consistencyFix();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Caught error: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log("All consistency fixes done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateAutoByList(actions: ActionInterface[]) {
|
|
||||||
console.log("Running auto updates (using external data sources) ...");
|
|
||||||
await bluebird.each(actions, async (action) => {
|
|
||||||
try {
|
|
||||||
if (action.updateAuto) {
|
|
||||||
console.log(`Auto update '${action.getName()}':`);
|
|
||||||
await action.updateAuto();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Caught error: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log("All auto updates done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateManualByList(actions: ActionInterface[]) {
|
|
||||||
console.log("Running manual updates (using external data sources) ...");
|
|
||||||
await bluebird.each(actions, async (action) => {
|
|
||||||
try {
|
|
||||||
if (action.updateManual) {
|
|
||||||
console.log(`Manual update '${action.getName()}':`);
|
|
||||||
await action.updateManual();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Caught error: ${error.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log("All manual updates done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sanityCheckAll(): Promise<[string[], string[]]> {
|
|
||||||
return await sanityCheckByActionList(actionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function consistencyCheckAll(): Promise<[string[], string[]]> {
|
|
||||||
return await consistencyCheckByActionList(actionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sanityFixAll(): Promise<void> {
|
|
||||||
await sanityFixByList(actionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function consistencyFixAll(): Promise<void> {
|
|
||||||
await consistencyFixByList(actionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateAutoAll(): Promise<void> {
|
|
||||||
await updateAutoByList(actionList);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateManualAll(): Promise<void> {
|
|
||||||
await updateManualByList(actionList);
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
export interface ValidatorModel {
|
|
||||||
id: string,
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
website: string,
|
|
||||||
staking: Staking
|
|
||||||
payout: Payout
|
|
||||||
status: ValidatorStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Staking {
|
|
||||||
freeSpace: number,
|
|
||||||
minDelegation: number
|
|
||||||
openForDelegation: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Payout {
|
|
||||||
commission: number // in %
|
|
||||||
payoutDelay: number // in cycles
|
|
||||||
payoutPeriod: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ValidatorStatus {
|
|
||||||
disabled: boolean;
|
|
||||||
note: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import { stakingChains } from "../generic/blockchains";
|
|
||||||
import {
|
|
||||||
getChainValidatorsListPath,
|
|
||||||
getChainValidatorAssetLogoPath,
|
|
||||||
getChainValidatorsAssets
|
|
||||||
} from "../generic/repo-structure";
|
|
||||||
import { isPathExistsSync } from "../generic/filesystem";
|
|
||||||
import { formatSortJsonFile, readJsonFile } from "../generic/json";
|
|
||||||
import { ActionInterface, CheckStepInterface } from "../generic/interface";
|
|
||||||
import { isValidJSON } from "../generic/json";
|
|
||||||
import { ValidatorModel } from "../generic/validator-models";
|
|
||||||
import { isLogoOK } from "../generic/image";
|
|
||||||
import * as bluebird from "bluebird";
|
|
||||||
|
|
||||||
function formatValidators() {
|
|
||||||
stakingChains.forEach(chain => {
|
|
||||||
const validatorsPath = getChainValidatorsListPath(chain);
|
|
||||||
formatSortJsonFile(validatorsPath);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChainValidatorsList(chain: string): ValidatorModel[] {
|
|
||||||
return readJsonFile(getChainValidatorsListPath(chain)) as ValidatorModel[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidatorHasAllKeys(val: ValidatorModel): boolean {
|
|
||||||
return typeof val.id === "string"
|
|
||||||
&& typeof val.name === "string"
|
|
||||||
&& typeof val.description === "string"
|
|
||||||
&& typeof val.website === "string";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Validators implements ActionInterface {
|
|
||||||
getName(): string { return "Validators"; }
|
|
||||||
|
|
||||||
getSanityChecks(): CheckStepInterface[] {
|
|
||||||
const steps: CheckStepInterface[] = [
|
|
||||||
{
|
|
||||||
getName: () => { return "Make sure tests added for new staking chain"},
|
|
||||||
check: async (): Promise<[string[], string[]]> => {
|
|
||||||
if (stakingChains.length != 8) {
|
|
||||||
return [[`Wrong number of staking chains ${stakingChains.length}`], []];
|
|
||||||
}
|
|
||||||
return [[], []];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
stakingChains.forEach(chain => {
|
|
||||||
steps.push(
|
|
||||||
{
|
|
||||||
getName: () => { return `Make sure chain ${chain} has valid list file, has logo`},
|
|
||||||
check: async (): Promise<[string[], string[]]> => {
|
|
||||||
const validatorsListPath = getChainValidatorsListPath(chain);
|
|
||||||
if (!isValidJSON(validatorsListPath)) {
|
|
||||||
return [[`Not valid Json file at path ${validatorsListPath}`], []];
|
|
||||||
}
|
|
||||||
|
|
||||||
const errors: string[] = [];
|
|
||||||
const validatorsList = getChainValidatorsList(chain);
|
|
||||||
const chainValidatorsAssetsList = getChainValidatorsAssets(chain);
|
|
||||||
await bluebird.each(validatorsList, async (val: ValidatorModel) => {
|
|
||||||
if (!isValidatorHasAllKeys(val)) {
|
|
||||||
errors.push(`Some key and/or type missing for validator ${JSON.stringify(val)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = val.id;
|
|
||||||
const path = getChainValidatorAssetLogoPath(chain, id);
|
|
||||||
if (!isPathExistsSync(path)) {
|
|
||||||
errors.push(`Chain ${chain} asset ${id} logo must be present at path ${path}`);
|
|
||||||
}
|
|
||||||
const [isOk, logoMsg] = await isLogoOK(path);
|
|
||||||
if (!isOk) {
|
|
||||||
errors.push(logoMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure validator has corresponding logo
|
|
||||||
if (!(chainValidatorsAssetsList.indexOf(id) >= 0)) {
|
|
||||||
errors.push(`Expecting image asset for validator ${id} on chain ${chain}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make sure validator asset logo has corresponding info
|
|
||||||
chainValidatorsAssetsList.forEach(valAssetLogoID => {
|
|
||||||
if (validatorsList.filter(v => v.id === valAssetLogoID).length != 1) {
|
|
||||||
errors.push(`Expect validator logo ${valAssetLogoID} to have info`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return [errors, []];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sanityFix(): Promise<void> {
|
|
||||||
formatValidators();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
export interface TagDescription {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TagValues: TagDescription[] = [
|
|
||||||
{
|
|
||||||
id: "stablecoin",
|
|
||||||
name: "Stablecoin",
|
|
||||||
description: "Tokens that are fixed to an external asset, e.g. the US dollar."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "wrapped",
|
|
||||||
name: "Wrapped",
|
|
||||||
description: "Tokens that are wrapped or peg representation of digital assets. Excluded stablecoins"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "synthetics",
|
|
||||||
name: "Synthetics",
|
|
||||||
description: "Synthetic assets created to track the value of another asset"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nft",
|
|
||||||
name: "NFT",
|
|
||||||
description: "Non-fungible tokens or tokens associated with the NFT ecosystem."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "governance",
|
|
||||||
name: "Governance",
|
|
||||||
description: "Tokens that used to participate in the governance process of the project."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "defi",
|
|
||||||
name: "DeFi",
|
|
||||||
description: "Tokens that are used for variety of decentralized financial applications."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "staking",
|
|
||||||
name: "Staking",
|
|
||||||
description: "Tokens that are used for staking to receive rewards."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "staking-native",
|
|
||||||
name: "Staking Native",
|
|
||||||
description: "Coins/Blockchains that are used for staking to secure the network to receive rewards."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "privacy",
|
|
||||||
name: "Privacy",
|
|
||||||
description: "Privacy tokens."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nsfw",
|
|
||||||
name: "NSFW",
|
|
||||||
description: "Content Not suitable for work."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "binance-peg",
|
|
||||||
name: "Binance-Peg",
|
|
||||||
description: "Binance-Peg tokens."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "deflationary",
|
|
||||||
name: "Deflationary",
|
|
||||||
description: "Tokens that are deflationary or use mechanism to burn a token on transfer/swap."
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import {
|
|
||||||
assetID
|
|
||||||
} from "../script/generic/asset";
|
|
||||||
|
|
||||||
describe("Test eth-address helpers", () => {
|
|
||||||
test(`Test coin`, () => {
|
|
||||||
expect(assetID(714)).toEqual('c714');
|
|
||||||
expect(assetID(714, '')).toEqual('c714');
|
|
||||||
});
|
|
||||||
test(`Test token`, () => {
|
|
||||||
expect(assetID(714, 'TWT-8C2')).toEqual('c714_tTWT-8C2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
import {
|
|
||||||
findDuplicates,
|
|
||||||
findCommonElementsOrDuplicates,
|
|
||||||
} from "../script/generic/types";
|
|
||||||
import {
|
|
||||||
isChecksum,
|
|
||||||
toChecksum,
|
|
||||||
isEthereumAddress
|
|
||||||
} from "../script/generic/eth-address";
|
|
||||||
import {
|
|
||||||
isDimensionTooLarge,
|
|
||||||
isDimensionOK,
|
|
||||||
calculateTargetSize
|
|
||||||
} from "../script/generic/image";
|
|
||||||
import {
|
|
||||||
sortElements,
|
|
||||||
makeUnique,
|
|
||||||
arrayDiff,
|
|
||||||
arrayDiffNocase,
|
|
||||||
arrayEqual,
|
|
||||||
reverseCase
|
|
||||||
} from "../script/generic/types";
|
|
||||||
import {
|
|
||||||
BinanceTokenInfo,
|
|
||||||
findImagesToFetch
|
|
||||||
} from "../script/blockchain/binance";
|
|
||||||
import { isValidStatusValue } from "../script/generic/status-values";
|
|
||||||
import { isValidTagValue, isValidTagValues } from "../script/generic/tag-values";
|
|
||||||
|
|
||||||
describe("Test eth-address helpers", () => {
|
|
||||||
test(`Test isChecksum`, () => {
|
|
||||||
expect(isChecksum("0x7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee", "ethereum"), `checksum`).toBe(true);
|
|
||||||
expect(isChecksum("0x7bb09bc8ade747178e95b1d035ecbeebbb18cfee", "ethereum"), `lowercase`).toBe(false);
|
|
||||||
expect(isChecksum("0x7Bb09bC8aDE747178e95B1D035ecBe", "ethereum"), `too short`).toBe(false);
|
|
||||||
expect(isChecksum("7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee", "ethereum"), `checksum, no prefix`).toBe(true);
|
|
||||||
expect(isChecksum("7bb09bc8ade747178e95b1d035ecbeebbb18cfee", "ethereum"), `lowercase, no prefix`).toBe(false);
|
|
||||||
expect(isChecksum("0x7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee", "wanchain"), `wanchain wrong checksum`).toBe(false);
|
|
||||||
expect(isChecksum("0x7bb09bc8ade747178e95b1d035ecbeebbb18cfee", "wanchain"), `wanchain lowercase`).toBe(false);
|
|
||||||
expect(isChecksum("0x7bB09Bc8Ade747178E95b1d035ECbEebBb18CfEE", "wanchain"), `wanchain checksum`).toBe(true);
|
|
||||||
});
|
|
||||||
test(`Test toChecksum`, () => {
|
|
||||||
expect(toChecksum("0x7bb09bc8ade747178e95b1d035ecbeebbb18cfee", "ethereum"), `from lowercase`).toEqual("0x7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee");
|
|
||||||
expect(toChecksum("0x7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee", "ethereum"), `from checksum`).toEqual("0x7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee");
|
|
||||||
expect(toChecksum("7bb09bc8ade747178e95b1d035ecbeebbb18cfee", "ethereum"), `from lowercase, no prefix`).toEqual("0x7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee");
|
|
||||||
expect(toChecksum("0x7bb09bc8ade747178e95b1d035ecbeebbb18cfee", "wanchain"), `wanchain, from lowercase`).toEqual("0x7bB09Bc8Ade747178E95b1d035ECbEebBb18CfEE");
|
|
||||||
});
|
|
||||||
test(`Test isEthereumAddress`, () => {
|
|
||||||
expect(isEthereumAddress("0x7bb09bc8ade747178e95b1d035ecbeebbb18cfee"), `valid, lowercase`).toBe(true);
|
|
||||||
expect(isEthereumAddress("0x7Bb09bC8aDE747178e95B1D035ecBeEBbB18cFee"), `valid, checksum`).toBe(true);
|
|
||||||
expect(isEthereumAddress("b09bc8ade747178e95b1d035ecbeebbb18cfee"), `invalid, short`).toBe(false);
|
|
||||||
expect(isEthereumAddress("7bb09bc8ade747178e95b1d035ecbeebbb18cfee"), `valid, no prefix`).toBe(true);
|
|
||||||
expect(isEthereumAddress("0x7bb09bc8qde747178e95b1d035ecbeebbb18cfee"), `invalid, length ok, invalid char`).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Test image helpers", () => {
|
|
||||||
test(`Test isDimensionTooLarge`, () => {
|
|
||||||
expect(isDimensionTooLarge(256, 256), `256x256`).toBe(false);
|
|
||||||
expect(isDimensionTooLarge(64, 64), `64x64`).toBe(false);
|
|
||||||
expect(isDimensionTooLarge(800, 800), `800x800`).toBe(true);
|
|
||||||
expect(isDimensionTooLarge(256, 800), `256x800`).toBe(true);
|
|
||||||
expect(isDimensionTooLarge(800, 256), `800x256`).toBe(true);
|
|
||||||
});
|
|
||||||
test(`Test isDimensionOK`, () => {
|
|
||||||
expect(isDimensionOK(256, 256), `256x256`).toBe(true);
|
|
||||||
expect(isDimensionOK(64, 64), `64x64`).toBe(true);
|
|
||||||
expect(isDimensionOK(800, 800), `800x800`).toBe(false);
|
|
||||||
expect(isDimensionOK(256, 800), `256x800`).toBe(false);
|
|
||||||
expect(isDimensionOK(800, 256), `800x256`).toBe(false);
|
|
||||||
expect(isDimensionOK(60, 60), `60x60`).toBe(false);
|
|
||||||
expect(isDimensionOK(64, 60), `64x60`).toBe(false);
|
|
||||||
expect(isDimensionOK(60, 64), `60x64`).toBe(false);
|
|
||||||
});
|
|
||||||
test(`Test calculateReducedSize`, () => {
|
|
||||||
expect(calculateTargetSize(256, 256, 512, 512), `small 1.0`).toEqual({width: 512, height: 512});
|
|
||||||
expect(calculateTargetSize(800, 800, 512, 512), `large 1.0`).toEqual({width: 512, height: 512});
|
|
||||||
expect(calculateTargetSize(200, 100, 512, 512), `small 2.0`).toEqual({width: 512, height: 256});
|
|
||||||
expect(calculateTargetSize(100, 200, 512, 512), `small 0.5`).toEqual({width: 256, height: 512});
|
|
||||||
expect(calculateTargetSize(1200, 600, 512, 512), `small 2.0`).toEqual({width: 512, height: 256});
|
|
||||||
expect(calculateTargetSize(600, 1200, 512, 512), `small 0.5`).toEqual({width: 256, height: 512});
|
|
||||||
expect(calculateTargetSize(256, 0, 512, 512), `zero`).toEqual({width: 512, height: 512});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Test type helpers", () => {
|
|
||||||
test(`Test sortElements`, () => {
|
|
||||||
expect(sortElements(["c", "a", "b"]), `3 elems`).toEqual(["a", "b", "c"]);
|
|
||||||
expect(sortElements(["C", "a", "b"]), `mixed case`).toEqual(["a", "b", "C"]);
|
|
||||||
expect(sortElements(["1", "2", "11"]), `numerical string`).toEqual(["1", "2", "11"]);
|
|
||||||
expect(sortElements([1, 2, 11]), `numerical`).toEqual([1, 2, 11]);
|
|
||||||
expect(sortElements(["C", "a", "1", "b", "2", "11"]), `complex`).toEqual(["1", "2", "11", "a", "b", "C"]);
|
|
||||||
});
|
|
||||||
test(`Test makeUnique`, () => {
|
|
||||||
expect(makeUnique(["a", "b", "c", "b"]), `4 elems with 1 duplicate`).toEqual(["a", "b", "c"]);
|
|
||||||
});
|
|
||||||
test(`Test arrayDiff`, () => {
|
|
||||||
expect(arrayDiff(["a", "b", "c"], ["c"]), `4 elems with 1 duplicate`).toEqual(["a", "b"]);
|
|
||||||
expect(arrayDiff(["a", "b", "c"], ["d"]), `4 elems with 0 duplicate`).toEqual(["a", "b", "c"]);
|
|
||||||
expect(arrayDiff(["a", "B", "c"], ["C"]), `4 elems with 0 duplicate`).toEqual(["a", "B", "c"]);
|
|
||||||
expect(arrayDiffNocase(["a", "B", "c"], ["C"]), `4 elems with 0 duplicate`).toEqual(["a", "B"]);
|
|
||||||
});
|
|
||||||
test(`Test findDuplicates`, () => {
|
|
||||||
expect(findDuplicates(["a", "bb", "ccc"]), `No duplicates`).toEqual([]);
|
|
||||||
expect(findDuplicates(["a", "bb", "ccc", "bb"]), `One double duplicate`).toEqual(["bb"]);
|
|
||||||
expect(findDuplicates([]), `Empty array`).toEqual([]);
|
|
||||||
expect(findDuplicates(["a"]), `One element`).toEqual([]);
|
|
||||||
expect(findDuplicates(["a", "bb", "ccc", "bb", "d", "bb"]), `One triple duplicate`).toEqual(["bb"]);
|
|
||||||
expect(findDuplicates(["a", "bb", "ccc", "bb", "a"]), `Two double duplicates`).toEqual(["bb", "a"]);
|
|
||||||
});
|
|
||||||
test(`Test findCommonElementsOrDuplicates`, () => {
|
|
||||||
expect(findCommonElementsOrDuplicates(["a", "bb", "ccc"], ["1", "22", "333"]), `No intersection or duplicates`).toEqual([]);
|
|
||||||
expect(findCommonElementsOrDuplicates(["a", "bb", "ccc"], ["1", "bb", "333"]), `Common element`).toEqual(["bb"]);
|
|
||||||
expect(findCommonElementsOrDuplicates(["a", "bb", "ccc", "bb"], ["1", "22", "333"]), `Duplicate in first`).toEqual(["bb"]);
|
|
||||||
expect(findCommonElementsOrDuplicates(["a", "bb", "ccc"], ["1", "22", "333", "22"]), `Duplicate in second`).toEqual(["22"]);
|
|
||||||
expect(findCommonElementsOrDuplicates(["a", "bb", "ccc", "1", "bb"], ["1", "22", "333", "22"]), `Intersection and duplicates`).toEqual(["bb", "1", "22"]);
|
|
||||||
expect(findCommonElementsOrDuplicates([], []), `Empty lists`).toEqual([]);
|
|
||||||
});
|
|
||||||
test(`Test arrayEqual`, () => {
|
|
||||||
expect(arrayEqual(["a", "b", "c"], ["a", "b", "c"]), `equal`).toBe(true);
|
|
||||||
expect(arrayEqual(["a", "b", "c", "d"], ["a", "b", "c"]), `length mismatch`).toBe(false);
|
|
||||||
expect(arrayEqual(["a", "b", "c"], ["a", "b", "b"]), `length mismatch`).toBe(false);
|
|
||||||
expect(arrayEqual(["a", "b", "b"], ["a", "b", "c"]), `length mismatch`).toBe(false);
|
|
||||||
});
|
|
||||||
test(`Test reverseCase`, () => {
|
|
||||||
expect(reverseCase("abCDef12+-"), `mixed`).toEqual("ABcdEF12+-");
|
|
||||||
expect(reverseCase("ABcdEF12+-"), `mixed`).toEqual("abCDef12+-");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Test blockchain binance", () => {
|
|
||||||
test(`Test findImagesToFetch`, () => {
|
|
||||||
const infoA1: BinanceTokenInfo = {asset: "A1-11", name: "A 1", mappedAsset: "A1", assetImg: "imgurl1", decimals: 8};
|
|
||||||
const infoA2: BinanceTokenInfo = {asset: "A2-12", name: "A 2", mappedAsset: "A2", assetImg: "imgurl2", decimals: 8};
|
|
||||||
const assetsInfoListNonexisting: BinanceTokenInfo[] = [infoA1, infoA2];
|
|
||||||
const assetsInfoListExisting: BinanceTokenInfo[] = [
|
|
||||||
{asset: "BUSD-BD1", name: "Binance USD", mappedAsset: "BUSD", assetImg: "imgurlBUSD", decimals: 8},
|
|
||||||
{asset: "ETH-1C9", name: "Binance Ethereum", mappedAsset: "BETH", assetImg: "imgurlETH", decimals: 8}
|
|
||||||
];
|
|
||||||
expect(findImagesToFetch(assetsInfoListNonexisting), `2 nonexisting`).toEqual(assetsInfoListNonexisting);
|
|
||||||
expect(findImagesToFetch(assetsInfoListExisting), `2 existing`).toEqual([]);
|
|
||||||
expect(findImagesToFetch([]), `empty`).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Test status, tag values", () => {
|
|
||||||
test(`Test status-values`, () => {
|
|
||||||
expect(isValidStatusValue("active")).toEqual(true);
|
|
||||||
expect(isValidStatusValue("abandoned")).toEqual(true);
|
|
||||||
expect(isValidStatusValue("invalidvalue")).toEqual(false);
|
|
||||||
expect(isValidStatusValue("ACTIVE")).toEqual(false);
|
|
||||||
expect(isValidStatusValue("")).toEqual(false);
|
|
||||||
});
|
|
||||||
test(`Test tag-values`, () => {
|
|
||||||
expect(isValidTagValue("defi")).toEqual(true);
|
|
||||||
expect(isValidTagValue("staking")).toEqual(true);
|
|
||||||
expect(isValidStatusValue("invalidvalue")).toEqual(false);
|
|
||||||
expect(isValidStatusValue("STAKING")).toEqual(false);
|
|
||||||
expect(isValidStatusValue("")).toEqual(false);
|
|
||||||
|
|
||||||
expect(isValidTagValues(["defi"])).toEqual(true);
|
|
||||||
expect(isValidTagValues(["staking"])).toEqual(true);
|
|
||||||
expect(isValidTagValues(["defi", "staking"])).toEqual(true);
|
|
||||||
expect(isValidTagValues(["invalid"])).toEqual(false);
|
|
||||||
expect(isValidTagValues(["defi", "invalid"])).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import {
|
|
||||||
toSatoshis,
|
|
||||||
fromSatoshis
|
|
||||||
} from "../script/generic/numbers";
|
|
||||||
|
|
||||||
describe("Test eth-address helpers", () => {
|
|
||||||
test(`Test to satoshis`, () => {
|
|
||||||
expect(toSatoshis('43523.423423432112321234', 18)).toEqual('43523423423432112321234');
|
|
||||||
expect(toSatoshis('0.123', 3)).toEqual('123');
|
|
||||||
expect(toSatoshis('0.00000001', 8)).toEqual('1');
|
|
||||||
});
|
|
||||||
test(`Test from Satoshis`, () => {
|
|
||||||
expect(fromSatoshis('123', 3)).toEqual('0.123');
|
|
||||||
expect(fromSatoshis('1234', 3)).toEqual('1.234');
|
|
||||||
expect(fromSatoshis('43523423423432112321234', 18)).toEqual('43523.423423432112321234');
|
|
||||||
expect(fromSatoshis('1', 8)).toEqual('0.00000001');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue
Block a user