@eth-optimism/tokenlist 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +1 -0
- package/.env.example +2 -0
- package/.eslintrc.js +188 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/mergify.yml +57 -0
- package/.github/workflows/dependabot.yml +13 -0
- package/.github/workflows/generate.yml +57 -0
- package/.github/workflows/publish.yml +50 -0
- package/.github/workflows/stale.yml +19 -0
- package/.github/workflows/test.yml +43 -0
- package/.github/workflows/validate.yml +67 -0
- package/.prettierrc.js +8 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/settings.json +10 -0
- package/CNAME +1 -0
- package/Dockerfile.bot +14 -0
- package/README.md +162 -0
- package/babel.config.js +6 -0
- package/bin/cli.ts +61 -0
- package/data/0xBTC/data.json +19 -0
- package/data/0xBTC/logo.png +0 -0
- package/data/AAVE/data.json +19 -0
- package/data/AAVE/logo.svg +1 -0
- package/data/ACX/data.json +15 -0
- package/data/ACX/logo.png +0 -0
- package/data/AELIN/data.json +11 -0
- package/data/AELIN/logo.png +0 -0
- package/data/BAL/data.json +19 -0
- package/data/BAL/logo.png +0 -0
- package/data/BANK/data.json +15 -0
- package/data/BANK/logo.png +0 -0
- package/data/BAXA/data.json +22 -0
- package/data/BAXA/logo.svg +3 -0
- package/data/BOB/data.json +16 -0
- package/data/BOB/logo.svg +11 -0
- package/data/BOND/data.json +19 -0
- package/data/BOND/logo.svg +10 -0
- package/data/BitANT/data.json +19 -0
- package/data/BitANT/logo.png +0 -0
- package/data/BitBTC/data.json +31 -0
- package/data/BitBTC/logo.png +0 -0
- package/data/CRV/data.json +13 -0
- package/data/CRV/logo.png +0 -0
- package/data/CTSI/data.json +19 -0
- package/data/CTSI/logo.svg +23 -0
- package/data/DAI/data.json +43 -0
- package/data/DAI/logo.svg +13 -0
- package/data/DCN/data.json +25 -0
- package/data/DCN/logo.svg +47 -0
- package/data/DF/data.json +20 -0
- package/data/DF/logo.svg +15 -0
- package/data/DHT/data.json +13 -0
- package/data/DHT/logo.svg +1 -0
- package/data/DOLA/data.json +19 -0
- package/data/DOLA/logo.png +0 -0
- package/data/DUCK/data.json +13 -0
- package/data/DUCK/logo.png +0 -0
- package/data/ENS/data.json +13 -0
- package/data/ENS/logo.png +0 -0
- package/data/EQZ/data.json +19 -0
- package/data/EQZ/logo.png +0 -0
- package/data/EST/data.json +13 -0
- package/data/EST/logo.png +0 -0
- package/data/ETH/data.json +25 -0
- package/data/ETH/logo.svg +13 -0
- package/data/EURT/data.json +13 -0
- package/data/EURT/logo.png +0 -0
- package/data/FLASH/data.json +16 -0
- package/data/FLASH/logo.svg +22 -0
- package/data/GTC/data.json +19 -0
- package/data/GTC/logo.svg +5 -0
- package/data/GYSR/data.json +19 -0
- package/data/GYSR/logo.png +0 -0
- package/data/HAN/data.json +22 -0
- package/data/HAN/logo.svg +25 -0
- package/data/IBEX/data.json +16 -0
- package/data/IBEX/logo.png +0 -0
- package/data/INSURE/data.json +16 -0
- package/data/INSURE/logo.png +0 -0
- package/data/KNC/data.json +19 -0
- package/data/KNC/logo.png +0 -0
- package/data/KROM/data.json +19 -0
- package/data/KROM/logo.png +0 -0
- package/data/LAND/data.json +13 -0
- package/data/LAND/logo.png +0 -0
- package/data/LDO/data.json +30 -0
- package/data/LDO/logo.svg +5 -0
- package/data/LINK/data.json +25 -0
- package/data/LINK/logo.png +0 -0
- package/data/LIZ/data.json +19 -0
- package/data/LIZ/logo.png +0 -0
- package/data/LPF/data.json +15 -0
- package/data/LPF/logo.svg +18 -0
- package/data/LRC/data.json +13 -0
- package/data/LRC/logo.png +0 -0
- package/data/LUSD/data.json +13 -0
- package/data/LUSD/logo.svg +1 -0
- package/data/LYRA/data.json +13 -0
- package/data/LYRA/logo.png +0 -0
- package/data/MASK/data.json +13 -0
- package/data/MASK/logo.svg +4 -0
- package/data/MKR/data.json +14 -0
- package/data/MKR/logo.png +0 -0
- package/data/MOCHI/data.json +16 -0
- package/data/MOCHI/logo.svg +36 -0
- package/data/MOM/data.json +19 -0
- package/data/MOM/logo.svg +5 -0
- package/data/OP/data.json +14 -0
- package/data/OP/logo.png +0 -0
- package/data/OUTb/data.json +13 -0
- package/data/OUTb/logo.svg +4 -0
- package/data/PAPER/data.json +13 -0
- package/data/PAPER/logo.svg +4 -0
- package/data/PERP/data.json +25 -0
- package/data/PERP/logo.png +0 -0
- package/data/PHTK/data.json +16 -0
- package/data/PHTK/logo.svg +14 -0
- package/data/POOL/data.json +13 -0
- package/data/POOL/logo.svg +22 -0
- package/data/POP/data.json +16 -0
- package/data/POP/logo.svg +8 -0
- package/data/PREMIA/data.json +13 -0
- package/data/PREMIA/logo.svg +4 -0
- package/data/RAI/data.json +19 -0
- package/data/RAI/logo.svg +273 -0
- package/data/RFWSTETH/data.json +21 -0
- package/data/RFWSTETH/logo.svg +36 -0
- package/data/RGT/data.json +13 -0
- package/data/RGT/logo.png +0 -0
- package/data/SARCO/data.json +13 -0
- package/data/SARCO/logo.png +0 -0
- package/data/SDL/data.json +22 -0
- package/data/SDL/logo.svg +5 -0
- package/data/SNX/data.json +43 -0
- package/data/SNX/logo.svg +20 -0
- package/data/SPANK/data.json +19 -0
- package/data/SPANK/logo.png +0 -0
- package/data/SUKU/data.json +22 -0
- package/data/SUKU/logo.png +0 -0
- package/data/THALES/data.json +13 -0
- package/data/THALES/logo.png +0 -0
- package/data/TRB/data.json +22 -0
- package/data/TRB/logo.png +0 -0
- package/data/TUSD/data.json +19 -0
- package/data/TUSD/logo.png +0 -0
- package/data/TheDAO/data.json +32 -0
- package/data/TheDAO/logo.svg +1 -0
- package/data/UBI/data.json +22 -0
- package/data/UBI/logo.svg +5 -0
- package/data/UMA/data.json +13 -0
- package/data/UMA/logo.png +0 -0
- package/data/UNI/data.json +19 -0
- package/data/UNI/logo.png +0 -0
- package/data/USDC/data.json +28 -0
- package/data/USDC/logo.png +0 -0
- package/data/USDD/data.json +19 -0
- package/data/USDD/logo.png +0 -0
- package/data/USDT/data.json +28 -0
- package/data/USDT/logo.png +0 -0
- package/data/USDplus/data.json +14 -0
- package/data/USDplus/logo.svg +9 -0
- package/data/UST/data.json +13 -0
- package/data/UST/logo.png +0 -0
- package/data/USX/data.json +31 -0
- package/data/USX/logo.svg +14 -0
- package/data/VALX/data.json +15 -0
- package/data/VALX/logo.png +0 -0
- package/data/VELO/data.json +10 -0
- package/data/VELO/logo.svg +71 -0
- package/data/WAD/data.json +19 -0
- package/data/WAD/logo.png +0 -0
- package/data/WBTC/data.json +30 -0
- package/data/WBTC/logo.svg +20 -0
- package/data/WETH/data.json +11 -0
- package/data/WETH/logo.png +0 -0
- package/data/WMP/data.json +15 -0
- package/data/WMP/logo.svg +52 -0
- package/data/WOO/data.json +16 -0
- package/data/WOO/logo.svg +1489 -0
- package/data/XCHF/data.json +13 -0
- package/data/XCHF/logo.png +0 -0
- package/data/ZRX/data.json +13 -0
- package/data/ZRX/logo.png +0 -0
- package/data/aCRV/data.json +22 -0
- package/data/aCRV/logo.svg +9 -0
- package/data/alETH/data.json +16 -0
- package/data/alETH/logo.svg +17 -0
- package/data/rETH/data.json +13 -0
- package/data/rETH/logo.png +0 -0
- package/data/sBTC/data.json +11 -0
- package/data/sBTC/logo.svg +18 -0
- package/data/sETH/data.json +11 -0
- package/data/sETH/logo.svg +23 -0
- package/data/sLINK/data.json +11 -0
- package/data/sLINK/logo.svg +23 -0
- package/data/sUSD/data.json +17 -0
- package/data/sUSD/logo.svg +17 -0
- package/data/veKWENTA/data.json +22 -0
- package/data/veKWENTA/logo.svg +24 -0
- package/data/wstETH/data.json +31 -0
- package/data/wstETH/logo.svg +11 -0
- package/dist/declarations/src/index.d.ts +79 -0
- package/dist/eth-optimism-tokenlist.cjs.d.ts +1 -0
- package/dist/eth-optimism-tokenlist.cjs.dev.js +3098 -0
- package/dist/eth-optimism-tokenlist.cjs.js +7 -0
- package/dist/eth-optimism-tokenlist.cjs.prod.js +3098 -0
- package/dist/eth-optimism-tokenlist.esm.js +3090 -0
- package/jest.config.js +5 -0
- package/optimism.defaulttokens.json +26 -0
- package/optimism.svg +4 -0
- package/optimism.tokenlist.json +3086 -0
- package/package.json +80 -0
- package/src/abi.ts +77 -0
- package/src/bot.ts +236 -0
- package/src/chains.ts +58 -0
- package/src/generate.ts +69 -0
- package/src/index.ts +71 -0
- package/src/schemas.ts +174 -0
- package/src/types.ts +33 -0
- package/src/validate.ts +271 -0
- package/tests/__snapshots__/generate.test.ts.snap +87 -0
- package/tests/data/ETH/data.json +21 -0
- package/tests/data/ETH/logo.svg +13 -0
- package/tests/data/OP/data.json +16 -0
- package/tests/data/OP/logo.png +0 -0
- package/tests/defaultTokens.test.ts +12 -0
- package/tests/generate.test.ts +20 -0
- package/tsconfig.json +27 -0
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
export const ADDRESS_TYPE = {
|
|
2
|
+
type: 'string',
|
|
3
|
+
minLength: 42,
|
|
4
|
+
maxLength: 42,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const TOKEN_SCHEMA = {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
address: ADDRESS_TYPE,
|
|
11
|
+
overrides: {
|
|
12
|
+
bridge: ADDRESS_TYPE,
|
|
13
|
+
name: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
},
|
|
16
|
+
symbol: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
},
|
|
19
|
+
decimals: {
|
|
20
|
+
type: 'integer',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
required: ['address'],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const TOKEN_DATA_SCHEMA = {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
nonstandard: {
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
},
|
|
34
|
+
nobridge: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
},
|
|
37
|
+
name: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
},
|
|
40
|
+
symbol: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
},
|
|
43
|
+
decimals: {
|
|
44
|
+
type: 'integer',
|
|
45
|
+
},
|
|
46
|
+
description: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
minLength: 1,
|
|
49
|
+
maxLength: 150,
|
|
50
|
+
},
|
|
51
|
+
website: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
format: 'uri',
|
|
54
|
+
},
|
|
55
|
+
twitter: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
},
|
|
58
|
+
tokens: {
|
|
59
|
+
oneOf: [
|
|
60
|
+
{
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
ethereum: TOKEN_SCHEMA,
|
|
64
|
+
optimism: TOKEN_SCHEMA,
|
|
65
|
+
kovan: TOKEN_SCHEMA,
|
|
66
|
+
'optimism-kovan': TOKEN_SCHEMA,
|
|
67
|
+
},
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
required: ['ethereum', 'optimism', 'kovan', 'optimism-kovan'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
ethereum: TOKEN_SCHEMA,
|
|
75
|
+
optimism: TOKEN_SCHEMA,
|
|
76
|
+
goerli: TOKEN_SCHEMA,
|
|
77
|
+
'optimism-goerli': TOKEN_SCHEMA,
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
required: ['ethereum', 'optimism', 'goerli', 'optimism-goerli'],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
ethereum: TOKEN_SCHEMA,
|
|
86
|
+
optimism: TOKEN_SCHEMA,
|
|
87
|
+
kovan: TOKEN_SCHEMA,
|
|
88
|
+
'optimism-kovan': TOKEN_SCHEMA,
|
|
89
|
+
goerli: TOKEN_SCHEMA,
|
|
90
|
+
'optimism-goerli': TOKEN_SCHEMA,
|
|
91
|
+
},
|
|
92
|
+
additionalProperties: false,
|
|
93
|
+
required: [
|
|
94
|
+
'ethereum',
|
|
95
|
+
'optimism',
|
|
96
|
+
'kovan',
|
|
97
|
+
'optimism-kovan',
|
|
98
|
+
'goerli',
|
|
99
|
+
'optimism-goerli',
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
optimism: TOKEN_SCHEMA,
|
|
106
|
+
'optimism-kovan': TOKEN_SCHEMA,
|
|
107
|
+
},
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
required: ['optimism', 'optimism-kovan'],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
optimism: TOKEN_SCHEMA,
|
|
115
|
+
'optimism-goerli': TOKEN_SCHEMA,
|
|
116
|
+
},
|
|
117
|
+
additionalProperties: false,
|
|
118
|
+
required: ['optimism', 'optimism-goerli'],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
optimism: TOKEN_SCHEMA,
|
|
124
|
+
'optimism-kovan': TOKEN_SCHEMA,
|
|
125
|
+
'optimism-goerli': TOKEN_SCHEMA,
|
|
126
|
+
},
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
required: ['optimism', 'optimism-kovan', 'optimism-goerli'],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
ethereum: TOKEN_SCHEMA,
|
|
134
|
+
optimism: TOKEN_SCHEMA,
|
|
135
|
+
},
|
|
136
|
+
additionalProperties: false,
|
|
137
|
+
required: ['ethereum', 'optimism'],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
kovan: TOKEN_SCHEMA,
|
|
143
|
+
'optimism-kovan': TOKEN_SCHEMA,
|
|
144
|
+
},
|
|
145
|
+
additionalProperties: false,
|
|
146
|
+
required: ['kovan', 'optimism-kovan'],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
goerli: TOKEN_SCHEMA,
|
|
152
|
+
'optimism-goerli': TOKEN_SCHEMA,
|
|
153
|
+
},
|
|
154
|
+
additionalProperties: false,
|
|
155
|
+
required: ['goerli', 'optimism-goerli'],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: {
|
|
160
|
+
optimism: TOKEN_SCHEMA,
|
|
161
|
+
},
|
|
162
|
+
additionalProperties: false,
|
|
163
|
+
required: ['optimism'],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
required: ['name', 'symbol', 'decimals', 'tokens', 'description', 'website'],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
TOKEN_DATA_SCHEMA,
|
|
174
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface Token {
|
|
2
|
+
address: string
|
|
3
|
+
overrides?: {
|
|
4
|
+
bridge?: string
|
|
5
|
+
name?: string
|
|
6
|
+
symbol?: string
|
|
7
|
+
decimals?: number
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TokenData {
|
|
12
|
+
nonstandard?: boolean
|
|
13
|
+
nobridge?: boolean
|
|
14
|
+
twitter?: string
|
|
15
|
+
name: string
|
|
16
|
+
symbol: string
|
|
17
|
+
decimals: number
|
|
18
|
+
description: string
|
|
19
|
+
website: string
|
|
20
|
+
tokens: {
|
|
21
|
+
ethereum?: Token
|
|
22
|
+
optimism?: Token
|
|
23
|
+
kovan?: Token
|
|
24
|
+
'optimism-kovan'?: Token
|
|
25
|
+
goerli?: Token
|
|
26
|
+
'optimism-goerli'?: Token
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ValidationResult {
|
|
31
|
+
type: 'error' | 'warning'
|
|
32
|
+
message: string
|
|
33
|
+
}
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { URLSearchParams } from 'url'
|
|
4
|
+
|
|
5
|
+
import { glob } from 'glob'
|
|
6
|
+
import fetch from 'node-fetch'
|
|
7
|
+
import { Validator } from 'jsonschema'
|
|
8
|
+
import Ajv from 'ajv'
|
|
9
|
+
import addFormats from 'ajv-formats'
|
|
10
|
+
import { schema } from '@uniswap/token-lists'
|
|
11
|
+
import { ethers } from 'ethers'
|
|
12
|
+
import { sleep } from '@eth-optimism/core-utils'
|
|
13
|
+
|
|
14
|
+
import { generate } from './generate'
|
|
15
|
+
import { TOKEN_DATA_SCHEMA } from './schemas'
|
|
16
|
+
import { NETWORK_DATA } from './chains'
|
|
17
|
+
import { FACTORY_ABI, TOKEN_ABI } from './abi'
|
|
18
|
+
import { TokenData, ValidationResult } from './types'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validates a token list data folder.
|
|
22
|
+
*
|
|
23
|
+
* @param datadir Directory containing data files.
|
|
24
|
+
* @param tokens List of tokens to run validation on.
|
|
25
|
+
*
|
|
26
|
+
* @return Validation results.
|
|
27
|
+
*/
|
|
28
|
+
export const validate = async (
|
|
29
|
+
datadir: string,
|
|
30
|
+
tokens: string[]
|
|
31
|
+
): Promise<ValidationResult[]> => {
|
|
32
|
+
// Load data files to validate and filter for requested tokens
|
|
33
|
+
console.log(tokens)
|
|
34
|
+
const folders = fs
|
|
35
|
+
.readdirSync(datadir)
|
|
36
|
+
.sort((a, b) => {
|
|
37
|
+
return a.toLowerCase().localeCompare(b.toLowerCase())
|
|
38
|
+
})
|
|
39
|
+
.filter((folder) => {
|
|
40
|
+
return !tokens || tokens.includes(folder)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// Load the CoinGecko tokenlist once to avoid additional requests
|
|
44
|
+
const cgret = await fetch('https://tokens.coingecko.com/uniswap/all.json')
|
|
45
|
+
const cg = await cgret.json()
|
|
46
|
+
|
|
47
|
+
const results = []
|
|
48
|
+
for (const folder of folders) {
|
|
49
|
+
// Make sure the data file exists
|
|
50
|
+
const datafile = path.join(datadir, folder, 'data.json')
|
|
51
|
+
if (!fs.existsSync(datafile)) {
|
|
52
|
+
results.push({
|
|
53
|
+
type: 'error',
|
|
54
|
+
message: `data file ${datafile} does not exist`,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Load the data now that we know it exists
|
|
59
|
+
const data: TokenData = JSON.parse(fs.readFileSync(datafile, 'utf8'))
|
|
60
|
+
|
|
61
|
+
// Make sure ONE logo file exists
|
|
62
|
+
const logofiles = glob.sync(`${path.join(datadir, folder)}/logo.{png,svg}`)
|
|
63
|
+
if (logofiles.length !== 1) {
|
|
64
|
+
results.push({
|
|
65
|
+
type: 'error',
|
|
66
|
+
message: `${folder} has ${logofiles.length} logo files, make sure your logo is either logo.png OR logo.svg`,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate the data file
|
|
71
|
+
const v = new Validator()
|
|
72
|
+
const result = v.validate(data, TOKEN_DATA_SCHEMA as any)
|
|
73
|
+
if (!result.valid) {
|
|
74
|
+
for (const error of result.errors) {
|
|
75
|
+
results.push({
|
|
76
|
+
type: 'error',
|
|
77
|
+
message: `${folder}: ${error.property}: ${error.message}`,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Since the data file is invalid, we can't continue validating the rest of the files
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Validate each token configuration
|
|
86
|
+
for (const [chain, token] of Object.entries(data.tokens)) {
|
|
87
|
+
// Check to make sure the website will load
|
|
88
|
+
for (let i = 0; i < 5; i++) {
|
|
89
|
+
try {
|
|
90
|
+
await fetch(data.website)
|
|
91
|
+
break
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (i < 4) {
|
|
94
|
+
await sleep(5000)
|
|
95
|
+
} else {
|
|
96
|
+
results.push({
|
|
97
|
+
type: 'error',
|
|
98
|
+
message: `${folder} on chain ${chain} website did not load`,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Validate any standard tokens
|
|
105
|
+
if (folder !== 'ETH' && data.nonstandard !== true) {
|
|
106
|
+
const contract = new ethers.Contract(
|
|
107
|
+
token.address,
|
|
108
|
+
TOKEN_ABI,
|
|
109
|
+
NETWORK_DATA[chain].provider
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Check that the token exists on this chain
|
|
113
|
+
if ((await contract.provider.getCode(token.address)) === '0x') {
|
|
114
|
+
results.push({
|
|
115
|
+
type: 'error',
|
|
116
|
+
message: `${folder} on chain ${chain} token ${token.address} does not exist`,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check that the token has the correct decimals
|
|
121
|
+
if (token.overrides?.decimals === undefined) {
|
|
122
|
+
try {
|
|
123
|
+
if (data.decimals !== (await contract.decimals())) {
|
|
124
|
+
results.push({
|
|
125
|
+
type: 'error',
|
|
126
|
+
message: `${folder} on chain ${chain} token ${token.address} has incorrect decimals`,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
results.push({
|
|
131
|
+
type: 'error',
|
|
132
|
+
message: `${folder} on chain ${chain} token ${token.address} failed to get decimals`,
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
results.push({
|
|
137
|
+
type: 'warning',
|
|
138
|
+
message: `${folder} on chain ${chain} token ${token.address} has overridden decimals`,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check that the token has the correct symbol
|
|
143
|
+
if (token.overrides?.symbol === undefined) {
|
|
144
|
+
try {
|
|
145
|
+
if (data.symbol !== (await contract.symbol())) {
|
|
146
|
+
results.push({
|
|
147
|
+
type: 'error',
|
|
148
|
+
message: `${folder} on chain ${chain} token ${token.address} has incorrect symbol`,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
results.push({
|
|
153
|
+
type: 'error',
|
|
154
|
+
message: `${folder} on chain ${chain} token ${token.address} failed to get symbol`,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
results.push({
|
|
159
|
+
type: 'warning',
|
|
160
|
+
message: `${folder} on chain ${chain} token ${token.address} has overridden symbol`,
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check that the token has the correct name
|
|
165
|
+
if (token.overrides?.name === undefined) {
|
|
166
|
+
try {
|
|
167
|
+
if (data.name !== (await contract.name())) {
|
|
168
|
+
results.push({
|
|
169
|
+
type: 'error',
|
|
170
|
+
message: `${folder} on chain ${chain} token ${token.address} has incorrect name`,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
results.push({
|
|
175
|
+
type: 'error',
|
|
176
|
+
message: `${folder} on chain ${chain} token ${token.address} failed to get name`,
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
results.push({
|
|
181
|
+
type: 'warning',
|
|
182
|
+
message: `${folder} on chain ${chain} token ${token.address} has overridden name`,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check that the Ethereum token exists in the CG token list
|
|
187
|
+
if (chain === 'ethereum') {
|
|
188
|
+
const found = cg.tokens.find((t) => {
|
|
189
|
+
return t.address.toLowerCase() === token.address.toLowerCase()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Trigger manual review if the Ethereum token is not in the CG token list
|
|
193
|
+
if (!found) {
|
|
194
|
+
results.push({
|
|
195
|
+
type: 'warning',
|
|
196
|
+
message: `${folder} on chain ${chain} token ${token.address} not found on CoinGecko token list`,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (chain.startsWith('optimism')) {
|
|
202
|
+
const factory = new ethers.Contract(
|
|
203
|
+
'0x4200000000000000000000000000000000000012',
|
|
204
|
+
FACTORY_ABI,
|
|
205
|
+
NETWORK_DATA[chain].provider
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
const events = await factory.queryFilter(
|
|
209
|
+
factory.filters.StandardL2TokenCreated(undefined, token.address)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
// Trigger review if not created by standard token factory.
|
|
213
|
+
if (events.length === 0) {
|
|
214
|
+
results.push({
|
|
215
|
+
type: 'warning',
|
|
216
|
+
message: `${folder} on chain ${chain} token ${token.address} not created by standard token factory`,
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Make sure the token is verified on Etherscan.
|
|
221
|
+
// Etherscan API is heavily rate limited, so sleep for 1s to avoid errors.
|
|
222
|
+
await sleep(1000)
|
|
223
|
+
const { qResult } = await (
|
|
224
|
+
await fetch(
|
|
225
|
+
`https://api${
|
|
226
|
+
chain === 'ethereum' ? '' : `-${chain}`
|
|
227
|
+
}.etherscan.io/api?` +
|
|
228
|
+
new URLSearchParams({
|
|
229
|
+
module: 'contract',
|
|
230
|
+
action: 'getsourcecode',
|
|
231
|
+
address: token.address,
|
|
232
|
+
apikey: process.env.ETHERSCAN_API_KEY,
|
|
233
|
+
})
|
|
234
|
+
)
|
|
235
|
+
).json()
|
|
236
|
+
|
|
237
|
+
// Trigger review if code not verified on Etherscan
|
|
238
|
+
if (qResult[0].ABI === 'Contract source code not verified') {
|
|
239
|
+
results.push({
|
|
240
|
+
type: 'warning',
|
|
241
|
+
message: `${folder} on chain ${chain} token ${token.address} code not verified on Etherscan`,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
results.push({
|
|
247
|
+
type: 'warning',
|
|
248
|
+
message: `${folder} on chain ${chain} token ${token.address} is a nonstandard token`,
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Verify that the final generated token list is valid
|
|
255
|
+
const list = generate(datadir)
|
|
256
|
+
const ajv = new Ajv({ allErrors: true, verbose: true })
|
|
257
|
+
addFormats(ajv)
|
|
258
|
+
const validator = ajv.compile(schema)
|
|
259
|
+
if (!validator(list)) {
|
|
260
|
+
results.push({
|
|
261
|
+
type: 'error',
|
|
262
|
+
message: `final token list is invalid: ${JSON.stringify(
|
|
263
|
+
validator.errors,
|
|
264
|
+
null,
|
|
265
|
+
2
|
|
266
|
+
)}`,
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return results
|
|
271
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`'generate' script parse data dir and compile correct token list 1`] = `
|
|
4
|
+
Object {
|
|
5
|
+
"keywords": Array [
|
|
6
|
+
"scaling",
|
|
7
|
+
"layer2",
|
|
8
|
+
"infrastructure",
|
|
9
|
+
],
|
|
10
|
+
"logoURI": "https://ethereum-optimism.github.io/optimism.svg",
|
|
11
|
+
"name": "Optimism",
|
|
12
|
+
"timestamp": "2022-08-17T17:00:00.000Z",
|
|
13
|
+
"tokens": Array [
|
|
14
|
+
Object {
|
|
15
|
+
"address": "0x0000000000000000000000000000000000000000",
|
|
16
|
+
"chainId": 1,
|
|
17
|
+
"decimals": 18,
|
|
18
|
+
"extensions": Object {
|
|
19
|
+
"optimismBridgeAddress": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1",
|
|
20
|
+
},
|
|
21
|
+
"logoURI": "https://ethereum-optimism.github.io/data/ETH/logo.svg",
|
|
22
|
+
"name": "Ether",
|
|
23
|
+
"symbol": "ETH",
|
|
24
|
+
},
|
|
25
|
+
Object {
|
|
26
|
+
"address": "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000",
|
|
27
|
+
"chainId": 10,
|
|
28
|
+
"decimals": 18,
|
|
29
|
+
"extensions": Object {
|
|
30
|
+
"optimismBridgeAddress": "0x4200000000000000000000000000000000000010",
|
|
31
|
+
},
|
|
32
|
+
"logoURI": "https://ethereum-optimism.github.io/data/ETH/logo.svg",
|
|
33
|
+
"name": "Ether",
|
|
34
|
+
"symbol": "ETH",
|
|
35
|
+
},
|
|
36
|
+
Object {
|
|
37
|
+
"address": "0x0000000000000000000000000000000000000000",
|
|
38
|
+
"chainId": 42,
|
|
39
|
+
"decimals": 18,
|
|
40
|
+
"extensions": Object {
|
|
41
|
+
"optimismBridgeAddress": "0x22F24361D548e5FaAfb36d1437839f080363982B",
|
|
42
|
+
},
|
|
43
|
+
"logoURI": "https://ethereum-optimism.github.io/data/ETH/logo.svg",
|
|
44
|
+
"name": "Ether",
|
|
45
|
+
"symbol": "ETH",
|
|
46
|
+
},
|
|
47
|
+
Object {
|
|
48
|
+
"address": "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000",
|
|
49
|
+
"chainId": 69,
|
|
50
|
+
"decimals": 18,
|
|
51
|
+
"extensions": Object {
|
|
52
|
+
"optimismBridgeAddress": "0x4200000000000000000000000000000000000010",
|
|
53
|
+
},
|
|
54
|
+
"logoURI": "https://ethereum-optimism.github.io/data/ETH/logo.svg",
|
|
55
|
+
"name": "Ether",
|
|
56
|
+
"symbol": "ETH",
|
|
57
|
+
},
|
|
58
|
+
Object {
|
|
59
|
+
"address": "0x4200000000000000000000000000000000000042",
|
|
60
|
+
"chainId": 10,
|
|
61
|
+
"decimals": 18,
|
|
62
|
+
"extensions": Object {
|
|
63
|
+
"optimismBridgeAddress": "0x4200000000000000000000000000000000000010",
|
|
64
|
+
},
|
|
65
|
+
"logoURI": "https://ethereum-optimism.github.io/data/OP/logo.png",
|
|
66
|
+
"name": "Optimism",
|
|
67
|
+
"symbol": "OP",
|
|
68
|
+
},
|
|
69
|
+
Object {
|
|
70
|
+
"address": "0x4200000000000000000000000000000000000042",
|
|
71
|
+
"chainId": 69,
|
|
72
|
+
"decimals": 18,
|
|
73
|
+
"extensions": Object {
|
|
74
|
+
"optimismBridgeAddress": "0x4200000000000000000000000000000000000010",
|
|
75
|
+
},
|
|
76
|
+
"logoURI": "https://ethereum-optimism.github.io/data/OP/logo.png",
|
|
77
|
+
"name": "Optimism",
|
|
78
|
+
"symbol": "OP",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
"version": Object {
|
|
82
|
+
"major": 4,
|
|
83
|
+
"minor": 3,
|
|
84
|
+
"patch": Any<Number>,
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Ether",
|
|
3
|
+
"symbol": "ETH",
|
|
4
|
+
"decimals": 18,
|
|
5
|
+
"description": "Ethereum",
|
|
6
|
+
"website": "https://ethereum.org",
|
|
7
|
+
"tokens": {
|
|
8
|
+
"ethereum": {
|
|
9
|
+
"address": "0x0000000000000000000000000000000000000000"
|
|
10
|
+
},
|
|
11
|
+
"optimism": {
|
|
12
|
+
"address": "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000"
|
|
13
|
+
},
|
|
14
|
+
"kovan": {
|
|
15
|
+
"address": "0x0000000000000000000000000000000000000000"
|
|
16
|
+
},
|
|
17
|
+
"optimism-kovan": {
|
|
18
|
+
"address": "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 32 32">
|
|
2
|
+
<g fill="none" fill-rule="evenodd">
|
|
3
|
+
<circle cx="16" cy="16" r="16" fill="#627EEA"/>
|
|
4
|
+
<g fill="#FFF" fill-rule="nonzero">
|
|
5
|
+
<path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/>
|
|
6
|
+
<path d="M16.498 4L9 16.22l7.498-3.35z"/>
|
|
7
|
+
<path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/>
|
|
8
|
+
<path d="M16.498 27.995v-6.028L9 17.616z"/>
|
|
9
|
+
<path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/>
|
|
10
|
+
<path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/>
|
|
11
|
+
</g>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Optimism",
|
|
3
|
+
"symbol": "OP",
|
|
4
|
+
"decimals": 18,
|
|
5
|
+
"nobridge": true,
|
|
6
|
+
"description": "Optimism",
|
|
7
|
+
"website": "https://optimism.io",
|
|
8
|
+
"tokens": {
|
|
9
|
+
"optimism": {
|
|
10
|
+
"address": "0x4200000000000000000000000000000000000042"
|
|
11
|
+
},
|
|
12
|
+
"optimism-kovan": {
|
|
13
|
+
"address": "0x4200000000000000000000000000000000000042"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { filterTokens, defaultTokenList, defaultSymbols } from '../src'
|
|
2
|
+
|
|
3
|
+
describe('default tokens', () => {
|
|
4
|
+
it('all default tokens symbols should exist on the default token list without a typo', () => {
|
|
5
|
+
expect(defaultSymbols.size).toBeGreaterThan(0)
|
|
6
|
+
defaultSymbols.forEach((symbol) => {
|
|
7
|
+
expect(
|
|
8
|
+
filterTokens(defaultTokenList.tokens, { filterTerm: symbol }).length
|
|
9
|
+
).toBeGreaterThan(0)
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { generate } from '../src/generate'
|
|
4
|
+
|
|
5
|
+
test("'generate' script parse data dir and compile correct token list", async () => {
|
|
6
|
+
jest
|
|
7
|
+
.spyOn(path, 'resolve')
|
|
8
|
+
.mockReturnValueOnce(path.resolve(__dirname, 'data'))
|
|
9
|
+
|
|
10
|
+
const mockDate = new Date(1660755600000)
|
|
11
|
+
jest.spyOn(global, 'Date').mockImplementation(() => mockDate as any)
|
|
12
|
+
|
|
13
|
+
const tokenList = generate(path.resolve(__dirname, 'data'))
|
|
14
|
+
expect(tokenList).toMatchSnapshot({
|
|
15
|
+
timestamp: mockDate.toISOString(),
|
|
16
|
+
version: {
|
|
17
|
+
patch: expect.any(Number),
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "./dist",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"target": "es2017",
|
|
6
|
+
"sourceMap": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"composite": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"noImplicitAny": false,
|
|
12
|
+
"removeComments": true,
|
|
13
|
+
"noLib": false,
|
|
14
|
+
"emitDecoratorMetadata": true,
|
|
15
|
+
"experimentalDecorators": true,
|
|
16
|
+
"typeRoots": ["node_modules/@types"]
|
|
17
|
+
},
|
|
18
|
+
"exclude": ["node_modules/**/*", "dist"],
|
|
19
|
+
"include": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"optimism.tokenlist.json",
|
|
22
|
+
"optimism.defaulttokens.json",
|
|
23
|
+
"package.json",
|
|
24
|
+
"src/**/*",
|
|
25
|
+
"bin/**/*"
|
|
26
|
+
]
|
|
27
|
+
}
|