diff --git a/package-lock.json b/package-lock.json index 7a9600419..d09bd4fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3161,6 +3161,11 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "diff-sequences": { "version": "25.2.6", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", @@ -3286,8 +3291,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.14.3", @@ -6662,6 +6666,61 @@ "minimist": "^1.2.5" } }, + "jsondiffpatch": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz", + "integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==", + "requires": { + "chalk": "^2.3.0", + "diff-match-patch": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "jsonpointer": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", diff --git a/package.json b/package.json index 4a398fc1e..c7c7dcdb5 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "bignumber.js": "^9.0.0" }, "dependencies": { - "codecov": "^3.7.2" + "codecov": "^3.7.2", + "jsondiffpatch": "^0.4.1" }, "jest": { "setupFilesAfterEnv": [ diff --git a/script/blockchain/binance.ts b/script/blockchain/binance.ts index e51d5c9d8..ea5dbbbfe 100644 --- a/script/blockchain/binance.ts +++ b/script/blockchain/binance.ts @@ -8,7 +8,7 @@ import { ActionInterface, CheckStepInterface } from "../generic/interface"; import { Binance } from "../generic/blockchains"; import { readDirSync } from "../generic/filesystem"; import { readJsonFile } from "../generic/json"; -import { TokenItem, Pair, generateTokensList, writeToFile } from "../generic/tokenlists"; +import { TokenItem, Pair, generateTokensList, writeToFileWithUpdate } from "../generic/tokenlists"; import { getChainAssetLogoPath, getChainAssetsPath, @@ -17,7 +17,7 @@ import { } from "../generic/repo-structure"; import { CoinType } from "@trustwallet/wallet-core"; import { toSatoshis } from "../generic/numbers"; -import { assetID } from "../generic/asset"; +import { assetIdSymbol, logoURI, tokenType } from "../generic/asset"; import { TokenType } from "../generic/tokentype"; const binanceChain = "binance"; @@ -139,8 +139,11 @@ export class BinanceAction implements ActionInterface { } // binance chain list - const list = await generateBinanceTokensList(); - writeToFile(getChainTokenlistPath(Binance), generateTokensList("BNB", list)); + const tokenList = await generateBinanceTokensList(); + const list = generateTokensList("BNB", tokenList, + "2020-10-03T12:37:57.000+00:00", // use constants here to prevent changing time every time + 0, 1, 0); + writeToFileWithUpdate(getChainTokenlistPath(Binance), list); } } @@ -165,7 +168,7 @@ async function generateBinanceTokensList(): Promise { function pair(market: BinanceMarket): Pair { return new Pair( - asset(market.base_asset_symbol), + assetIdSymbol(market.base_asset_symbol, BNBSymbol, CoinType.binance), toSatoshis(market.lot_size, decimals), toSatoshis(market.tick_size, decimals) ) @@ -184,35 +187,17 @@ async function generateBinanceTokensList(): Promise { pairsList.add(market.quote_asset_symbol) }) - function logoURI(symbol: string): string { - if (symbol == BNBSymbol) { - return `${config.assetsURL}/blockchains/binance/assets/${symbol}/logo.png` - } - return `${config.assetsURL}/blockchains/binance/assets/${symbol}/logo.png` - } - function asset(symbol: string): string { - if (symbol == BNBSymbol) { - return assetID(CoinType.binance) - } - return assetID(CoinType.binance, symbol) - } - function tokenType(symbol: string): string { - if (symbol == BNBSymbol) { - return TokenType.COIN - } - return TokenType.BEP2 - } const list = Array.from(pairsList.values()) return list.map(item => { const token = tokensMap[item] return new TokenItem ( - asset(token.symbol), - tokenType(token.symbol), + assetIdSymbol(token.symbol, BNBSymbol, CoinType.binance), + tokenType(token.symbol, BNBSymbol, TokenType.BEP2), token.symbol, token.name, token.original_symbol, decimals, - logoURI(token.symbol), + logoURI(token.symbol, 'binance', BNBSymbol), pairsMap[token.symbol] || [] ) }); diff --git a/script/generic/asset.ts b/script/generic/asset.ts index 1f009f232..51b1c4fc2 100644 --- a/script/generic/asset.ts +++ b/script/generic/asset.ts @@ -1,6 +1,34 @@ -export function assetID(coin: number, token_id = ``): string { - if (token_id.length > 0) { - return `c${coin}_t${token_id}` +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${coin}` -} \ No newline at end of file + 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` +} diff --git a/script/generic/tokenlists.ts b/script/generic/tokenlists.ts index 53732cd0c..25c2fcd3c 100644 --- a/script/generic/tokenlists.ts +++ b/script/generic/tokenlists.ts @@ -1,4 +1,5 @@ -import { writeJsonFile } from "../generic/json"; +import { readJsonFile, writeJsonFile } from "../generic/json"; +import { diff } from "jsondiffpatch"; class Version { major: number @@ -12,7 +13,7 @@ class Version { } } -class List { +export class List { name: string logoURI: string timestamp: string @@ -53,29 +54,29 @@ export class TokenItem { export class Pair { base: string; - lotSize: string; - tickSize: string; + lotSize?: string; + tickSize?: string; - constructor(base: string, lotSize: string, tickSize: string) { + constructor(base: string, lotSize?: string, tickSize?: string) { this.base = base this.lotSize = lotSize this.tickSize = tickSize } } -export function generateTokensList(titleCoin: string, tokens: TokenItem[]): List { - return new List( +export function generateTokensList(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", - "2020-10-03T12:37:57.000+00:00", - 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); - }), - new Version(0, 1, 0) - ) + time, + tokens, + new Version(versionMajor, versionMinor, versionPatch) + ); + sort(list); + return list; } function totalPairs(list: List): number { @@ -88,3 +89,64 @@ 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; + let oldVersion: Version = new Version(0, 0, 0); + try { + listOld = readJsonFile(filename) as List; + } catch (err) { + listOld = undefined; + } + let changed = false; + if (listOld === undefined) { + changed = true; + } else { + oldVersion = listOld.version; + const diffs = diffTokenlist(list, listOld); + if (diffs != undefined) { + //console.log("List has Changed", JSON.stringify(diffs)); + changed = true; + } + } + if (changed) { + // update version and time + list.version.major = oldVersion.major + 1; + list.version.minor = 0; + list.version.patch = 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); +} + +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 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; +}