Add token list support for pairs on binance chain (#4428)

* Add token list support for pairs on binance chain

* Update tokenlists.ts

* Update tokenlists.ts

* Update tokenlists.ts

* Fix asset for bnb pair

* Update package-lock.json

* Add models for token list

* Add BinanceMarket
This commit is contained in:
Viktor Radchenko 2020-10-13 18:20:42 -07:00 committed by GitHub
parent 62a6265eed
commit 9b41a52182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 2227 additions and 76 deletions

File diff suppressed because it is too large Load Diff

12
package-lock.json generated
View File

@ -1930,9 +1930,9 @@
"dev": true
},
"bignumber.js": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
"integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz",
"integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==",
"dev": true
},
"bindings": {
@ -3630,6 +3630,12 @@
"integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
"dev": true
},
"bignumber.js": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
"integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==",
"dev": true
},
"caseless": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",

View File

@ -10,7 +10,8 @@
"fix": "ts-node ./script/entrypoint/fix",
"fix-sanity": "ts-node ./script/entrypoint/fix-sanity",
"update": "ts-node ./script/entrypoint/update",
"lint": "npx eslint . --ext .js,.jsx,.ts,.tsx"
"lint": "npx eslint . --ext .js,.jsx,.ts,.tsx",
"lint:fix": "npx eslint . --ext .js,.jsx,.ts,.tsx --fix"
},
"repository": {
"type": "git",
@ -55,7 +56,8 @@
"tinify": "^1.6.0-beta.2",
"ts-jest": "^25.5.1",
"ts-node": "^8.10.2",
"typescript": "^3.9.7"
"typescript": "^3.9.7",
"bignumber.js": "^9.0.0"
},
"dependencies": {
"codecov": "^3.7.2"

View File

@ -16,27 +16,25 @@ import {
} from "../generic/repo-structure";
const binanceChain = "binance";
const binanceUrlTokens2 = config.binanceUrlTokens2;
const binanceUrlTokens8 = config.binanceUrlTokens8;
const binanceUrlTokenAssets = config.binanceUrlTokenAssets;
let cachedAssets = [];
async function retrieveBep2AssetList(): Promise<unknown[]> {
console.log(` Retrieving token asset infos from: ${binanceUrlTokenAssets}`);
console.log(`Retrieving token asset infos from: ${binanceUrlTokenAssets}`);
const { assetInfoList } = await axios.get(binanceUrlTokenAssets).then(r => r.data);
console.log(` Retrieved ${assetInfoList.length} token asset infos`);
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 (${binanceUrlTokens2}, ${binanceUrlTokens8})`);
const bep2assets = await axios.get(binanceUrlTokens2);
const bep8assets = await axios.get(binanceUrlTokens8);
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`);
console.log(`Using ${cachedAssets.length} assets`);
return cachedAssets;
}

View File

@ -4,6 +4,6 @@ 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", "dangerfile.ts", "Gemfile", "Gemfile.lock", ".eslintignore", ".eslintrc.js"];
export const binanceUrlTokens2 = "https://dex-atlantic.binance.org/api/v1/tokens?limit=1000";
export const binanceUrlTokens8 = "https://dex-atlantic.binance.org/api/v1/mini/tokens?limit=1000";
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://raw.githubusercontent.com/trustwallet/assets/master'

View File

