├── .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 | [![Tests](https://github.com/Uniswap/token-lists/workflows/Tests/badge.svg)](https://github.com/Uniswap/token-lists/actions?query=workflow%3ATests) 4 | [![npm](https://img.shields.io/npm/v/@uniswap/token-lists)](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 | --------------------------------------------------------------------------------