├── .github
└── workflows
│ └── tests.yaml
├── .gitignore
├── DEVELOPMENT.md
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
├── diffTokenLists.ts
├── getVersionUpgrade.ts
├── index.ts
├── isVersionUpdate.ts
├── minVersionBump.ts
├── nextVersion.ts
├── tokenlist.schema.json
└── types.ts
├── test
├── __snapshots__
│ └── tokenlist.schema.test.ts.snap
├── diffTokenLists.test.ts
├── getVersionUpgrade.test.ts
├── isVersionUpdate.test.ts
├── minVersionBump.test.ts
├── nextVersion.test.ts
├── schema
│ ├── bigexample.tokenlist.json
│ ├── bigwords.tokenlist.json
│ ├── empty.tokenlist.json
│ ├── example.tokenlist.json
│ ├── exampleminimum.tokenlist.json
│ ├── invaliddecimals.1.tokenlist.json
│ ├── invaliddecimals.2.tokenlist.json
│ ├── invalidlogouri.1.tokenlist.json
│ ├── invalidlogouri.2.tokenlist.json
│ ├── invalidtimestamp.tokenlist.json
│ ├── invalidtokenaddress.tokenlist.json
│ ├── invalidversion.1.tokenlist.json
│ ├── invalidversion.2.tokenlist.json
│ └── invalidversion.3.tokenlist.json
├── tokenlist.schema.test.ts
└── types.test.ts
└── tsconfig.json
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | matrix:
8 | node: [ '10', '12' ]
9 | name: Node ${{ matrix.node }}
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Setup node
13 | uses: actions/setup-node@v1
14 | with:
15 | node-version: ${{ matrix.node }}
16 | - run: npm install
17 | - run: npm test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | dist/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/DEVELOPMENT.md:
--------------------------------------------------------------------------------
1 | # Local development
2 |
3 | Below is a list of commands you will probably find useful.
4 |
5 | ## `npm start`
6 |
7 | Runs the project in development/watch mode. Your project will be rebuilt upon changes. TSDX has a special logger for you convenience. Error messages are pretty printed and formatted for compatibility VS Code's Problems tab.
8 |
9 |
10 |
11 | Your library will be rebuilt if you make edits.
12 |
13 | ## `npm run build`
14 |
15 | Bundles the package to the `dist` folder.
16 | The package is optimized and bundled with Rollup into multiple formats (CommonJS, UMD, and ES Module).
17 |
18 |
19 |
20 | ## `npm test`
21 |
22 | Runs the test watcher (Jest) in an interactive mode.
23 | By default, runs tests related to files changed since the last commit.
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Uniswap
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @uniswap/token-lists (beta)
2 |
3 | [](https://github.com/Uniswap/token-lists/actions?query=workflow%3ATests)
4 | [](https://unpkg.com/@uniswap/token-lists@latest/)
5 |
6 | This package includes a JSON schema for token lists, and TypeScript utilities for working with token lists.
7 |
8 | The JSON schema represents the technical specification for a token list which can be used in a dApp interface, such as the Uniswap Interface.
9 |
10 | ## What are token lists?
11 |
12 | Uniswap Token Lists is a specification for lists of token metadata (e.g. address, decimals, ...) that can be used by any dApp interfaces that needs one or more lists of tokens.
13 |
14 | Anyone can create and maintain a token list, as long as they follow the specification.
15 |
16 | Specifically an instance of a token list is a [JSON](https://www.json.org/json-en.html) blob that contains a list of
17 | [ERC20](https://github.com/ethereum/eips/issues/20) token metadata for use in dApp user interfaces.
18 | Token list JSON must validate against the [JSON schema](https://json-schema.org/) in order to be used in the Uniswap Interface.
19 | Tokens on token lists, and token lists themselves, are tagged so that users can easily find tokens.
20 |
21 | ## JSON Schema $id
22 |
23 | The JSON schema ID is [https://uniswap.org/tokenlist.schema.json](https://uniswap.org/tokenlist.schema.json)
24 |
25 | ## Validating token lists
26 |
27 | This package does not include code for token list validation. You can easily do this by including a library such as
28 | [ajv](https://ajv.js.org/) to perform the validation against the JSON schema. The schema is exported from the package
29 | for ease of use.
30 |
31 | ## Authoring token lists
32 |
33 | ### Manual
34 |
35 | The best way to manually author token lists is to use an editor that supports JSON schema validation. Most popular
36 | code editors do, such as [IntelliJ](https://www.jetbrains.com/help/idea/json.html#ws_json_schema_add_custom) or
37 | [VSCode](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings). Other editors
38 | can be found [here](https://json-schema.org/implementations.html#editors).
39 |
40 | The schema is registered in the [SchemaStore](https://github.com/SchemaStore/schemastore), and any file that matches
41 | the pattern `*.tokenlist.json` should
42 | [automatically utilize](https://www.jetbrains.com/help/idea/json.html#ws_json_using_schemas)
43 | the JSON schema for the [supported text editors](https://www.schemastore.org/json/#editors).
44 |
45 | In order for your token list to be able to be used, it must pass all JSON schema validation.
46 |
47 | ### Automated
48 |
49 | If you want to automate token listing, e.g. by pulling from a smart contract, or other sources, you can use this
50 | npm package to take advantage of the JSON schema for validation and the TypeScript types.
51 | Otherwise, you are simply working with JSON. All the usual tools apply, e.g.:
52 |
53 | ```typescript
54 | import { TokenList, schema } from '@uniswap/token-lists'
55 |
56 | // generate your token list however you like.
57 | const myList: TokenList = generateMyTokenList();
58 |
59 | // use a tool like `ajv` to validate your generated token list
60 | validateMyTokenList(myList, schema);
61 |
62 | // print the resulting JSON to stdout
63 | process.stdout.write(JSON.stringify(myList));
64 | ```
65 |
66 | ## Semantic versioning
67 |
68 | Lists include a `version` field, which follows [semantic versioning](https://semver.org/).
69 |
70 | List versions must follow the rules:
71 |
72 | - Increment major version when tokens are removed
73 | - Increment minor version when tokens are added
74 | - Increment patch version when tokens already on the list have minor details changed (name, symbol, logo URL, decimals)
75 |
76 | Changing a token address or chain ID is considered both a remove and an add, and should be a major version update.
77 |
78 | Note that list versioning is used to improve the user experience, but not for security, i.e. list versions are not meant
79 | to provide protection against malicious updates to a token list; i.e. the list semver is used as a lossy compression
80 | of the diff of list updates. List updates may still be diffed in the client dApp.
81 |
82 | ## Deploying your list
83 |
84 | Once you have authored the list, you can make it available at any URI. Prefer pinning your list to IPFS
85 | (e.g. via [pinata.cloud](https://pinata.cloud)) and referencing the list by an ENS name that resolves to the
86 | [contenthash](https://eips.ethereum.org/EIPS/eip-1577).
87 |
88 | ### Linking an ENS name to the list
89 |
90 | An ENS name can be assigned to an IPFS hash via the [contenthash](https://eips.ethereum.org/EIPS/eip-1577) text record.
91 | This is the preferred way of referencing your list.
92 |
93 | ## Examples
94 |
95 | You can find a simple example of a token list in [test/schema/example.tokenlist.json](test/schema/example.tokenlist.json).
96 |
97 | A snapshot of the Uniswap default list encoded as a token list is found in [test/schema/bigexample.tokenlist.json](test/schema/bigexample.tokenlist.json).
98 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@uniswap/token-lists",
3 | "author": "Moody Salem",
4 | "version": "1.0.0-beta.8",
5 | "license": "MIT",
6 | "main": "dist/index.js",
7 | "typings": "dist/index.d.ts",
8 | "files": [
9 | "dist",
10 | "src"
11 | ],
12 | "engines": {
13 | "node": ">=10"
14 | },
15 | "repository": {
16 | "url": "https://github.com/Uniswap/token-lists",
17 | "type": "git"
18 | },
19 | "scripts": {
20 | "start": "tsdx watch",
21 | "build": "tsdx build",
22 | "test": "tsdx test",
23 | "lint": "tsdx lint",
24 | "prepare": "tsdx build"
25 | },
26 | "peerDependencies": {},
27 | "husky": {
28 | "hooks": {
29 | "pre-commit": "tsdx lint"
30 | }
31 | },
32 | "prettier": {
33 | "printWidth": 80,
34 | "semi": true,
35 | "singleQuote": true,
36 | "trailingComma": "es5"
37 | },
38 | "module": "dist/token-lists.esm.js",
39 | "devDependencies": {
40 | "ajv": "^6.12.2",
41 | "husky": "^4.2.5",
42 | "tsdx": "^0.13.2",
43 | "tslib": "^2.0.0",
44 | "typescript": "^3.9.5"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/diffTokenLists.ts:
--------------------------------------------------------------------------------
1 | import { TokenInfo } from './types';
2 |
3 | export type TokenInfoChangeKey = Exclude<
4 | keyof TokenInfo,
5 | 'address' | 'chainId'
6 | >;
7 | export type TokenInfoChanges = Array;
8 |
9 | /**
10 | * compares two token info key values
11 | * this subset of full deep equal functionality does not work on objects or object arrays
12 | * @param a comparison item a
13 | * @param b comparison item b
14 | */
15 | function compareTokenInfoProperty(a: unknown, b: unknown): boolean {
16 | if (a === b) return true;
17 | if (typeof a !== typeof b) return false;
18 | if (Array.isArray(a) && Array.isArray(b)) {
19 | return a.every((el, i) => b[i] === el);
20 | }
21 | return false;
22 | }
23 |
24 | /**
25 | * Differences between a base list and an updated list.
26 | */
27 | export interface TokenListDiff {
28 | /**
29 | * Tokens from updated with chainId/address not present in base list
30 | */
31 | readonly added: TokenInfo[];
32 | /**
33 | * Tokens from base with chainId/address not present in the updated list
34 | */
35 | readonly removed: TokenInfo[];
36 | /**
37 | * The token info that changed
38 | */
39 | readonly changed: {
40 | [chainId: number]: {
41 | [address: string]: TokenInfoChanges;
42 | };
43 | };
44 | }
45 |
46 | /**
47 | * Computes the diff of a token list where the first argument is the base and the second argument is the updated list.
48 | * @param base base list
49 | * @param update updated list
50 | */
51 | export function diffTokenLists(
52 | base: TokenInfo[],
53 | update: TokenInfo[]
54 | ): TokenListDiff {
55 | const indexedBase = base.reduce<{
56 | [chainId: number]: { [address: string]: TokenInfo };
57 | }>((memo, tokenInfo) => {
58 | if (!memo[tokenInfo.chainId]) memo[tokenInfo.chainId] = {};
59 | memo[tokenInfo.chainId][tokenInfo.address] = tokenInfo;
60 | return memo;
61 | }, {});
62 |
63 | const newListUpdates = update.reduce<{
64 | added: TokenInfo[];
65 | changed: {
66 | [chainId: number]: {
67 | [address: string]: TokenInfoChanges;
68 | };
69 | };
70 | index: {
71 | [chainId: number]: {
72 | [address: string]: true;
73 | };
74 | };
75 | }>(
76 | (memo, tokenInfo) => {
77 | const baseToken = indexedBase[tokenInfo.chainId]?.[tokenInfo.address];
78 | if (!baseToken) {
79 | memo.added.push(tokenInfo);
80 | } else {
81 | const changes: TokenInfoChanges = Object.keys(tokenInfo)
82 | .filter(
83 | (s): s is TokenInfoChangeKey => s !== 'address' && s !== 'chainId'
84 | )
85 | .filter(s => {
86 | return !compareTokenInfoProperty(tokenInfo[s], baseToken[s]);
87 | });
88 | if (changes.length > 0) {
89 | if (!memo.changed[tokenInfo.chainId]) {
90 | memo.changed[tokenInfo.chainId] = {};
91 | }
92 | memo.changed[tokenInfo.chainId][tokenInfo.address] = changes;
93 | }
94 | }
95 |
96 | if (!memo.index[tokenInfo.chainId]) {
97 | memo.index[tokenInfo.chainId] = {
98 | [tokenInfo.address]: true,
99 | };
100 | } else {
101 | memo.index[tokenInfo.chainId][tokenInfo.address] = true;
102 | }
103 |
104 | return memo;
105 | },
106 | { added: [], changed: {}, index: {} }
107 | );
108 |
109 | const removed = base.reduce((list, curr) => {
110 | if (
111 | !newListUpdates.index[curr.chainId] ||
112 | !newListUpdates.index[curr.chainId][curr.address]
113 | ) {
114 | list.push(curr);
115 | }
116 | return list;
117 | }, []);
118 |
119 | return {
120 | added: newListUpdates.added,
121 | changed: newListUpdates.changed,
122 | removed,
123 | };
124 | }
125 |
--------------------------------------------------------------------------------
/src/getVersionUpgrade.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enum describing types of version differences
3 | */
4 | import { Version } from './types';
5 |
6 | export enum VersionUpgrade {
7 | NONE,
8 | PATCH,
9 | MINOR,
10 | MAJOR,
11 | }
12 |
13 | /**
14 | * Return the upgrade type from the base version to the update version.
15 | * Note that downgrades and equivalent versions are both treated as `NONE`.
16 | * @param base base list
17 | * @param update update to the list
18 | */
19 | export function getVersionUpgrade(
20 | base: Version,
21 | update: Version
22 | ): VersionUpgrade {
23 | if (update.major > base.major) {
24 | return VersionUpgrade.MAJOR;
25 | }
26 | if (update.major < base.major) {
27 | return VersionUpgrade.NONE;
28 | }
29 | if (update.minor > base.minor) {
30 | return VersionUpgrade.MINOR;
31 | }
32 | if (update.minor < base.minor) {
33 | return VersionUpgrade.NONE;
34 | }
35 | return update.patch > base.patch ? VersionUpgrade.PATCH : VersionUpgrade.NONE;
36 | }
37 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import schema from './tokenlist.schema.json';
2 |
3 | export * from './types';
4 | export * from './isVersionUpdate';
5 | export * from './getVersionUpgrade';
6 | export * from './diffTokenLists';
7 | export * from './minVersionBump';
8 | export * from './nextVersion';
9 |
10 | export { schema };
11 |
--------------------------------------------------------------------------------
/src/isVersionUpdate.ts:
--------------------------------------------------------------------------------
1 | import { getVersionUpgrade, VersionUpgrade } from './getVersionUpgrade';
2 | import { Version } from './types';
3 |
4 | /**
5 | * Returns true if versionB is an update over versionA
6 | */
7 | export function isVersionUpdate(base: Version, update: Version): boolean {
8 | return getVersionUpgrade(base, update) !== VersionUpgrade.NONE;
9 | }
10 |
--------------------------------------------------------------------------------
/src/minVersionBump.ts:
--------------------------------------------------------------------------------
1 | import { diffTokenLists } from './diffTokenLists';
2 | import { VersionUpgrade } from './getVersionUpgrade';
3 | import { TokenInfo } from './types';
4 |
5 | /**
6 | * Returns the minimum version bump for the given list
7 | * @param baseList the base list of tokens
8 | * @param updatedList the updated list of tokens
9 | */
10 | export function minVersionBump(
11 | baseList: TokenInfo[],
12 | updatedList: TokenInfo[]
13 | ): VersionUpgrade {
14 | const diff = diffTokenLists(baseList, updatedList);
15 | if (diff.added.length > 0) return VersionUpgrade.MAJOR;
16 | if (diff.removed.length > 0) return VersionUpgrade.MINOR;
17 | if (Object.keys(diff.changed).length > 0) return VersionUpgrade.PATCH;
18 | return VersionUpgrade.NONE;
19 | }
20 |
--------------------------------------------------------------------------------
/src/nextVersion.ts:
--------------------------------------------------------------------------------
1 | import { VersionUpgrade } from './getVersionUpgrade';
2 | import { Version } from './types';
3 |
4 | /**
5 | * Returns the next version of the list given a base version and the upgrade type
6 | * @param base current version
7 | * @param bump the upgrade type
8 | */
9 | export function nextVersion(base: Version, bump: VersionUpgrade): Version {
10 | switch (bump) {
11 | case VersionUpgrade.NONE:
12 | return base;
13 |
14 | case VersionUpgrade.MAJOR:
15 | return { major: base.major + 1, minor: 0, patch: 0 };
16 |
17 | case VersionUpgrade.MINOR:
18 | return {
19 | major: base.major,
20 | minor: base.minor + 1,
21 | patch: 0,
22 | };
23 |
24 | case VersionUpgrade.PATCH:
25 | return {
26 | major: base.major,
27 | minor: base.minor,
28 | patch: base.patch + 1,
29 | };
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/tokenlist.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "$id": "https://uniswap.org/tokenlist.schema.json",
4 | "title": "Uniswap Token List",
5 | "description": "Schema for lists of tokens compatible with the Uniswap Interface",
6 | "definitions": {
7 | "Version": {
8 | "type": "object",
9 | "description": "The version of the list, used in change detection",
10 | "examples": [
11 | {
12 | "major": 1,
13 | "minor": 0,
14 | "patch": 0
15 | }
16 | ],
17 | "additionalProperties": false,
18 | "properties": {
19 | "major": {
20 | "type": "integer",
21 | "description": "The major version of the list. Must be incremented when tokens are removed from the list or token addresses are changed.",
22 | "minimum": 0,
23 | "examples": [
24 | 1,
25 | 2
26 | ]
27 | },
28 | "minor": {
29 | "type": "integer",
30 | "description": "The minor version of the list. Must be incremented when tokens are added to the list.",
31 | "minimum": 0,
32 | "examples": [
33 | 0,
34 | 1
35 | ]
36 | },
37 | "patch": {
38 | "type": "integer",
39 | "description": "The patch version of the list. Must be incremented for any changes to the list.",
40 | "minimum": 0,
41 | "examples": [
42 | 0,
43 | 1
44 | ]
45 | }
46 | },
47 | "required": [
48 | "major",
49 | "minor",
50 | "patch"
51 | ]
52 | },
53 | "TagIdentifier": {
54 | "type": "string",
55 | "description": "The unique identifier of a tag",
56 | "minLength": 1,
57 | "maxLength": 10,
58 | "pattern": "^[\\w]+$",
59 | "examples": [
60 | "compound",
61 | "stablecoin"
62 | ]
63 | },
64 | "TagDefinition": {
65 | "type": "object",
66 | "description": "Definition of a tag that can be associated with a token via its identifier",
67 | "additionalProperties": false,
68 | "properties": {
69 | "name": {
70 | "type": "string",
71 | "description": "The name of the tag",
72 | "pattern": "^[ \\w]+$",
73 | "minLength": 1,
74 | "maxLength": 20
75 | },
76 | "description": {
77 | "type": "string",
78 | "description": "A user-friendly description of the tag",
79 | "pattern": "^[ \\w\\.]+$",
80 | "minLength": 1,
81 | "maxLength": 200
82 | }
83 | },
84 | "required": [
85 | "name",
86 | "description"
87 | ],
88 | "examples": [
89 | {
90 | "name": "Stablecoin",
91 | "description": "A token with value pegged to another asset"
92 | }
93 | ]
94 | },
95 | "TokenInfo": {
96 | "type": "object",
97 | "description": "Metadata for a single token in a token list",
98 | "additionalProperties": false,
99 | "properties": {
100 | "chainId": {
101 | "type": "integer",
102 | "description": "The chain ID of the Ethereum network where this token is deployed",
103 | "minimum": 1,
104 | "examples": [
105 | 1,
106 | 42
107 | ]
108 | },
109 | "address": {
110 | "type": "string",
111 | "description": "The checksummed address of the token on the specified chain ID",
112 | "pattern": "^0x[a-fA-F0-9]{40}$",
113 | "examples": [
114 | "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
115 | ]
116 | },
117 | "decimals": {
118 | "type": "integer",
119 | "description": "The number of decimals for the token balance",
120 | "minimum": 0,
121 | "maximum": 255,
122 | "examples": [
123 | 18
124 | ]
125 | },
126 | "name": {
127 | "type": "string",
128 | "description": "The name of the token",
129 | "minLength": 1,
130 | "maxLength": 40,
131 | "pattern": "^[ \\w+.'-]+$",
132 | "examples": [
133 | "USD Coin"
134 | ]
135 | },
136 | "symbol": {
137 | "type": "string",
138 | "description": "The symbol for the token; must be alphanumeric",
139 | "pattern": "^[a-zA-Z0-9+]+$",
140 | "minLength": 1,
141 | "maxLength": 20,
142 | "examples": [
143 | "USDC"
144 | ]
145 | },
146 | "logoURI": {
147 | "type": "string",
148 | "description": "A URI to the token logo asset; if not set, interface will attempt to find a logo based on the token address; suggest SVG or PNG of size 64x64",
149 | "format": "uri",
150 | "examples": [
151 | "ipfs://QmXfzKRvjZz3u5JRgC4v5mGVbm9ahrUiB4DgzHBsnWbTMM"
152 | ]
153 | },
154 | "tags": {
155 | "type": "array",
156 | "description": "An array of tag identifiers associated with the token; tags are defined at the list level",
157 | "items": {
158 | "$ref": "#/definitions/TagIdentifier"
159 | },
160 | "maxLength": 10,
161 | "examples": [
162 | "stablecoin",
163 | "compound"
164 | ]
165 | }
166 | },
167 | "required": [
168 | "chainId",
169 | "address",
170 | "decimals",
171 | "name",
172 | "symbol"
173 | ]
174 | }
175 | },
176 | "type": "object",
177 | "additionalProperties": false,
178 | "properties": {
179 | "name": {
180 | "type": "string",
181 | "description": "The name of the token list",
182 | "minLength": 1,
183 | "maxLength": 20,
184 | "pattern": "^[\\w ]+$",
185 | "examples": [
186 | "My Token List"
187 | ]
188 | },
189 | "timestamp": {
190 | "type": "string",
191 | "format": "date-time",
192 | "description": "The timestamp of this list version; i.e. when this immutable version of the list was created"
193 | },
194 | "version": {
195 | "$ref": "#/definitions/Version"
196 | },
197 | "tokens": {
198 | "type": "array",
199 | "description": "The list of tokens included in the list",
200 | "items": {
201 | "$ref": "#/definitions/TokenInfo"
202 | },
203 | "minItems": 1,
204 | "maxItems": 1000
205 | },
206 | "keywords": {
207 | "type": "array",
208 | "description": "Keywords associated with the contents of the list; may be used in list discoverability",
209 | "items": {
210 | "type": "string",
211 | "description": "A keyword to describe the contents of the list",
212 | "minLength": 1,
213 | "maxLength": 20,
214 | "pattern": "^[\\w ]+$",
215 | "examples": [
216 | "compound",
217 | "lending",
218 | "personal tokens"
219 | ]
220 | },
221 | "maxItems": 20,
222 | "uniqueItems": true
223 | },
224 | "tags": {
225 | "type": "object",
226 | "description": "A mapping of tag identifiers to their name and description",
227 | "propertyNames": {
228 | "$ref": "#/definitions/TagIdentifier"
229 | },
230 | "additionalProperties": {
231 | "$ref": "#/definitions/TagDefinition"
232 | },
233 | "maxProperties": 20,
234 | "examples": [
235 | {
236 | "stablecoin": {
237 | "name": "Stablecoin",
238 | "description": "A token with value pegged to another asset"
239 | }
240 | }
241 | ]
242 | },
243 | "logoURI": {
244 | "type": "string",
245 | "description": "A URI for the logo of the token list; prefer SVG or PNG of size 256x256",
246 | "format": "uri",
247 | "examples": [
248 | "ipfs://QmXfzKRvjZz3u5JRgC4v5mGVbm9ahrUiB4DgzHBsnWbTMM"
249 | ]
250 | }
251 | },
252 | "required": [
253 | "name",
254 | "timestamp",
255 | "version",
256 | "tokens"
257 | ]
258 | }
259 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export interface TokenInfo {
2 | readonly chainId: number;
3 | readonly address: string;
4 | readonly name: string;
5 | readonly decimals: number;
6 | readonly symbol: string;
7 | readonly logoURI?: string;
8 | readonly tags?: string[];
9 | }
10 |
11 | export interface Version {
12 | readonly major: number;
13 | readonly minor: number;
14 | readonly patch: number;
15 | }
16 |
17 | export interface Tags {
18 | readonly [tagId: string]: {
19 | readonly name: string;
20 | readonly description: string;
21 | };
22 | }
23 |
24 | export interface TokenList {
25 | readonly name: string;
26 | readonly timestamp: string;
27 | readonly version: Version;
28 | readonly tokens: TokenInfo[];
29 | readonly keywords?: string[];
30 | readonly tags?: Tags;
31 | }
32 |
--------------------------------------------------------------------------------
/test/__snapshots__/tokenlist.schema.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`schema checks token address 1`] = `
4 | Array [
5 | Object {
6 | "dataPath": ".tokens[0].address",
7 | "keyword": "pattern",
8 | "message": "should match pattern \\"^0x[a-fA-F0-9]{40}$\\"",
9 | "params": Object {
10 | "pattern": "^0x[a-fA-F0-9]{40}$",
11 | },
12 | "schemaPath": "#/properties/address/pattern",
13 | },
14 | ]
15 | `;
16 |
17 | exports[`schema checks version 1`] = `
18 | Array [
19 | Object {
20 | "dataPath": ".version.patch",
21 | "keyword": "minimum",
22 | "message": "should be >= 0",
23 | "params": Object {
24 | "comparison": ">=",
25 | "exclusive": false,
26 | "limit": 0,
27 | },
28 | "schemaPath": "#/definitions/Version/properties/patch/minimum",
29 | },
30 | ]
31 | `;
32 |
33 | exports[`schema checks version 2`] = `
34 | Array [
35 | Object {
36 | "dataPath": ".version.minor",
37 | "keyword": "minimum",
38 | "message": "should be >= 0",
39 | "params": Object {
40 | "comparison": ">=",
41 | "exclusive": false,
42 | "limit": 0,
43 | },
44 | "schemaPath": "#/definitions/Version/properties/minor/minimum",
45 | },
46 | ]
47 | `;
48 |
49 | exports[`schema checks version 3`] = `
50 | Array [
51 | Object {
52 | "dataPath": ".version.major",
53 | "keyword": "minimum",
54 | "message": "should be >= 0",
55 | "params": Object {
56 | "comparison": ">=",
57 | "exclusive": false,
58 | "limit": 0,
59 | },
60 | "schemaPath": "#/definitions/Version/properties/major/minimum",
61 | },
62 | ]
63 | `;
64 |
65 | exports[`schema empty list fails 1`] = `
66 | Array [
67 | Object {
68 | "dataPath": ".tokens",
69 | "keyword": "minItems",
70 | "message": "should NOT have fewer than 1 items",
71 | "params": Object {
72 | "limit": 1,
73 | },
74 | "schemaPath": "#/properties/tokens/minItems",
75 | },
76 | ]
77 | `;
78 |
79 | exports[`schema fails with big names 1`] = `
80 | Array [
81 | Object {
82 | "dataPath": ".tokens[0].name",
83 | "keyword": "maxLength",
84 | "message": "should NOT be longer than 40 characters",
85 | "params": Object {
86 | "limit": 40,
87 | },
88 | "schemaPath": "#/properties/name/maxLength",
89 | },
90 | ]
91 | `;
92 |
93 | exports[`schema invalid decimals 1`] = `
94 | Array [
95 | Object {
96 | "dataPath": ".tokens[0].decimals",
97 | "keyword": "maximum",
98 | "message": "should be <= 255",
99 | "params": Object {
100 | "comparison": "<=",
101 | "exclusive": false,
102 | "limit": 255,
103 | },
104 | "schemaPath": "#/properties/decimals/maximum",
105 | },
106 | ]
107 | `;
108 |
109 | exports[`schema invalid decimals 2`] = `
110 | Array [
111 | Object {
112 | "dataPath": ".tokens[0].decimals",
113 | "keyword": "minimum",
114 | "message": "should be >= 0",
115 | "params": Object {
116 | "comparison": ">=",
117 | "exclusive": false,
118 | "limit": 0,
119 | },
120 | "schemaPath": "#/properties/decimals/minimum",
121 | },
122 | ]
123 | `;
124 |
125 | exports[`schema invalid logo URI 1`] = `
126 | Array [
127 | Object {
128 | "dataPath": ".tokens[0].logoURI",
129 | "keyword": "format",
130 | "message": "should match format \\"uri\\"",
131 | "params": Object {
132 | "format": "uri",
133 | },
134 | "schemaPath": "#/properties/logoURI/format",
135 | },
136 | ]
137 | `;
138 |
139 | exports[`schema invalid logo URI 2`] = `
140 | Array [
141 | Object {
142 | "dataPath": ".logoURI",
143 | "keyword": "format",
144 | "message": "should match format \\"uri\\"",
145 | "params": Object {
146 | "format": "uri",
147 | },
148 | "schemaPath": "#/properties/logoURI/format",
149 | },
150 | ]
151 | `;
152 |
153 | exports[`schema invalid timestamp 1`] = `
154 | Array [
155 | Object {
156 | "dataPath": ".timestamp",
157 | "keyword": "format",
158 | "message": "should match format \\"date-time\\"",
159 | "params": Object {
160 | "format": "date-time",
161 | },
162 | "schemaPath": "#/properties/timestamp/format",
163 | },
164 | ]
165 | `;
166 |
167 | exports[`schema minimum example schema 1`] = `null`;
168 |
169 | exports[`schema requires name, timestamp, version, tokens 1`] = `
170 | Array [
171 | Object {
172 | "dataPath": "",
173 | "keyword": "required",
174 | "message": "should have required property 'name'",
175 | "params": Object {
176 | "missingProperty": "name",
177 | },
178 | "schemaPath": "#/required",
179 | },
180 | Object {
181 | "dataPath": "",
182 | "keyword": "required",
183 | "message": "should have required property 'timestamp'",
184 | "params": Object {
185 | "missingProperty": "timestamp",
186 | },
187 | "schemaPath": "#/required",
188 | },
189 | Object {
190 | "dataPath": "",
191 | "keyword": "required",
192 | "message": "should have required property 'version'",
193 | "params": Object {
194 | "missingProperty": "version",
195 | },
196 | "schemaPath": "#/required",
197 | },
198 | Object {
199 | "dataPath": "",
200 | "keyword": "required",
201 | "message": "should have required property 'tokens'",
202 | "params": Object {
203 | "missingProperty": "tokens",
204 | },
205 | "schemaPath": "#/required",
206 | },
207 | ]
208 | `;
209 |
210 | exports[`schema works for big example schema 1`] = `null`;
211 |
212 | exports[`schema works for example schema 1`] = `null`;
213 |
--------------------------------------------------------------------------------
/test/diffTokenLists.test.ts:
--------------------------------------------------------------------------------
1 | import { diffTokenLists, TokenInfo } from '../src';
2 |
3 | const tokenA: TokenInfo = {
4 | chainId: 1,
5 | address: '0x0a',
6 | logoURI: 'ipfs://test',
7 | symbol: 'abcd',
8 | name: 'token a',
9 | decimals: 18,
10 | tags: ['hello', 'world'],
11 | };
12 | const tokenAChangedNameDecimals: TokenInfo = {
13 | ...tokenA,
14 | name: 'blah',
15 | decimals: 12,
16 | };
17 | const tokenAChangedTags: TokenInfo = {
18 | ...tokenA,
19 | tags: ['hello', 'worlds'],
20 | };
21 | const tokenB: TokenInfo = {
22 | chainId: 1,
23 | address: '0x0b',
24 | logoURI: 'ipfs://blah',
25 | symbol: 'defg',
26 | name: 'token b',
27 | decimals: 9,
28 | tags: ['token', 'other'],
29 | };
30 |
31 | describe('#diffTokenLists', () => {
32 | it('change address', () => {
33 | expect(diffTokenLists([tokenA], [tokenB])).toEqual({
34 | added: [tokenB],
35 | removed: [tokenA],
36 | changed: {},
37 | });
38 | });
39 |
40 | it('change name', () => {
41 | expect(
42 | diffTokenLists([tokenB, tokenA], [tokenB, tokenAChangedNameDecimals])
43 | ).toEqual({
44 | added: [],
45 | removed: [],
46 | changed: {
47 | 1: {
48 | '0x0a': ['name', 'decimals'],
49 | },
50 | },
51 | });
52 | });
53 |
54 | it('change tags', () => {
55 | expect(diffTokenLists([tokenB, tokenA], [tokenAChangedTags])).toEqual({
56 | added: [],
57 | removed: [tokenB],
58 | changed: {
59 | 1: {
60 | '0x0a': ['tags'],
61 | },
62 | },
63 | });
64 | });
65 | it('remove tags', () => {
66 | expect(
67 | diffTokenLists([tokenB, tokenA], [{ ...tokenA, tags: undefined }])
68 | ).toEqual({
69 | added: [],
70 | removed: [tokenB],
71 | changed: {
72 | 1: {
73 | '0x0a': ['tags'],
74 | },
75 | },
76 | });
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/test/getVersionUpgrade.test.ts:
--------------------------------------------------------------------------------
1 | import { getVersionUpgrade, VersionUpgrade } from '../src';
2 |
3 | describe('#getVersionUpgrade', () => {
4 | it('major version', () => {
5 | expect(
6 | getVersionUpgrade(
7 | { major: 1, minor: 0, patch: 0 },
8 | { major: 2, minor: 0, patch: 0 }
9 | )
10 | ).toEqual(VersionUpgrade.MAJOR);
11 | expect(
12 | getVersionUpgrade(
13 | { major: 1, minor: 0, patch: 0 },
14 | { major: 1, minor: 0, patch: 0 }
15 | )
16 | ).toEqual(VersionUpgrade.NONE);
17 | expect(
18 | getVersionUpgrade(
19 | { major: 1, minor: 0, patch: 0 },
20 | { major: 0, minor: 0, patch: 0 }
21 | )
22 | ).toEqual(VersionUpgrade.NONE);
23 | expect(
24 | getVersionUpgrade(
25 | { major: 1, minor: 1, patch: 0 },
26 | { major: 2, minor: 0, patch: 0 }
27 | )
28 | ).toEqual(VersionUpgrade.MAJOR);
29 | expect(
30 | getVersionUpgrade(
31 | { major: 1, minor: 0, patch: 2 },
32 | { major: 1, minor: 0, patch: 1 }
33 | )
34 | ).toEqual(VersionUpgrade.NONE);
35 | expect(
36 | getVersionUpgrade(
37 | { major: 1, minor: 0, patch: 2 },
38 | { major: 1, minor: 0, patch: 2 }
39 | )
40 | ).toEqual(VersionUpgrade.NONE);
41 | });
42 |
43 | it('minor version', () => {
44 | expect(
45 | getVersionUpgrade(
46 | { major: 1, minor: 0, patch: 0 },
47 | { major: 1, minor: 1, patch: 0 }
48 | )
49 | ).toEqual(VersionUpgrade.MINOR);
50 | expect(
51 | getVersionUpgrade(
52 | { major: 1, minor: 0, patch: 0 },
53 | { major: 1, minor: 0, patch: 0 }
54 | )
55 | ).toEqual(VersionUpgrade.NONE);
56 | expect(
57 | getVersionUpgrade(
58 | { major: 1, minor: 1, patch: 0 },
59 | { major: 1, minor: 0, patch: 0 }
60 | )
61 | ).toEqual(VersionUpgrade.NONE);
62 | expect(
63 | getVersionUpgrade(
64 | { major: 1, minor: 1, patch: 1 },
65 | { major: 1, minor: 2, patch: 0 }
66 | )
67 | ).toEqual(VersionUpgrade.MINOR);
68 | });
69 |
70 | it('patch version', () => {
71 | expect(
72 | getVersionUpgrade(
73 | { major: 1, minor: 0, patch: 0 },
74 | { major: 1, minor: 1, patch: 0 }
75 | )
76 | ).toEqual(VersionUpgrade.MINOR);
77 | expect(
78 | getVersionUpgrade(
79 | { major: 1, minor: 0, patch: 0 },
80 | { major: 1, minor: 0, patch: 0 }
81 | )
82 | ).toEqual(VersionUpgrade.NONE);
83 | expect(
84 | getVersionUpgrade(
85 | { major: 1, minor: 1, patch: 0 },
86 | { major: 1, minor: 0, patch: 0 }
87 | )
88 | ).toEqual(VersionUpgrade.NONE);
89 | expect(
90 | getVersionUpgrade(
91 | { major: 1, minor: 1, patch: 1 },
92 | { major: 1, minor: 2, patch: 0 }
93 | )
94 | ).toEqual(VersionUpgrade.MINOR);
95 | expect(
96 | getVersionUpgrade(
97 | { major: 1, minor: 1, patch: 1 },
98 | { major: 2, minor: 1, patch: 1 }
99 | )
100 | ).toEqual(VersionUpgrade.MAJOR);
101 | expect(
102 | getVersionUpgrade(
103 | { major: 2, minor: 1, patch: 1 },
104 | { major: 2, minor: 1, patch: 2 }
105 | )
106 | ).toEqual(VersionUpgrade.PATCH);
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/test/isVersionUpdate.test.ts:
--------------------------------------------------------------------------------
1 | import { isVersionUpdate } from '../src';
2 |
3 | describe('#isVersionUpdate', () => {
4 | it('major version', () => {
5 | expect(
6 | isVersionUpdate(
7 | { major: 1, minor: 0, patch: 0 },
8 | { major: 2, minor: 0, patch: 0 }
9 | )
10 | ).toEqual(true);
11 | expect(
12 | isVersionUpdate(
13 | { major: 1, minor: 0, patch: 0 },
14 | { major: 1, minor: 0, patch: 0 }
15 | )
16 | ).toEqual(false);
17 | expect(
18 | isVersionUpdate(
19 | { major: 1, minor: 0, patch: 0 },
20 | { major: 0, minor: 0, patch: 0 }
21 | )
22 | ).toEqual(false);
23 | expect(
24 | isVersionUpdate(
25 | { major: 1, minor: 1, patch: 0 },
26 | { major: 2, minor: 0, patch: 0 }
27 | )
28 | ).toEqual(true);
29 | expect(
30 | isVersionUpdate(
31 | { major: 1, minor: 0, patch: 2 },
32 | { major: 1, minor: 0, patch: 1 }
33 | )
34 | ).toEqual(false);
35 | expect(
36 | isVersionUpdate(
37 | { major: 1, minor: 0, patch: 2 },
38 | { major: 1, minor: 0, patch: 2 }
39 | )
40 | ).toEqual(false);
41 | });
42 |
43 | it('minor version', () => {
44 | expect(
45 | isVersionUpdate(
46 | { major: 1, minor: 0, patch: 0 },
47 | { major: 1, minor: 1, patch: 0 }
48 | )
49 | ).toEqual(true);
50 | expect(
51 | isVersionUpdate(
52 | { major: 1, minor: 0, patch: 0 },
53 | { major: 1, minor: 0, patch: 0 }
54 | )
55 | ).toEqual(false);
56 | expect(
57 | isVersionUpdate(
58 | { major: 1, minor: 1, patch: 0 },
59 | { major: 1, minor: 0, patch: 0 }
60 | )
61 | ).toEqual(false);
62 | expect(
63 | isVersionUpdate(
64 | { major: 1, minor: 1, patch: 1 },
65 | { major: 1, minor: 2, patch: 0 }
66 | )
67 | ).toEqual(true);
68 | });
69 |
70 | it('patch version', () => {
71 | expect(
72 | isVersionUpdate(
73 | { major: 1, minor: 0, patch: 0 },
74 | { major: 1, minor: 1, patch: 0 }
75 | )
76 | ).toEqual(true);
77 | expect(
78 | isVersionUpdate(
79 | { major: 1, minor: 0, patch: 0 },
80 | { major: 1, minor: 0, patch: 0 }
81 | )
82 | ).toEqual(false);
83 | expect(
84 | isVersionUpdate(
85 | { major: 1, minor: 1, patch: 0 },
86 | { major: 1, minor: 0, patch: 0 }
87 | )
88 | ).toEqual(false);
89 | expect(
90 | isVersionUpdate(
91 | { major: 1, minor: 1, patch: 1 },
92 | { major: 1, minor: 2, patch: 0 }
93 | )
94 | ).toEqual(true);
95 | expect(
96 | isVersionUpdate(
97 | { major: 1, minor: 1, patch: 1 },
98 | { major: 2, minor: 1, patch: 1 }
99 | )
100 | ).toEqual(true);
101 | expect(
102 | isVersionUpdate(
103 | { major: 2, minor: 1, patch: 1 },
104 | { major: 2, minor: 1, patch: 2 }
105 | )
106 | ).toEqual(true);
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/test/minVersionBump.test.ts:
--------------------------------------------------------------------------------
1 | import { minVersionBump, TokenInfo, VersionUpgrade } from '../src';
2 | const tokenA: TokenInfo = {
3 | chainId: 1,
4 | address: '0x0a',
5 | logoURI: 'ipfs://test',
6 | symbol: 'abcd',
7 | name: 'token a',
8 | decimals: 18,
9 | tags: ['hello', 'world'],
10 | };
11 | const tokenAChangedNameDecimals: TokenInfo = {
12 | ...tokenA,
13 | name: 'blah',
14 | decimals: 12,
15 | };
16 | const tokenAChangedTags: TokenInfo = {
17 | ...tokenA,
18 | tags: ['hello', 'worlds'],
19 | };
20 | const tokenB: TokenInfo = {
21 | chainId: 1,
22 | address: '0x0b',
23 | logoURI: 'ipfs://blah',
24 | symbol: 'defg',
25 | name: 'token b',
26 | decimals: 9,
27 | tags: ['token', 'other'],
28 | };
29 | describe('#minVersionBump', () => {
30 | it('empty', () => {
31 | expect(minVersionBump([], [])).toBe(VersionUpgrade.NONE);
32 | });
33 | it('patch for tag changes only', () => {
34 | expect(minVersionBump([tokenA], [tokenAChangedTags])).toBe(
35 | VersionUpgrade.PATCH
36 | );
37 | });
38 | it('patch for name/decimals changes', () => {
39 | expect(minVersionBump([tokenA], [tokenAChangedNameDecimals])).toBe(
40 | VersionUpgrade.PATCH
41 | );
42 | });
43 | it('minor for remove only', () => {
44 | expect(minVersionBump([tokenA], [])).toBe(VersionUpgrade.MINOR);
45 | });
46 | it('major for add', () => {
47 | expect(minVersionBump([], [tokenA])).toBe(VersionUpgrade.MAJOR);
48 | });
49 | it('major for add/remove', () => {
50 | expect(minVersionBump([tokenB], [tokenA])).toBe(VersionUpgrade.MAJOR);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/test/nextVersion.test.ts:
--------------------------------------------------------------------------------
1 | import { nextVersion, VersionUpgrade } from '../src';
2 |
3 | describe('#nextVersion', () => {
4 | it('none', () => {
5 | expect(
6 | nextVersion({ major: 1, minor: 0, patch: 0 }, VersionUpgrade.NONE)
7 | ).toEqual({ major: 1, minor: 0, patch: 0 });
8 | expect(
9 | nextVersion({ major: 1, minor: 2, patch: 0 }, VersionUpgrade.NONE)
10 | ).toEqual({ major: 1, minor: 2, patch: 0 });
11 | expect(
12 | nextVersion({ major: 2, minor: 5, patch: 3 }, VersionUpgrade.NONE)
13 | ).toEqual({ major: 2, minor: 5, patch: 3 });
14 | });
15 | it('patch', () => {
16 | expect(
17 | nextVersion({ major: 1, minor: 0, patch: 0 }, VersionUpgrade.PATCH)
18 | ).toEqual({ major: 1, minor: 0, patch: 1 });
19 | expect(
20 | nextVersion({ major: 1, minor: 2, patch: 0 }, VersionUpgrade.PATCH)
21 | ).toEqual({ major: 1, minor: 2, patch: 1 });
22 | expect(
23 | nextVersion({ major: 2, minor: 5, patch: 3 }, VersionUpgrade.PATCH)
24 | ).toEqual({ major: 2, minor: 5, patch: 4 });
25 | });
26 | it('minor', () => {
27 | expect(
28 | nextVersion({ major: 1, minor: 0, patch: 0 }, VersionUpgrade.MINOR)
29 | ).toEqual({ major: 1, minor: 1, patch: 0 });
30 | expect(
31 | nextVersion({ major: 1, minor: 2, patch: 0 }, VersionUpgrade.MINOR)
32 | ).toEqual({ major: 1, minor: 3, patch: 0 });
33 | expect(
34 | nextVersion({ major: 2, minor: 5, patch: 3 }, VersionUpgrade.MINOR)
35 | ).toEqual({ major: 2, minor: 6, patch: 0 });
36 | });
37 | it('major', () => {
38 | expect(
39 | nextVersion({ major: 1, minor: 0, patch: 0 }, VersionUpgrade.MAJOR)
40 | ).toEqual({ major: 2, minor: 0, patch: 0 });
41 | expect(
42 | nextVersion({ major: 1, minor: 2, patch: 0 }, VersionUpgrade.MAJOR)
43 | ).toEqual({ major: 2, minor: 0, patch: 0 });
44 | expect(
45 | nextVersion({ major: 2, minor: 5, patch: 3 }, VersionUpgrade.MAJOR)
46 | ).toEqual({ major: 3, minor: 0, patch: 0 });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/test/schema/bigexample.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Example List",
3 | "timestamp": "2020-06-15T12:00:00+00:00",
4 | "version": {
5 | "major": 1,
6 | "minor": 0,
7 | "patch": 0
8 | },
9 | "keywords": [
10 | "uniswap",
11 | "default",
12 | "list"
13 | ],
14 | "tokens": [
15 | {
16 | "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
17 | "chainId": 1,
18 | "name": "Wrapped Ether",
19 | "symbol": "WETH",
20 | "decimals": 18
21 | },
22 | {
23 | "address": "0xB6eD7644C69416d67B522e20bC294A9a9B405B31",
24 | "chainId": 1,
25 | "name": "0xBitcoin Token",
26 | "symbol": "0xBTC",
27 | "decimals": 8
28 | },
29 | {
30 | "address": "0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d",
31 | "chainId": 1,
32 | "name": "Aave Interest bearing DAI",
33 | "symbol": "aDAI",
34 | "decimals": 18
35 | },
36 | {
37 | "address": "0x737F98AC8cA59f2C68aD658E3C3d8C8963E40a4c",
38 | "chainId": 1,
39 | "name": "Amon",
40 | "symbol": "AMN",
41 | "decimals": 18
42 | },
43 | {
44 | "address": "0xD46bA6D942050d489DBd938a2C909A5d5039A161",
45 | "chainId": 1,
46 | "name": "Ampleforth",
47 | "symbol": "AMPL",
48 | "decimals": 9
49 | },
50 | {
51 | "address": "0xcD62b1C403fa761BAadFC74C525ce2B51780b184",
52 | "chainId": 1,
53 | "name": "Aragon Network Juror",
54 | "symbol": "ANJ",
55 | "decimals": 18
56 | },
57 | {
58 | "address": "0x960b236A07cf122663c4303350609A66A7B288C0",
59 | "chainId": 1,
60 | "name": "Aragon Network Token",
61 | "symbol": "ANT",
62 | "decimals": 18
63 | },
64 | {
65 | "address": "0x27054b13b1B798B345b591a4d22e6562d47eA75a",
66 | "chainId": 1,
67 | "name": "AirSwap Token",
68 | "symbol": "AST",
69 | "decimals": 4
70 | },
71 | {
72 | "address": "0xBA11D00c5f74255f56a5E366F4F77f5A186d7f55",
73 | "chainId": 1,
74 | "name": "BandToken",
75 | "symbol": "BAND",
76 | "decimals": 18
77 | },
78 | {
79 | "address": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF",
80 | "chainId": 1,
81 | "name": "Basic Attention Token",
82 | "symbol": "BAT",
83 | "decimals": 18
84 | },
85 | {
86 | "address": "0x107c4504cd79C5d2696Ea0030a8dD4e92601B82e",
87 | "chainId": 1,
88 | "name": "Bloom Token",
89 | "symbol": "BLT",
90 | "decimals": 18
91 | },
92 | {
93 | "address": "0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C",
94 | "chainId": 1,
95 | "name": "Bancor Network Token",
96 | "symbol": "BNT",
97 | "decimals": 18
98 | },
99 | {
100 | "address": "0x0327112423F3A68efdF1fcF402F6c5CB9f7C33fd",
101 | "chainId": 1,
102 | "name": "PieDAO BTC++",
103 | "symbol": "BTC++",
104 | "decimals": 18
105 | },
106 | {
107 | "address": "0x4F9254C83EB525f9FCf346490bbb3ed28a81C667",
108 | "chainId": 1,
109 | "name": "CelerToken",
110 | "symbol": "CELR",
111 | "decimals": 18
112 | },
113 | {
114 | "address": "0xF5DCe57282A584D2746FaF1593d3121Fcac444dC",
115 | "chainId": 1,
116 | "name": "Compound Dai",
117 | "symbol": "cSAI",
118 | "decimals": 8
119 | },
120 | {
121 | "address": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643",
122 | "chainId": 1,
123 | "name": "Compound Dai",
124 | "symbol": "cDAI",
125 | "decimals": 8
126 | },
127 | {
128 | "address": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
129 | "chainId": 1,
130 | "name": "Compound USD Coin",
131 | "symbol": "cUSDC",
132 | "decimals": 8
133 | },
134 | {
135 | "address": "0xaaAEBE6Fe48E54f431b0C390CfaF0b017d09D42d",
136 | "chainId": 1,
137 | "name": "Celsius",
138 | "symbol": "CEL",
139 | "decimals": 4
140 | },
141 | {
142 | "address": "0x06AF07097C9Eeb7fD685c692751D5C66dB49c215",
143 | "chainId": 1,
144 | "name": "Chai",
145 | "symbol": "CHAI",
146 | "decimals": 18
147 | },
148 | {
149 | "address": "0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359",
150 | "chainId": 1,
151 | "name": "Dai Stablecoin v1.0 aka SAI",
152 | "symbol": "SAI",
153 | "decimals": 18
154 | },
155 | {
156 | "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
157 | "chainId": 1,
158 | "name": "Dai Stablecoin",
159 | "symbol": "DAI",
160 | "decimals": 18
161 | },
162 | {
163 | "address": "0x0Cf0Ee63788A0849fE5297F3407f701E122cC023",
164 | "chainId": 1,
165 | "name": "Streamr DATAcoin",
166 | "symbol": "DATA",
167 | "decimals": 18
168 | },
169 | {
170 | "address": "0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A",
171 | "chainId": 1,
172 | "name": "DigixDAO",
173 | "symbol": "DGD",
174 | "decimals": 9
175 | },
176 | {
177 | "address": "0x4f3AfEC4E5a3F2A6a1A411DEF7D7dFe50eE057bF",
178 | "chainId": 1,
179 | "name": "Digix Gold Token",
180 | "symbol": "DGX",
181 | "decimals": 9
182 | },
183 | {
184 | "address": "0xc719d010B63E5bbF2C0551872CD5316ED26AcD83",
185 | "chainId": 1,
186 | "name": "Decentralized Insurance Protocol",
187 | "symbol": "DIP",
188 | "decimals": 18
189 | },
190 | {
191 | "address": "0xC0F9bD5Fa5698B6505F643900FFA515Ea5dF54A9",
192 | "chainId": 1,
193 | "name": "Donut",
194 | "symbol": "DONUT",
195 | "decimals": 18
196 | },
197 | {
198 | "address": "0x86FADb80d8D2cff3C3680819E4da99C10232Ba0F",
199 | "chainId": 1,
200 | "name": "EURBASE Stablecoin",
201 | "symbol": "EBASE",
202 | "decimals": 18
203 | },
204 | {
205 | "address": "0xF629cBd94d3791C9250152BD8dfBDF380E2a3B9c",
206 | "chainId": 1,
207 | "name": "Enjin Coin",
208 | "symbol": "ENJ",
209 | "decimals": 18
210 | },
211 | {
212 | "address": "0x06f65b8CfCb13a9FE37d836fE9708dA38Ecb29B2",
213 | "chainId": 1,
214 | "name": "SAINT FAME Genesis Shirt",
215 | "symbol": "FAME",
216 | "decimals": 18
217 | },
218 | {
219 | "address": "0x4946Fcea7C692606e8908002e55A582af44AC121",
220 | "chainId": 1,
221 | "name": "FOAM Token",
222 | "symbol": "FOAM",
223 | "decimals": 18
224 | },
225 | {
226 | "address": "0x419D0d8BdD9aF5e606Ae2232ed285Aff190E711b",
227 | "chainId": 1,
228 | "name": "FunFair",
229 | "symbol": "FUN",
230 | "decimals": 8
231 | },
232 | {
233 | "address": "0x4a57E687b9126435a9B19E4A802113e266AdeBde",
234 | "chainId": 1,
235 | "name": "Flexacoin",
236 | "symbol": "FXC",
237 | "decimals": 18
238 | },
239 | {
240 | "address": "0x543Ff227F64Aa17eA132Bf9886cAb5DB55DCAddf",
241 | "chainId": 1,
242 | "name": "DAOstack",
243 | "symbol": "GEN",
244 | "decimals": 18
245 | },
246 | {
247 | "address": "0x6810e776880C02933D47DB1b9fc05908e5386b96",
248 | "chainId": 1,
249 | "name": "Gnosis Token",
250 | "symbol": "GNO",
251 | "decimals": 18
252 | },
253 | {
254 | "address": "0x12B19D3e2ccc14Da04FAe33e63652ce469b3F2FD",
255 | "chainId": 1,
256 | "name": "GRID Token",
257 | "symbol": "GRID",
258 | "decimals": 12
259 | },
260 | {
261 | "address": "0x0000000000b3F879cb30FE243b4Dfee438691c04",
262 | "chainId": 1,
263 | "name": "Gastoken.io",
264 | "symbol": "GST2",
265 | "decimals": 2
266 | },
267 | {
268 | "address": "0xF1290473E210b2108A85237fbCd7b6eb42Cc654F",
269 | "chainId": 1,
270 | "name": "HedgeTrade",
271 | "symbol": "HEDG",
272 | "decimals": 18
273 | },
274 | {
275 | "address": "0x6c6EE5e31d828De241282B9606C8e98Ea48526E2",
276 | "chainId": 1,
277 | "name": "HoloToken",
278 | "symbol": "HOT",
279 | "decimals": 18
280 | },
281 | {
282 | "address": "0x493C57C4763932315A328269E1ADaD09653B9081",
283 | "chainId": 1,
284 | "name": "Fulcrum DAI iToken",
285 | "symbol": "iDAI",
286 | "decimals": 18
287 | },
288 | {
289 | "address": "0x14094949152EDDBFcd073717200DA82fEd8dC960",
290 | "chainId": 1,
291 | "name": "Fulcrum SAI iToken ",
292 | "symbol": "iSAI",
293 | "decimals": 18
294 | },
295 | {
296 | "address": "0x6fB3e0A217407EFFf7Ca062D46c26E5d60a14d69",
297 | "chainId": 1,
298 | "name": "IoTeX Network",
299 | "symbol": "IOTX",
300 | "decimals": 18
301 | },
302 | {
303 | "address": "0x4Cd988AfBad37289BAAf53C13e98E2BD46aAEa8c",
304 | "chainId": 1,
305 | "name": "KEY",
306 | "symbol": "KEY",
307 | "decimals": 18
308 | },
309 | {
310 | "address": "0xdd974D5C2e2928deA5F71b9825b8b646686BD200",
311 | "chainId": 1,
312 | "name": "Kyber Network Crystal",
313 | "symbol": "KNC",
314 | "decimals": 18
315 | },
316 | {
317 | "address": "0x514910771AF9Ca656af840dff83E8264EcF986CA",
318 | "chainId": 1,
319 | "name": "ChainLink Token",
320 | "symbol": "LINK",
321 | "decimals": 18
322 | },
323 | {
324 | "address": "0xBBbbCA6A901c926F240b89EacB641d8Aec7AEafD",
325 | "chainId": 1,
326 | "name": "LoopringCoin V2",
327 | "symbol": "LRC",
328 | "decimals": 18
329 | },
330 | {
331 | "address": "0x80fB784B7eD66730e8b1DBd9820aFD29931aab03",
332 | "chainId": 1,
333 | "name": "EthLend Token",
334 | "symbol": "LEND",
335 | "decimals": 18
336 | },
337 | {
338 | "address": "0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0",
339 | "chainId": 1,
340 | "name": "LoomToken",
341 | "symbol": "LOOM",
342 | "decimals": 18
343 | },
344 | {
345 | "address": "0x58b6A8A3302369DAEc383334672404Ee733aB239",
346 | "chainId": 1,
347 | "name": "Livepeer Token",
348 | "symbol": "LPT",
349 | "decimals": 18
350 | },
351 | {
352 | "address": "0xD29F0b5b3F50b07Fe9a9511F7d86F4f4bAc3f8c4",
353 | "chainId": 1,
354 | "name": "Liquidity.Network Token",
355 | "symbol": "LQD",
356 | "decimals": 18
357 | },
358 | {
359 | "address": "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942",
360 | "chainId": 1,
361 | "name": "Decentraland MANA",
362 | "symbol": "MANA",
363 | "decimals": 18
364 | },
365 | {
366 | "address": "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
367 | "chainId": 1,
368 | "name": "Matic Token",
369 | "symbol": "MATIC",
370 | "decimals": 18
371 | },
372 | {
373 | "address": "0x8888889213DD4dA823EbDD1e235b09590633C150",
374 | "chainId": 1,
375 | "name": "Marblecoin",
376 | "symbol": "MBC",
377 | "decimals": 18
378 | },
379 | {
380 | "address": "0xd15eCDCF5Ea68e3995b2D0527A0aE0a3258302F8",
381 | "chainId": 1,
382 | "name": "MachiX Token",
383 | "symbol": "MCX",
384 | "decimals": 18
385 | },
386 | {
387 | "address": "0xa3d58c4E56fedCae3a7c43A725aeE9A71F0ece4e",
388 | "chainId": 1,
389 | "name": "Metronome",
390 | "symbol": "MET",
391 | "decimals": 18
392 | },
393 | {
394 | "address": "0x80f222a749a2e18Eb7f676D371F19ad7EFEEe3b7",
395 | "chainId": 1,
396 | "name": "Magnolia Token",
397 | "symbol": "MGN",
398 | "decimals": 18
399 | },
400 | {
401 | "address": "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2",
402 | "chainId": 1,
403 | "name": "Maker",
404 | "symbol": "MKR",
405 | "decimals": 18
406 | },
407 | {
408 | "address": "0xec67005c4E498Ec7f55E092bd1d35cbC47C91892",
409 | "chainId": 1,
410 | "name": "Melon Token",
411 | "symbol": "MLN",
412 | "decimals": 18
413 | },
414 | {
415 | "address": "0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e",
416 | "chainId": 1,
417 | "name": "Modum Token",
418 | "symbol": "MOD",
419 | "decimals": 0
420 | },
421 | {
422 | "address": "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206",
423 | "chainId": 1,
424 | "name": "Nexo",
425 | "symbol": "NEXO",
426 | "decimals": 18
427 | },
428 | {
429 | "address": "0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671",
430 | "chainId": 1,
431 | "name": "Numeraire",
432 | "symbol": "NMR",
433 | "decimals": 18
434 | },
435 | {
436 | "address": "0x985dd3D42De1e256d09e1c10F112bCCB8015AD41",
437 | "chainId": 1,
438 | "name": "OceanToken",
439 | "symbol": "OCEAN",
440 | "decimals": 18
441 | },
442 | {
443 | "address": "0x4575f41308EC1483f3d399aa9a2826d74Da13Deb",
444 | "chainId": 1,
445 | "name": "Orchid",
446 | "symbol": "OXT",
447 | "decimals": 18
448 | },
449 | {
450 | "address": "0xD56daC73A4d6766464b38ec6D91eB45Ce7457c44",
451 | "chainId": 1,
452 | "name": "Panvala pan",
453 | "symbol": "PAN",
454 | "decimals": 18
455 | },
456 | {
457 | "address": "0x8E870D67F660D95d5be530380D0eC0bd388289E1",
458 | "chainId": 1,
459 | "name": "PAX",
460 | "symbol": "PAX",
461 | "decimals": 18
462 | },
463 | {
464 | "address": "0x45804880De22913dAFE09f4980848ECE6EcbAf78",
465 | "chainId": 1,
466 | "name": "Paxos Gold",
467 | "symbol": "PAXG",
468 | "decimals": 18
469 | },
470 | {
471 | "address": "0x93ED3FBe21207Ec2E8f2d3c3de6e058Cb73Bc04d",
472 | "chainId": 1,
473 | "name": "Pinakion",
474 | "symbol": "PNK",
475 | "decimals": 18
476 | },
477 | {
478 | "address": "0x6758B7d441a9739b98552B373703d8d3d14f9e62",
479 | "chainId": 1,
480 | "name": "POA ERC20 on Foundation",
481 | "symbol": "POA20",
482 | "decimals": 18
483 | },
484 | {
485 | "address": "0x687BfC3E73f6af55F0CccA8450114D107E781a0e",
486 | "chainId": 1,
487 | "name": "QChi",
488 | "symbol": "QCH",
489 | "decimals": 18
490 | },
491 | {
492 | "address": "0x4a220E6096B25EADb88358cb44068A3248254675",
493 | "chainId": 1,
494 | "name": "Quant",
495 | "symbol": "QNT",
496 | "decimals": 18
497 | },
498 | {
499 | "address": "0x99ea4dB9EE77ACD40B119BD1dC4E33e1C070b80d",
500 | "chainId": 1,
501 | "name": "Quantstamp Token",
502 | "symbol": "QSP",
503 | "decimals": 18
504 | },
505 | {
506 | "address": "0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6",
507 | "chainId": 1,
508 | "name": "Ripio Credit Network Token",
509 | "symbol": "RCN",
510 | "decimals": 18
511 | },
512 | {
513 | "address": "0x255Aa6DF07540Cb5d3d297f0D0D4D84cb52bc8e6",
514 | "chainId": 1,
515 | "name": "Raiden Token",
516 | "symbol": "RDN",
517 | "decimals": 18
518 | },
519 | {
520 | "address": "0x408e41876cCCDC0F92210600ef50372656052a38",
521 | "chainId": 1,
522 | "name": "Republic Token",
523 | "symbol": "REN",
524 | "decimals": 18
525 | },
526 | {
527 | "address": "0x459086F2376525BdCebA5bDDA135e4E9d3FeF5bf",
528 | "chainId": 1,
529 | "name": "renBCH",
530 | "symbol": "renBCH",
531 | "decimals": 8
532 | },
533 | {
534 | "address": "0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D",
535 | "chainId": 1,
536 | "name": "renBTC",
537 | "symbol": "renBTC",
538 | "decimals": 8
539 | },
540 | {
541 | "address": "0x1C5db575E2Ff833E46a2E9864C22F4B22E0B37C2",
542 | "chainId": 1,
543 | "name": "renZEC",
544 | "symbol": "renZEC",
545 | "decimals": 8
546 | },
547 | {
548 | "address": "0x1985365e9f78359a9B6AD760e32412f4a445E862",
549 | "chainId": 1,
550 | "name": "Reputation",
551 | "symbol": "REP",
552 | "decimals": 18
553 | },
554 | {
555 | "address": "0x9469D013805bFfB7D3DEBe5E7839237e535ec483",
556 | "chainId": 1,
557 | "name": "Darwinia Network Native Token",
558 | "symbol": "RING",
559 | "decimals": 18
560 | },
561 | {
562 | "address": "0x607F4C5BB672230e8672085532f7e901544a7375",
563 | "chainId": 1,
564 | "name": "iEx.ec Network Token",
565 | "symbol": "RLC",
566 | "decimals": 9
567 | },
568 | {
569 | "address": "0xB4EFd85c19999D84251304bDA99E90B92300Bd93",
570 | "chainId": 1,
571 | "name": "Rocket Pool",
572 | "symbol": "RPL",
573 | "decimals": 18
574 | },
575 | {
576 | "address": "0x4156D3342D5c385a87D264F90653733592000581",
577 | "chainId": 1,
578 | "name": "Salt",
579 | "symbol": "SALT",
580 | "decimals": 8
581 | },
582 | {
583 | "address": "0x7C5A0CE9267ED19B22F8cae653F198e3E8daf098",
584 | "chainId": 1,
585 | "name": "SANtiment network token",
586 | "symbol": "SAN",
587 | "decimals": 18
588 | },
589 | {
590 | "address": "0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb",
591 | "chainId": 1,
592 | "name": "Synth sETH",
593 | "symbol": "sETH",
594 | "decimals": 18
595 | },
596 | {
597 | "address": "0x3A9FfF453d50D4Ac52A6890647b823379ba36B9E",
598 | "chainId": 1,
599 | "name": "Shuffle.Monster V3",
600 | "symbol": "SHUF",
601 | "decimals": 18
602 | },
603 | {
604 | "address": "0x744d70FDBE2Ba4CF95131626614a1763DF805B9E",
605 | "chainId": 1,
606 | "name": "Status Network Token",
607 | "symbol": "SNT",
608 | "decimals": 18
609 | },
610 | {
611 | "address": "0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F",
612 | "chainId": 1,
613 | "name": "Synthetix Network Token",
614 | "symbol": "SNX",
615 | "decimals": 18
616 | },
617 | {
618 | "address": "0x23B608675a2B2fB1890d3ABBd85c5775c51691d5",
619 | "chainId": 1,
620 | "name": "Unisocks Edition 0",
621 | "symbol": "SOCKS",
622 | "decimals": 18
623 | },
624 | {
625 | "address": "0x42d6622deCe394b54999Fbd73D108123806f6a18",
626 | "chainId": 1,
627 | "name": "SPANK",
628 | "symbol": "SPANK",
629 | "decimals": 18
630 | },
631 | {
632 | "address": "0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC",
633 | "chainId": 1,
634 | "name": "StorjToken",
635 | "symbol": "STORJ",
636 | "decimals": 8
637 | },
638 | {
639 | "address": "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51",
640 | "chainId": 1,
641 | "name": "Synth sUSD",
642 | "symbol": "sUSD",
643 | "decimals": 18
644 | },
645 | {
646 | "address": "0x261EfCdD24CeA98652B9700800a13DfBca4103fF",
647 | "chainId": 1,
648 | "name": "Synth sXAU",
649 | "symbol": "sXAU",
650 | "decimals": 18
651 | },
652 | {
653 | "address": "0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9",
654 | "chainId": 1,
655 | "name": "Swipe",
656 | "symbol": "SXP",
657 | "decimals": 18
658 | },
659 | {
660 | "address": "0x00006100F7090010005F1bd7aE6122c3C2CF0090",
661 | "chainId": 1,
662 | "name": "TrueAUD",
663 | "symbol": "TAUD",
664 | "decimals": 18
665 | },
666 | {
667 | "address": "0x00000100F2A2bd000715001920eB70D229700085",
668 | "chainId": 1,
669 | "name": "TrueCAD",
670 | "symbol": "TCAD",
671 | "decimals": 18
672 | },
673 | {
674 | "address": "0x00000000441378008EA67F4284A57932B1c000a5",
675 | "chainId": 1,
676 | "name": "TrueGBP",
677 | "symbol": "TGBP",
678 | "decimals": 18
679 | },
680 | {
681 | "address": "0x0000852600CEB001E08e00bC008be620d60031F2",
682 | "chainId": 1,
683 | "name": "TrueHKD",
684 | "symbol": "THKD",
685 | "decimals": 18
686 | },
687 | {
688 | "address": "0xaAAf91D9b90dF800Df4F55c205fd6989c977E73a",
689 | "chainId": 1,
690 | "name": "Monolith TKN",
691 | "symbol": "TKN",
692 | "decimals": 8
693 | },
694 | {
695 | "address": "0x0Ba45A8b5d5575935B8158a88C631E9F9C95a2e5",
696 | "chainId": 1,
697 | "name": "Tellor Tributes",
698 | "symbol": "TRB",
699 | "decimals": 18
700 | },
701 | {
702 | "address": "0xCb94be6f13A1182E4A4B6140cb7bf2025d28e41B",
703 | "chainId": 1,
704 | "name": "Trustcoin",
705 | "symbol": "TRST",
706 | "decimals": 6
707 | },
708 | {
709 | "address": "0x2C537E5624e4af88A7ae4060C022609376C8D0EB",
710 | "chainId": 1,
711 | "name": "BiLira",
712 | "symbol": "TRYB",
713 | "decimals": 6
714 | },
715 | {
716 | "address": "0x0000000000085d4780B73119b644AE5ecd22b376",
717 | "chainId": 1,
718 | "name": "TrueUSD",
719 | "symbol": "TUSD",
720 | "decimals": 18
721 | },
722 | {
723 | "address": "0x8400D94A5cb0fa0D041a3788e395285d61c9ee5e",
724 | "chainId": 1,
725 | "name": "UniBright",
726 | "symbol": "UBT",
727 | "decimals": 8
728 | },
729 | {
730 | "address": "0x04Fa0d235C4abf4BcF4787aF4CF447DE572eF828",
731 | "chainId": 1,
732 | "name": "UMA Voting Token v1",
733 | "symbol": "UMA",
734 | "decimals": 18
735 | },
736 | {
737 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
738 | "chainId": 1,
739 | "name": "USD Coin",
740 | "symbol": "USDC",
741 | "decimals": 6
742 | },
743 | {
744 | "address": "0xA4Bdb11dc0a2bEC88d24A3aa1E6Bb17201112eBe",
745 | "chainId": 1,
746 | "name": "StableUSD",
747 | "symbol": "USDS",
748 | "decimals": 6
749 | },
750 | {
751 | "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
752 | "chainId": 1,
753 | "name": "Tether USD",
754 | "symbol": "USDT",
755 | "decimals": 6
756 | },
757 | {
758 | "address": "0xeb269732ab75A6fD61Ea60b06fE994cD32a83549",
759 | "chainId": 1,
760 | "name": "dForce",
761 | "symbol": "USDx",
762 | "decimals": 18
763 | },
764 | {
765 | "address": "0x8f3470A7388c05eE4e7AF3d01D8C722b0FF52374",
766 | "chainId": 1,
767 | "name": "Veritaseum",
768 | "symbol": "VERI",
769 | "decimals": 18
770 | },
771 | {
772 | "address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
773 | "chainId": 1,
774 | "name": "Wrapped BTC",
775 | "symbol": "WBTC",
776 | "decimals": 8
777 | },
778 | {
779 | "address": "0x09fE5f0236F0Ea5D930197DCE254d77B04128075",
780 | "chainId": 1,
781 | "name": "Wrapped CryptoKitties",
782 | "symbol": "WCK",
783 | "decimals": 18
784 | },
785 | {
786 | "address": "0xB4272071eCAdd69d933AdcD19cA99fe80664fc08",
787 | "chainId": 1,
788 | "name": "CryptoFranc",
789 | "symbol": "XCHF",
790 | "decimals": 18
791 | },
792 | {
793 | "address": "0x0f7F961648aE6Db43C75663aC7E5414Eb79b5704",
794 | "chainId": 1,
795 | "name": "XIO Network",
796 | "symbol": "XIO",
797 | "decimals": 18
798 | },
799 | {
800 | "address": "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
801 | "chainId": 1,
802 | "name": "0x Protocol Token",
803 | "symbol": "ZRX",
804 | "decimals": 18
805 | },
806 | {
807 | "address": "0xc778417E063141139Fce010982780140Aa0cD5Ab",
808 | "chainId": 3,
809 | "name": "Wrapped Ether",
810 | "symbol": "WETH",
811 | "decimals": 18
812 | },
813 | {
814 | "address": "0xaD6D458402F60fD3Bd25163575031ACDce07538D",
815 | "chainId": 3,
816 | "name": "Dai Stablecoin",
817 | "symbol": "DAI",
818 | "decimals": 18
819 | },
820 | {
821 | "address": "0xc778417E063141139Fce010982780140Aa0cD5Ab",
822 | "chainId": 4,
823 | "name": "Wrapped Ether",
824 | "symbol": "WETH",
825 | "decimals": 18
826 | },
827 | {
828 | "address": "0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735",
829 | "chainId": 4,
830 | "name": "Dai Stablecoin",
831 | "symbol": "DAI",
832 | "decimals": 18
833 | },
834 | {
835 | "address": "0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85",
836 | "chainId": 4,
837 | "name": "Maker",
838 | "symbol": "MKR",
839 | "decimals": 18
840 | },
841 | {
842 | "address": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
843 | "chainId": 5,
844 | "name": "Wrapped Ether",
845 | "symbol": "WETH",
846 | "decimals": 18
847 | },
848 | {
849 | "address": "0xd0A1E359811322d97991E03f863a0C30C2cF029C",
850 | "chainId": 42,
851 | "name": "Wrapped Ether",
852 | "symbol": "WETH",
853 | "decimals": 18
854 | },
855 | {
856 | "address": "0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa",
857 | "chainId": 42,
858 | "name": "Dai Stablecoin",
859 | "symbol": "DAI",
860 | "decimals": 18
861 | },
862 | {
863 | "address": "0xAaF64BFCC32d0F15873a02163e7E500671a4ffcD",
864 | "chainId": 42,
865 | "name": "Maker",
866 | "symbol": "MKR",
867 | "decimals": 18
868 | }
869 | ]
870 | }
--------------------------------------------------------------------------------
/test/schema/bigwords.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | {
15 | "name": "blah blah blah blah blah blah blah blah blah",
16 | "address": "0x0000000000000000000000000000000000000000",
17 | "chainId": 1,
18 | "decimals": 18,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": 0,
28 | "minor": 0,
29 | "patch": 1
30 | }
31 | }
--------------------------------------------------------------------------------
/test/schema/empty.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | ],
15 | "version": {
16 | "major": 0,
17 | "minor": 0,
18 | "patch": 1
19 | }
20 | }
--------------------------------------------------------------------------------
/test/schema/example.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Token List",
3 | "keywords": [
4 | "audited",
5 | "verified",
6 | "special tokens"
7 | ],
8 | "tags": {
9 | "stablecoin": {
10 | "name": "Stablecoin",
11 | "description": "Tokens that are fixed to an external asset"
12 | },
13 | "compound": {
14 | "name": "Compound Finance",
15 | "description": "Tokens that earn interest on compound.finance"
16 | }
17 | },
18 | "timestamp": "2020-06-12T00:00:00+00:00",
19 | "tokens": [
20 | {
21 | "chainId": 1,
22 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
23 | "symbol": "USDC",
24 | "name": "USD Coin",
25 | "decimals": 6,
26 | "logoURI": "ipfs://QmXfzKRvjZz3u5JRgC4v5mGVbm9ahrUiB4DgzHBsnWbTMM",
27 | "tags": [
28 | "stablecoin"
29 | ]
30 | },
31 | {
32 | "chainId": 1,
33 | "address": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
34 | "symbol": "cUSDC",
35 | "name": "Compound USD Coin",
36 | "decimals": 8,
37 | "logoURI": "ipfs://QmUSNbwUxUYNMvMksKypkgWs8unSm8dX2GjCPBVGZ7GGMr",
38 | "tags": [
39 | "compound"
40 | ]
41 | }
42 | ],
43 | "logoURI": "ipfs://QmUSNbwUxUYNMvMksKypkgWs8unSm8dX2GjCPBVGZ7GGMr",
44 | "version": {
45 | "major": 1,
46 | "minor": 0,
47 | "patch": 0
48 | }
49 | }
--------------------------------------------------------------------------------
/test/schema/exampleminimum.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Token List",
3 | "timestamp": "2020-06-12T00:00:00+00:00",
4 | "tokens": [
5 | {
6 | "chainId": 1,
7 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
8 | "decimals": 18,
9 | "name": "required name",
10 | "symbol": "symbol"
11 | },
12 | {
13 | "chainId": 1,
14 | "address": "0x39AA39c021dfbaE8faC545936693aC917d5E7563",
15 | "decimals": 18,
16 | "name": "required name",
17 | "symbol": "symbol"
18 | }
19 | ],
20 | "version": {
21 | "major": 1,
22 | "minor": 0,
23 | "patch": 0
24 | }
25 | }
--------------------------------------------------------------------------------
/test/schema/invaliddecimals.1.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | {
15 | "name": "name",
16 | "address": "0x0000000000000000000000000000000000000000",
17 | "chainId": 1,
18 | "decimals": 256,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": 0,
28 | "minor": 0,
29 | "patch": 1
30 | }
31 | }
--------------------------------------------------------------------------------
/test/schema/invaliddecimals.2.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | {
15 | "name": "name",
16 | "address": "0x0000000000000000000000000000000000000000",
17 | "chainId": 1,
18 | "decimals": -1,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": 0,
28 | "minor": 0,
29 | "patch": 1
30 | }
31 | }
--------------------------------------------------------------------------------
/test/schema/invalidlogouri.1.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Token List",
3 | "keywords": [
4 | ],
5 | "timestamp": "2020-06-12T00:00:00+00:00",
6 | "tokens": [
7 | {
8 | "chainId": 1,
9 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
10 | "symbol": "USDC",
11 | "name": "USD Coin",
12 | "decimals": 6,
13 | "logoURI": "hello",
14 | "tags": [
15 | "stablecoin"
16 | ]
17 | }
18 | ],
19 | "version": {
20 | "major": 0,
21 | "minor": 0,
22 | "patch": 1
23 | }
24 | }
--------------------------------------------------------------------------------
/test/schema/invalidlogouri.2.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Token List",
3 | "keywords": [
4 | ],
5 | "timestamp": "2020-06-12T00:00:00+00:00",
6 | "tokens": [
7 | {
8 | "chainId": 1,
9 | "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
10 | "symbol": "USDC",
11 | "name": "USD Coin",
12 | "decimals": 6,
13 | "logoURI": "ipfs://hello-world",
14 | "tags": [
15 | "stablecoin"
16 | ]
17 | }
18 | ],
19 | "logoURI": "hello-world",
20 | "version": {
21 | "major": 0,
22 | "minor": 0,
23 | "patch": 1
24 | }
25 | }
--------------------------------------------------------------------------------
/test/schema/invalidtimestamp.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "coin": {
8 | "name": "Stablecoin",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "abc",
13 | "tokens": [
14 | {
15 | "name": "blah",
16 | "address": "0x0000000000000000000000000000000000000000",
17 | "chainId": 1,
18 | "decimals": 18,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": 0,
28 | "minor": 0,
29 | "patch": 1
30 | }
31 | }
--------------------------------------------------------------------------------
/test/schema/invalidtokenaddress.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | {
15 | "name": "blah blah ",
16 | "address": "0x00000000000000000000000000000000000000_0",
17 | "chainId": 1,
18 | "decimals": 18,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": 0,
28 | "minor": 0,
29 | "patch": 1
30 | }
31 | }
--------------------------------------------------------------------------------
/test/schema/invalidversion.1.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | {
15 | "name": "blah blah ",
16 | "address": "0x0000000000000000000000000000000000000000",
17 | "chainId": 1,
18 | "decimals": 18,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": 0,
28 | "minor": 0,
29 | "patch": -1
30 | }
31 | }
--------------------------------------------------------------------------------
/test/schema/invalidversion.2.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | {
15 | "name": "blah blah ",
16 | "address": "0x0000000000000000000000000000000000000000",
17 | "chainId": 1,
18 | "decimals": 18,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": 0,
28 | "minor": -1,
29 | "patch": 0
30 | }
31 | }
--------------------------------------------------------------------------------
/test/schema/invalidversion.3.tokenlist.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "My Example List",
3 | "keywords": [
4 | "abc"
5 | ],
6 | "tags": {
7 | "1": {
8 | "name": "tag 1",
9 | "description": "blah blah"
10 | }
11 | },
12 | "timestamp": "2018-11-13T20:20:39+00:00",
13 | "tokens": [
14 | {
15 | "name": "blah blah ",
16 | "address": "0x0000000000000000000000000000000000000000",
17 | "chainId": 1,
18 | "decimals": 18,
19 | "logoURI": "https://test",
20 | "symbol": "abc",
21 | "tags": [
22 | "coin"
23 | ]
24 | }
25 | ],
26 | "version": {
27 | "major": -1,
28 | "minor": 0,
29 | "patch": 0
30 | }
31 | }
--------------------------------------------------------------------------------
/test/tokenlist.schema.test.ts:
--------------------------------------------------------------------------------
1 | import Ajv from 'ajv';
2 | import { schema } from '../src';
3 | import exampleList from './schema/example.tokenlist.json';
4 | import bigExampleList from './schema/bigexample.tokenlist.json';
5 | import exampleListMinimum from './schema/exampleminimum.tokenlist.json';
6 | import emptyList from './schema/empty.tokenlist.json';
7 | import bigWords from './schema/bigwords.tokenlist.json';
8 | import invalidTokenAddress from './schema/invalidtokenaddress.tokenlist.json';
9 | import invalidTimestamp from './schema/invalidtimestamp.tokenlist.json';
10 | import invalidLogoURI1 from './schema/invalidlogouri.1.tokenlist.json';
11 | import invalidLogoURI2 from './schema/invalidlogouri.2.tokenlist.json';
12 | import invalidVersion1 from './schema/invalidversion.1.tokenlist.json';
13 | import invalidVersion2 from './schema/invalidversion.2.tokenlist.json';
14 | import invalidVersion3 from './schema/invalidversion.3.tokenlist.json';
15 | import invalidDecimals1 from './schema/invaliddecimals.1.tokenlist.json';
16 | import invalidDecimals2 from './schema/invaliddecimals.2.tokenlist.json';
17 |
18 | const ajv = new Ajv({ allErrors: true, format: 'full' });
19 | const validator = ajv.compile(schema);
20 |
21 | describe('schema', () => {
22 | it('is valid', () => {
23 | expect(ajv.validateSchema(schema)).toEqual(true);
24 | });
25 |
26 | function checkSchema(schema: any, valid: boolean): void {
27 | expect(validator(schema)).toEqual(valid);
28 | expect(validator.errors).toMatchSnapshot();
29 | }
30 |
31 | it('works for example schema', () => {
32 | checkSchema(exampleList, true);
33 | });
34 |
35 | it('works for big example schema', () => {
36 | checkSchema(bigExampleList, true);
37 | });
38 |
39 | it('minimum example schema', () => {
40 | checkSchema(exampleListMinimum, true);
41 | });
42 |
43 | it('requires name, timestamp, version, tokens', () => {
44 | checkSchema({}, false);
45 | });
46 |
47 | it('empty list fails', () => {
48 | checkSchema(emptyList, false);
49 | });
50 |
51 | it('fails with big names', () => {
52 | checkSchema(bigWords, false);
53 | });
54 |
55 | it('checks token address', () => {
56 | checkSchema(invalidTokenAddress, false);
57 | });
58 |
59 | it('invalid timestamp', () => {
60 | checkSchema(invalidTimestamp, false);
61 | });
62 |
63 | it('invalid logo URI', () => {
64 | checkSchema(invalidLogoURI1, false);
65 | checkSchema(invalidLogoURI2, false);
66 | });
67 |
68 | it('invalid decimals', () => {
69 | checkSchema(invalidDecimals1, false);
70 | checkSchema(invalidDecimals2, false);
71 | });
72 |
73 | it('checks version', () => {
74 | checkSchema(invalidVersion1, false);
75 | checkSchema(invalidVersion2, false);
76 | checkSchema(invalidVersion3, false);
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/test/types.test.ts:
--------------------------------------------------------------------------------
1 | import { TokenList } from '../src';
2 | import exampleList from './schema/example.tokenlist.json';
3 |
4 | describe('types', () => {
5 | it('matches example schema', () => {
6 | // this is enough--typescript won't cast it unless it matches the interface
7 | const list: TokenList = exampleList;
8 |
9 | expect(list.name).toEqual('My Token List');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "types"],
3 | "compilerOptions": {
4 | "module": "esnext",
5 | "lib": ["dom", "esnext"],
6 | "importHelpers": true,
7 | "declaration": true,
8 | "sourceMap": true,
9 | "rootDir": "./src",
10 | "strict": true,
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "noImplicitReturns": true,
14 | "noFallthroughCasesInSwitch": true,
15 | "resolveJsonModule": true,
16 | "moduleResolution": "node",
17 | "baseUrl": "./",
18 | "paths": {
19 | "*": ["src/*", "node_modules/*"]
20 | },
21 | "jsx": "react",
22 | "esModuleInterop": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------