@ -1,18 +1,18 @@
import { chainsWithDenylist } from "../generic/blockchains";
import { chainsWithDenylist } from "./blockchains";
import {
getChainAssetsList,
getChainAllowlistPath,
getChainDenylistPath
} from "../generic/repo-structure";
import { readFileSync, writeFileSync } from "../generic/filesystem";
} from "./repo-structure";
import { readFileSync, writeFileSync } from "./filesystem";
import {
arrayDiff,
arrayDiffNocase,
findCommonElementsOrDuplicates,
makeUnique
} from "../generic/types";
import { ActionInterface, CheckStepInterface } from "../generic/interface";
import { formatSortJson } from "../generic/json";
} from "./types";
import { ActionInterface, CheckStepInterface } from "./interface";
import { formatSortJson } from "./json";
import * as bluebird from "bluebird";
async function checkUpdateAllowDenyList(chain: string, checkOnly: boolean ): Promise<[boolean, string[], string[]]> {
@ -85,7 +85,7 @@ async function checkUpdateAllowDenyList(chain: string, checkOnly: boolean ): Pro
return [(errorMsgs.length == 0 && warningMsgs.length == 0), errorMsgs, warningMsgs];
}
export class Allowlist implements ActionInterface {
export class Allowlists implements ActionInterface {
getName(): string { return "Allowlists"; }
getSanityChecks = null;

6
script/generic/asset.ts Normal file
View File

@ -0,0 +1,6 @@
export function assetID(coin: number, token_id = ``): string {
if (token_id.length > 0) {
return `c${coin}_t${token_id}`
}
return `c${coin}`
}

View File

@ -0,0 +1,9 @@
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()
}

View File

@ -0,0 +1,170 @@
import { ActionInterface, CheckStepInterface } from "./interface";
import axios from "axios";
import {
getChainTokenlistPath
} from "./repo-structure";
import { Binance } from "./blockchains";
import { writeFileSync } from "./filesystem";
import { formatJson } from "./json";
import { assetID } from "./asset";
import * as config from "../config";
import { CoinType } from "@trustwallet/wallet-core";
import { toSatoshis } from "./numbers";
class BinanceMarket {
base_asset_symbol: string
quote_asset_symbol: string
lot_size: string
tick_size: string
}
class Version {
major: number
minor: number
patch: number
constructor(major: number, minor: number, patch: number) {
this.major = major
this.minor = minor
this.patch = patch
}
}
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
}
}
class TokenItem {
asset: string;
address: string;
name: string;
symbol: string;
decimals: number;
logoURI: string;
pairs: [Pair];
constructor(asset: string, address: string, name: string, symbol: string, decimals: number, logoURI: string, pairs: [Pair]) {
this.asset = asset
this.address = address
this.name = name;
this.symbol = symbol
this.decimals = decimals
this.logoURI = logoURI
this.pairs = pairs
}
}
class Pair {
base: string;
lotSize: string;
tickSize: string;
constructor(base: string, lotSize: string, tickSize: string) {
this.base = base
this.lotSize = lotSize
this.tickSize = tickSize
}
}
export class TokenLists implements ActionInterface {
getName(): string { return "TokenLists"; }
getSanityChecks = null;
getConsistencyChecks(): CheckStepInterface[] {
const steps: CheckStepInterface[] = [];
return steps;
}
async consistencyFix(): Promise<void> {
// binance chain list
const list = await generateBinanceTokensList()
writeFileSync(getChainTokenlistPath(Binance), formatJson(generateTokensList(list)));
return
}
}
function generateTokensList(tokens: [TokenItem]): List {
return new List(
"Trust Wallet: BNB",
"https://trustwallet.com/assets/images/favicon.png",
"2020-10-03T12:37:57.000+00:00",
tokens,
new Version(0, 1, 0)
)
}
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);
const tokens = await axios.get(`${config.binanceDexURL}/v1/tokens?limit=10000`).then(r => r.data);
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(
asset(market.base_asset_symbol),
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)
})
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)
}
const list = <[TokenItem]>Array.from(pairsList.values())
return <[TokenItem]>list.map(item => {
const token = tokensMap[item.symbol]
return new TokenItem (
asset(token.symbol),
token.symbol,
token.name,
token.symbol,
decimals,
logoURI(token.symbol),
pairsMap[token.symbol] || []
)
}).sort((n1,n2) => (n2.pairs || []).length - (n1.pairs || []).length);
}

View File

@ -10,7 +10,8 @@ import { TezosAction } from "../blockchain/tezos";
import { TronAction } from "../blockchain/tron";
import { Validators } from "../generic/validators";
import { WavesAction } from "../blockchain/waves";
import { Allowlist } from "../generic/allowlists";
import { Allowlists } from "../generic/allowlists";
import { TokenLists } from "../generic/tokenlists";
import { ActionInterface, CheckStepInterface } from "../generic/interface";
import * as chalk from 'chalk';
import * as bluebird from "bluebird";
@ -19,7 +20,8 @@ const actionList: ActionInterface[] = [
new FoldersFiles(),
new EthForks(),
new LogoSize(),
new Allowlist(),
new Allowlists(),
new TokenLists(),
new Validators(),
new JsonAction(),
// chains:

13
test/asset.test.ts Normal file
View File

@ -0,0 +1,13 @@
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');
});
});

18
test/numbers.test.ts Normal file
View File

@ -0,0 +1,18 @@
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');
});
});