@uniswap/token-list-bridge-utils 0.1.1-beta.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/LICENSE +674 -0
- package/README.md +17 -0
- package/dist/arb-token-lists/server/app.d.ts +1 -0
- package/dist/arb-token-lists/src/lib/getClargs.d.ts +8 -0
- package/dist/arb-token-lists/src/lib/graph.d.ts +2 -0
- package/dist/arb-token-lists/src/lib/instantiate_bridge.d.ts +15 -0
- package/dist/arb-token-lists/src/lib/token_list_gen.d.ts +23 -0
- package/dist/arb-token-lists/src/lib/types.d.ts +36 -0
- package/dist/arb-token-lists/src/lib/utils.d.ts +18 -0
- package/dist/arb-token-lists/src/main.d.ts +1 -0
- package/dist/constants/chainId.d.ts +10 -0
- package/dist/constants/tokens.d.ts +5 -0
- package/dist/fixtures/index.d.ts +24 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8 -0
- package/dist/providers/ArbitrumMappingProvider.d.ts +11 -0
- package/dist/providers/MappingProvider.d.ts +4 -0
- package/dist/providers/OptimismMappingProvider.d.ts +10 -0
- package/dist/providers/PolygonMappingProvider.d.ts +9 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/token-list-bridge-utils.cjs.development.js +2217 -0
- package/dist/token-list-bridge-utils.cjs.development.js.map +1 -0
- package/dist/token-list-bridge-utils.cjs.production.min.js +2 -0
- package/dist/token-list-bridge-utils.cjs.production.min.js.map +1 -0
- package/dist/token-list-bridge-utils.esm.js +2201 -0
- package/dist/token-list-bridge-utils.esm.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/verify.d.ts +9 -0
- package/package.json +68 -0
- package/src/arb-token-lists/.env.sample +4 -0
- package/src/arb-token-lists/.eslintrc.js +43 -0
- package/src/arb-token-lists/.gitignore +4 -0
- package/src/arb-token-lists/package.json +40 -0
- package/src/arb-token-lists/readme.md +14 -0
- package/src/arb-token-lists/server/app.ts +21 -0
- package/src/arb-token-lists/src/Assets/coingecko_uris.json +5108 -0
- package/src/arb-token-lists/src/Assets/logo_uris.json +44 -0
- package/src/arb-token-lists/src/WarningList/warningTokens.json +290 -0
- package/src/arb-token-lists/src/lib/getClargs.ts +32 -0
- package/src/arb-token-lists/src/lib/graph.ts +116 -0
- package/src/arb-token-lists/src/lib/instantiate_bridge.ts +35 -0
- package/src/arb-token-lists/src/lib/token_list_gen.ts +334 -0
- package/src/arb-token-lists/src/lib/types.ts +44 -0
- package/src/arb-token-lists/src/lib/utils.ts +285 -0
- package/src/arb-token-lists/src/main.ts +54 -0
- package/src/arb-token-lists/tsconfig.json +20 -0
- package/src/arb-token-lists/update_all +6 -0
- package/src/arb-token-lists/yarn.lock +4273 -0
- package/src/constants/chainId.ts +13 -0
- package/src/constants/tokens.ts +31 -0
- package/src/fixtures/index.ts +104 -0
- package/src/index.test.ts +211 -0
- package/src/index.ts +92 -0
- package/src/providers/ArbitrumMappingProvider.ts +28 -0
- package/src/providers/MappingProvider.ts +5 -0
- package/src/providers/OptimismMappingProvider.ts +13 -0
- package/src/providers/PolygonMappingProvider.ts +127 -0
- package/src/providers/index.ts +17 -0
- package/src/utils.ts +21 -0
- package/src/verify.test.ts +19 -0
- package/src/verify.ts +60 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Token } from '@uniswap/sdk-core'
|
|
2
|
+
import { ChainId } from './chainId'
|
|
3
|
+
|
|
4
|
+
export const DAI = new Token(
|
|
5
|
+
ChainId.MAINNET,
|
|
6
|
+
'0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
7
|
+
18,
|
|
8
|
+
'DAI',
|
|
9
|
+
'Dai Stablecoin'
|
|
10
|
+
)
|
|
11
|
+
export const DAI_ARBITRUM_ONE = new Token(
|
|
12
|
+
ChainId.ARBITRUM_ONE,
|
|
13
|
+
'0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
|
|
14
|
+
18,
|
|
15
|
+
'DAI',
|
|
16
|
+
'Dai stable coin'
|
|
17
|
+
)
|
|
18
|
+
export const DAI_OPTIMISM = new Token(
|
|
19
|
+
ChainId.OPTIMISM,
|
|
20
|
+
'0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
|
|
21
|
+
18,
|
|
22
|
+
'DAI',
|
|
23
|
+
'Dai stable coin'
|
|
24
|
+
)
|
|
25
|
+
export const DAI_POLYGON = new Token(
|
|
26
|
+
ChainId.POLYGON,
|
|
27
|
+
'0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
|
|
28
|
+
18,
|
|
29
|
+
'DAI',
|
|
30
|
+
'Dai Stablecoin'
|
|
31
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Token } from '@uniswap/sdk-core'
|
|
2
|
+
import { TokenInfo, TokenList } from '@uniswap/token-lists'
|
|
3
|
+
import { ChainId } from '../constants/chainId'
|
|
4
|
+
import { DAI, DAI_ARBITRUM_ONE, DAI_POLYGON } from '../constants/tokens'
|
|
5
|
+
import { compareTokenInfos } from '../utils'
|
|
6
|
+
|
|
7
|
+
export const Tokens: Partial<Record<ChainId, Record<string, TokenInfo>>> = {
|
|
8
|
+
[ChainId.MAINNET]: {
|
|
9
|
+
DAI: tokenToTokenInfo(DAI),
|
|
10
|
+
},
|
|
11
|
+
[ChainId.ARBITRUM_ONE]: {
|
|
12
|
+
DAI: tokenToTokenInfo(DAI_ARBITRUM_ONE),
|
|
13
|
+
},
|
|
14
|
+
[ChainId.POLYGON]: {
|
|
15
|
+
DAI: tokenToTokenInfo(DAI_POLYGON),
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const sampleL1TokenList: TokenList = {
|
|
20
|
+
name: 'Sample',
|
|
21
|
+
timestamp: new Date(1646146610).toISOString(),
|
|
22
|
+
version: {
|
|
23
|
+
major: 1,
|
|
24
|
+
minor: 0,
|
|
25
|
+
patch: 0,
|
|
26
|
+
},
|
|
27
|
+
tokens: [Tokens[ChainId.MAINNET]!.DAI],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const arbBridgeL2Address = '0x467194771dae2967aef3ecbedd3bf9a310c76c65'
|
|
31
|
+
export const arbBridgeL1Address = '0xd3b5b60020504bc3489d6949d545893982ba3011'
|
|
32
|
+
|
|
33
|
+
export const arbedSampleTokenList = {
|
|
34
|
+
...sampleL1TokenList,
|
|
35
|
+
name: 'Arbed Sample',
|
|
36
|
+
tokens: [
|
|
37
|
+
{
|
|
38
|
+
...Tokens[ChainId.ARBITRUM_ONE]!.DAI,
|
|
39
|
+
extensions: {
|
|
40
|
+
bridgeInfo: {
|
|
41
|
+
[ChainId.MAINNET]: {
|
|
42
|
+
tokenAddress: DAI.address,
|
|
43
|
+
originBridgeAddress: arbBridgeL2Address,
|
|
44
|
+
destBridgeAddress: arbBridgeL1Address,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
} as unknown as TokenInfo,
|
|
49
|
+
{
|
|
50
|
+
...(Tokens[ChainId.MAINNET]!.DAI as unknown as TokenInfo),
|
|
51
|
+
extensions: {
|
|
52
|
+
bridgeInfo: {
|
|
53
|
+
[ChainId.ARBITRUM_ONE]: {
|
|
54
|
+
tokenAddress: DAI_ARBITRUM_ONE.address,
|
|
55
|
+
destBridgeAddress: arbBridgeL2Address,
|
|
56
|
+
originBridgeAddress: arbBridgeL1Address,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
} as unknown as TokenInfo,
|
|
61
|
+
].sort(compareTokenInfos),
|
|
62
|
+
}
|
|
63
|
+
export const polygonedSampleTokenList = {
|
|
64
|
+
...sampleL1TokenList,
|
|
65
|
+
name: 'Polygoned Sample',
|
|
66
|
+
tokens: [
|
|
67
|
+
{
|
|
68
|
+
...Tokens[ChainId.POLYGON]!.DAI,
|
|
69
|
+
extensions: {
|
|
70
|
+
bridgeInfo: {
|
|
71
|
+
[ChainId.MAINNET]: {
|
|
72
|
+
tokenAddress: DAI.address,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
} as unknown as TokenInfo,
|
|
77
|
+
{
|
|
78
|
+
...(Tokens[ChainId.MAINNET]!.DAI as unknown as TokenInfo),
|
|
79
|
+
extensions: {
|
|
80
|
+
bridgeInfo: {
|
|
81
|
+
[ChainId.POLYGON]: {
|
|
82
|
+
tokenAddress: DAI_POLYGON.address,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
} as unknown as TokenInfo,
|
|
87
|
+
].sort(compareTokenInfos),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function tokenToTokenInfo({
|
|
91
|
+
chainId,
|
|
92
|
+
address,
|
|
93
|
+
name,
|
|
94
|
+
decimals,
|
|
95
|
+
symbol,
|
|
96
|
+
}: Token): TokenInfo {
|
|
97
|
+
return {
|
|
98
|
+
chainId,
|
|
99
|
+
address: address,
|
|
100
|
+
name: name ?? 'n/a',
|
|
101
|
+
decimals,
|
|
102
|
+
symbol: symbol ?? 'n/a',
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { TokenInfo } from '@uniswap/token-lists'
|
|
2
|
+
import { chainify, chainifyTokenList, mergeTokenLists } from '.'
|
|
3
|
+
import { ChainId } from './constants/chainId'
|
|
4
|
+
import { DAI, DAI_ARBITRUM_ONE, DAI_POLYGON } from './constants/tokens'
|
|
5
|
+
import {
|
|
6
|
+
arbBridgeL1Address,
|
|
7
|
+
arbBridgeL2Address,
|
|
8
|
+
arbedSampleTokenList,
|
|
9
|
+
polygonedSampleTokenList,
|
|
10
|
+
sampleL1TokenList,
|
|
11
|
+
Tokens,
|
|
12
|
+
} from './fixtures'
|
|
13
|
+
|
|
14
|
+
describe(chainifyTokenList, () => {
|
|
15
|
+
it('no-ops on Optimism', async () => {
|
|
16
|
+
const tokenList = await chainifyTokenList(
|
|
17
|
+
ChainId.ARBITRUM_ONE,
|
|
18
|
+
sampleL1TokenList
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
expect(tokenList).toEqual(tokenList)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('outputs arbitrum list correctly', async () => {
|
|
25
|
+
const tokenList = await chainifyTokenList(
|
|
26
|
+
ChainId.ARBITRUM_ONE,
|
|
27
|
+
sampleL1TokenList
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(tokenList).not.toBeUndefined()
|
|
31
|
+
expect(tokenList?.version).toEqual(arbedSampleTokenList.version)
|
|
32
|
+
expect(
|
|
33
|
+
tokenList?.tokens.map((t) => [
|
|
34
|
+
t.address.toLowerCase(),
|
|
35
|
+
t.chainId,
|
|
36
|
+
JSON.parse(JSON.stringify(t.extensions).toLowerCase()),
|
|
37
|
+
])
|
|
38
|
+
).toEqual(
|
|
39
|
+
// ignores other metadata
|
|
40
|
+
arbedSampleTokenList.tokens.map((t) => [
|
|
41
|
+
t.address.toLowerCase(),
|
|
42
|
+
t.chainId,
|
|
43
|
+
JSON.parse(JSON.stringify(t.extensions).toLowerCase()),
|
|
44
|
+
])
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('outputs polygon list correctly', async () => {
|
|
49
|
+
const tokenList = await chainifyTokenList(
|
|
50
|
+
ChainId.POLYGON,
|
|
51
|
+
sampleL1TokenList
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
expect(tokenList).toBeDefined()
|
|
55
|
+
expect(tokenList?.version).toEqual(polygonedSampleTokenList.version)
|
|
56
|
+
expect(
|
|
57
|
+
tokenList?.tokens.map((t) => [t.address, t.chainId, t.extensions])
|
|
58
|
+
).toEqual(
|
|
59
|
+
// ignores other metadata
|
|
60
|
+
polygonedSampleTokenList.tokens.map((t) => [
|
|
61
|
+
t.address,
|
|
62
|
+
t.chainId,
|
|
63
|
+
t.extensions,
|
|
64
|
+
])
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe(mergeTokenLists, () => {
|
|
70
|
+
it('correctly deduplicates', () => {
|
|
71
|
+
const merged = mergeTokenLists(sampleL1TokenList, sampleL1TokenList)
|
|
72
|
+
|
|
73
|
+
expect(merged).toEqual(sampleL1TokenList)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('deduplicates with address case insensitivity', () => {
|
|
77
|
+
const allUpper = JSON.parse(JSON.stringify(sampleL1TokenList))
|
|
78
|
+
allUpper.tokens = allUpper.tokens.map((t: TokenInfo) => ({
|
|
79
|
+
...t,
|
|
80
|
+
address: t.address.toUpperCase(),
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
const mergedLeft = mergeTokenLists(sampleL1TokenList, allUpper)
|
|
84
|
+
expect(mergedLeft).toEqual(sampleL1TokenList)
|
|
85
|
+
|
|
86
|
+
// should prefer the first provided token list
|
|
87
|
+
const mergedRight = mergeTokenLists(allUpper, sampleL1TokenList)
|
|
88
|
+
expect(mergedRight).not.toEqual(sampleL1TokenList)
|
|
89
|
+
expect(mergedRight).toEqual(allUpper)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('correctly merges multiple lists', () => {
|
|
93
|
+
const withPolygon = mergeTokenLists(
|
|
94
|
+
sampleL1TokenList,
|
|
95
|
+
polygonedSampleTokenList
|
|
96
|
+
)
|
|
97
|
+
const plusArbitrum = mergeTokenLists(withPolygon, arbedSampleTokenList)
|
|
98
|
+
|
|
99
|
+
expect(withPolygon.tokens).toEqual([
|
|
100
|
+
{
|
|
101
|
+
...Tokens[ChainId.MAINNET]!.DAI,
|
|
102
|
+
extensions: {
|
|
103
|
+
bridgeInfo: {
|
|
104
|
+
[ChainId.POLYGON]: {
|
|
105
|
+
tokenAddress: DAI_POLYGON.address,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
...Tokens[ChainId.POLYGON]!.DAI,
|
|
112
|
+
extensions: {
|
|
113
|
+
bridgeInfo: {
|
|
114
|
+
[ChainId.MAINNET]: {
|
|
115
|
+
tokenAddress: DAI.address,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
expect(plusArbitrum.tokens).toEqual([
|
|
123
|
+
{
|
|
124
|
+
...Tokens[ChainId.MAINNET]!.DAI,
|
|
125
|
+
extensions: {
|
|
126
|
+
bridgeInfo: {
|
|
127
|
+
[ChainId.POLYGON]: {
|
|
128
|
+
tokenAddress: DAI_POLYGON.address,
|
|
129
|
+
},
|
|
130
|
+
[ChainId.ARBITRUM_ONE]: {
|
|
131
|
+
tokenAddress: DAI_ARBITRUM_ONE.address,
|
|
132
|
+
destBridgeAddress: arbBridgeL2Address,
|
|
133
|
+
originBridgeAddress: arbBridgeL1Address,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
...Tokens[ChainId.POLYGON]!.DAI,
|
|
140
|
+
extensions: {
|
|
141
|
+
bridgeInfo: {
|
|
142
|
+
[ChainId.MAINNET]: {
|
|
143
|
+
tokenAddress: DAI.address,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
...Tokens[ChainId.ARBITRUM_ONE]!.DAI,
|
|
150
|
+
extensions: {
|
|
151
|
+
bridgeInfo: {
|
|
152
|
+
[ChainId.MAINNET]: {
|
|
153
|
+
tokenAddress: DAI.address,
|
|
154
|
+
originBridgeAddress: arbBridgeL2Address,
|
|
155
|
+
destBridgeAddress: arbBridgeL1Address,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
])
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe(chainify, () => {
|
|
165
|
+
it('provides bridge extensions', async () => {
|
|
166
|
+
const chainified = await chainify(sampleL1TokenList)
|
|
167
|
+
|
|
168
|
+
expect(chainified.tokens).toEqual([
|
|
169
|
+
{
|
|
170
|
+
...Tokens[ChainId.MAINNET]!.DAI,
|
|
171
|
+
extensions: {
|
|
172
|
+
bridgeInfo: {
|
|
173
|
+
[ChainId.POLYGON]: {
|
|
174
|
+
tokenAddress: DAI_POLYGON.address,
|
|
175
|
+
},
|
|
176
|
+
[ChainId.ARBITRUM_ONE]: {
|
|
177
|
+
tokenAddress: DAI_ARBITRUM_ONE.address,
|
|
178
|
+
destBridgeAddress: arbBridgeL2Address,
|
|
179
|
+
originBridgeAddress: arbBridgeL1Address,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
...Tokens[ChainId.POLYGON]!.DAI,
|
|
186
|
+
extensions: {
|
|
187
|
+
bridgeInfo: {
|
|
188
|
+
[ChainId.MAINNET]: {
|
|
189
|
+
tokenAddress: DAI.address,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
...Tokens[ChainId.ARBITRUM_ONE]!.DAI,
|
|
196
|
+
extensions: {
|
|
197
|
+
bridgeInfo: {
|
|
198
|
+
[ChainId.MAINNET]: {
|
|
199
|
+
tokenAddress: DAI.address.toLowerCase(),
|
|
200
|
+
originBridgeAddress: arbBridgeL2Address,
|
|
201
|
+
destBridgeAddress: arbBridgeL1Address,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
logoURI:
|
|
206
|
+
'https://assets.coingecko.com/coins/images/9956/thumb/4943.png?1636636734',
|
|
207
|
+
name: 'Dai Stablecoin',
|
|
208
|
+
},
|
|
209
|
+
])
|
|
210
|
+
})
|
|
211
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { TokenList } from '@uniswap/token-lists'
|
|
2
|
+
import { cloneDeep, groupBy, merge } from 'lodash'
|
|
3
|
+
import { ChainId } from './constants/chainId'
|
|
4
|
+
import { getMappingProvider } from './providers'
|
|
5
|
+
import {
|
|
6
|
+
compareTokenInfos,
|
|
7
|
+
getTokenList,
|
|
8
|
+
TokenListOrFetchableTokenList
|
|
9
|
+
} from './utils'
|
|
10
|
+
import { verifyExtensions } from './verify'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Adds bridgeInfo to the given token list for Optimism, Polygon and Arbitrum.
|
|
14
|
+
* @param l1TokenListOrPathOrUrl
|
|
15
|
+
* @returns TokenList with l2 bridgeInfo filled
|
|
16
|
+
*/
|
|
17
|
+
export async function chainify(
|
|
18
|
+
l1TokenListOrPathOrUrl: TokenListOrFetchableTokenList
|
|
19
|
+
): Promise<TokenList> {
|
|
20
|
+
const l1TokenList = await getTokenList(l1TokenListOrPathOrUrl)
|
|
21
|
+
|
|
22
|
+
const optimisimed = await chainifyTokenList(
|
|
23
|
+
ChainId.OPTIMISM,
|
|
24
|
+
l1TokenListOrPathOrUrl
|
|
25
|
+
)
|
|
26
|
+
const polygoned = await chainifyTokenList(
|
|
27
|
+
ChainId.POLYGON,
|
|
28
|
+
l1TokenListOrPathOrUrl
|
|
29
|
+
)
|
|
30
|
+
const arbified = await chainifyTokenList(
|
|
31
|
+
ChainId.ARBITRUM_ONE,
|
|
32
|
+
l1TokenListOrPathOrUrl
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return mergeTokenLists(
|
|
36
|
+
l1TokenList, // providing l1 first to make sure duplicated tokens resolve to this list
|
|
37
|
+
mergeTokenLists(mergeTokenLists(arbified, optimisimed), polygoned)
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Given a network and a TokenList, returns the TokenList with `extensions` filled.
|
|
43
|
+
* @param chainId chainId to operate on
|
|
44
|
+
* @param l1TokenListOrPathOrUrl either an L1 TokenList object or a path/url to a TokenList
|
|
45
|
+
* @returns L1 TokenList with `extensions` filled for the given network
|
|
46
|
+
*/
|
|
47
|
+
export async function chainifyTokenList(
|
|
48
|
+
chainId: ChainId,
|
|
49
|
+
l1TokenListOrPathOrUrl: TokenListOrFetchableTokenList
|
|
50
|
+
): Promise<TokenList> {
|
|
51
|
+
try {
|
|
52
|
+
const tokenList = await getMappingProvider(chainId).provide(
|
|
53
|
+
await getTokenList(l1TokenListOrPathOrUrl)
|
|
54
|
+
)
|
|
55
|
+
return verifyExtensions(tokenList)
|
|
56
|
+
} catch (e) {
|
|
57
|
+
throw new Error(`An error occured: ${e}`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Merges two token lists, resolving conflicts to primary list. */
|
|
62
|
+
export function mergeTokenLists(
|
|
63
|
+
primary: TokenList,
|
|
64
|
+
secondary: TokenList
|
|
65
|
+
): TokenList {
|
|
66
|
+
primary = cloneDeep(primary)
|
|
67
|
+
secondary = cloneDeep(secondary)
|
|
68
|
+
|
|
69
|
+
const grouped = groupBy(
|
|
70
|
+
[...secondary.tokens, ...primary.tokens],
|
|
71
|
+
(t) => `${t.chainId}-${t.address.toLowerCase()}`
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const merged = Object.values(grouped).map((group) => {
|
|
75
|
+
if (group.length === 1) {
|
|
76
|
+
return group[0]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const merged = merge(group[0], group[1])
|
|
80
|
+
if (merged.extensions?.bridgeInfo) {
|
|
81
|
+
// remove reference to self-chain from merge
|
|
82
|
+
// @ts-expect-error TokenList schema doesn't yet define objects
|
|
83
|
+
delete merged.extensions.bridgeInfo[merged.chainId]
|
|
84
|
+
}
|
|
85
|
+
return merged
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return cloneDeep({
|
|
89
|
+
...primary,
|
|
90
|
+
tokens: merged.sort(compareTokenInfos),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { TokenList } from '@uniswap/token-lists'
|
|
2
|
+
import { generateTokenList } from '../arb-token-lists/src/lib/token_list_gen'
|
|
3
|
+
import { compareTokenInfos } from '../utils'
|
|
4
|
+
import { MappingProvider } from './MappingProvider'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The Arbitrum team maintains an (unpublished) package to `arbify` an L1
|
|
8
|
+
* token list.
|
|
9
|
+
*
|
|
10
|
+
* The provider simply invokes this package which we host as a submodule.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class ArbitrumMappingProvider implements MappingProvider {
|
|
14
|
+
async provide(l1TokenList: TokenList): Promise<TokenList> {
|
|
15
|
+
const arbedTokenList = (await generateTokenList(
|
|
16
|
+
l1TokenList,
|
|
17
|
+
/*prevArbTokenList=*/ undefined,
|
|
18
|
+
{
|
|
19
|
+
includeAllL1Tokens: true,
|
|
20
|
+
}
|
|
21
|
+
)) as unknown as TokenList
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...arbedTokenList,
|
|
25
|
+
tokens: arbedTokenList.tokens.sort(compareTokenInfos),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TokenList } from '@uniswap/token-lists'
|
|
2
|
+
import { MappingProvider } from './MappingProvider'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The Optimism L2 mapping is manually maintained by the Optimism team.
|
|
6
|
+
*
|
|
7
|
+
* This provider mostly no-ops, i.e. grabs the list and returns it.
|
|
8
|
+
*/
|
|
9
|
+
export class OptimismMappingProvider implements MappingProvider {
|
|
10
|
+
async provide(l1TokenList: TokenList): Promise<TokenList> {
|
|
11
|
+
return l1TokenList
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { TokenInfo, TokenList } from '@uniswap/token-lists'
|
|
2
|
+
import axios from 'axios'
|
|
3
|
+
import { compareTokenInfos } from '../utils'
|
|
4
|
+
import { MappingProvider } from './MappingProvider'
|
|
5
|
+
|
|
6
|
+
interface PolygonMappedToken {
|
|
7
|
+
chainId: number
|
|
8
|
+
child_address_passed_by_user: boolean
|
|
9
|
+
child_token: string
|
|
10
|
+
count: number
|
|
11
|
+
created_at: string
|
|
12
|
+
decimals: number
|
|
13
|
+
deleted: boolean
|
|
14
|
+
id: number
|
|
15
|
+
map_type: 'POS'
|
|
16
|
+
mintable: boolean
|
|
17
|
+
name: string
|
|
18
|
+
new_child_token: string
|
|
19
|
+
new_mintable: boolean
|
|
20
|
+
owner: string
|
|
21
|
+
reason: string
|
|
22
|
+
reason_for_remapping: string
|
|
23
|
+
remapping_allowed: boolean
|
|
24
|
+
remapping_request_submitted: boolean
|
|
25
|
+
remapping_verified: boolean
|
|
26
|
+
root_token: string
|
|
27
|
+
status: number
|
|
28
|
+
symbol: string
|
|
29
|
+
token_type: string // ERC20
|
|
30
|
+
updated_at: string
|
|
31
|
+
uri: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type PolygonMappedTokenData = { [key: string]: PolygonMappedToken }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The Polygon team manually maintains the mapping via user submissions at
|
|
38
|
+
* https://mapper.polygon.technology.
|
|
39
|
+
*/
|
|
40
|
+
export class PolygonMappingProvider implements MappingProvider {
|
|
41
|
+
async provide(l1TokenList: TokenList): Promise<TokenList> {
|
|
42
|
+
const polygonTokens = await fetchPolygonMappedTokens()
|
|
43
|
+
|
|
44
|
+
const mappedTokens = []
|
|
45
|
+
for (const rootToken of l1TokenList.tokens) {
|
|
46
|
+
const childToken = polygonTokens[rootToken.address.toLowerCase()]
|
|
47
|
+
const childTokenValid = Boolean(childToken && !childToken.deleted)
|
|
48
|
+
|
|
49
|
+
// build extension info if available
|
|
50
|
+
const toRootExtensions = childTokenValid
|
|
51
|
+
? {
|
|
52
|
+
extensions: {
|
|
53
|
+
bridgeInfo: {
|
|
54
|
+
[rootToken.chainId]: {
|
|
55
|
+
tokenAddress: childToken.root_token,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
: {}
|
|
61
|
+
const toChildExtensions = childTokenValid
|
|
62
|
+
? {
|
|
63
|
+
extensions: {
|
|
64
|
+
bridgeInfo: {
|
|
65
|
+
[childToken.chainId]: {
|
|
66
|
+
tokenAddress: childToken.child_token,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
: {}
|
|
72
|
+
|
|
73
|
+
const rootTokenInfo: TokenInfo = {
|
|
74
|
+
...rootToken,
|
|
75
|
+
...toChildExtensions,
|
|
76
|
+
} as unknown as TokenInfo
|
|
77
|
+
|
|
78
|
+
const childTokenInfo: TokenInfo = {
|
|
79
|
+
...rootToken,
|
|
80
|
+
chainId: childToken.chainId,
|
|
81
|
+
name: childToken.name ?? rootToken.name,
|
|
82
|
+
symbol: childToken.symbol ?? rootToken.symbol,
|
|
83
|
+
address: childToken.child_token,
|
|
84
|
+
...toRootExtensions,
|
|
85
|
+
} as unknown as TokenInfo
|
|
86
|
+
|
|
87
|
+
mappedTokens.push(rootTokenInfo)
|
|
88
|
+
mappedTokens.push(childTokenInfo)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const tokenList = {
|
|
92
|
+
name: `Polygon ${l1TokenList.name}`,
|
|
93
|
+
timestamp: new Date().toISOString(),
|
|
94
|
+
version: l1TokenList.version,
|
|
95
|
+
tokens: mappedTokens.sort(compareTokenInfos),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return tokenList
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const url = 'https://tokenmapper.api.matic.today/api/v1/mapping?'
|
|
103
|
+
const params = 'map_type=[%22POS%22]&chain_id=137&limit=200&offset='
|
|
104
|
+
|
|
105
|
+
async function fetchPolygonMappedTokens(): Promise<PolygonMappedTokenData> {
|
|
106
|
+
let offset = 0
|
|
107
|
+
const tokens: PolygonMappedTokenData = {}
|
|
108
|
+
|
|
109
|
+
while (true) {
|
|
110
|
+
const response = await axios.get(`${url}${params}${offset}`)
|
|
111
|
+
|
|
112
|
+
if (response.data.message === 'success') {
|
|
113
|
+
for (const token of response.data.data.mapping) {
|
|
114
|
+
tokens[token.root_token.toLowerCase()] = token
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (response.data.data.has_next_page === true) {
|
|
118
|
+
offset += 200
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
break
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return tokens
|
|
127
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChainId } from '../constants/chainId'
|
|
2
|
+
import { ArbitrumMappingProvider } from './ArbitrumMappingProvider'
|
|
3
|
+
import { OptimismMappingProvider } from './OptimismMappingProvider'
|
|
4
|
+
import { PolygonMappingProvider } from './PolygonMappingProvider'
|
|
5
|
+
|
|
6
|
+
export function getMappingProvider(chainId: ChainId) {
|
|
7
|
+
switch (chainId) {
|
|
8
|
+
case ChainId.ARBITRUM_ONE:
|
|
9
|
+
return new ArbitrumMappingProvider()
|
|
10
|
+
case ChainId.OPTIMISM:
|
|
11
|
+
return new OptimismMappingProvider()
|
|
12
|
+
case ChainId.POLYGON:
|
|
13
|
+
return new PolygonMappingProvider()
|
|
14
|
+
default:
|
|
15
|
+
throw new Error(`Chain ${chainId} not supported.`)
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TokenInfo, TokenList } from '@uniswap/token-lists'
|
|
2
|
+
import { getTokenListObj } from './arb-token-lists/src/lib/utils'
|
|
3
|
+
|
|
4
|
+
export type TokenListOrFetchableTokenList = TokenList | string
|
|
5
|
+
|
|
6
|
+
export function compareTokenInfos(t1: TokenInfo, t2: TokenInfo) {
|
|
7
|
+
if (t1.chainId === t2.chainId) {
|
|
8
|
+
return t1.symbol.toLowerCase() < t2.symbol.toLowerCase() ? -1 : 1
|
|
9
|
+
}
|
|
10
|
+
return t1.chainId < t2.chainId ? -1 : 1
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function getTokenList(
|
|
14
|
+
l1TokenListOrPathOrUrl: TokenListOrFetchableTokenList
|
|
15
|
+
): Promise<TokenList> {
|
|
16
|
+
if (typeof l1TokenListOrPathOrUrl === 'string') {
|
|
17
|
+
return getTokenListObj(l1TokenListOrPathOrUrl)
|
|
18
|
+
} else {
|
|
19
|
+
return l1TokenListOrPathOrUrl
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
arbedSampleTokenList,
|
|
3
|
+
polygonedSampleTokenList,
|
|
4
|
+
sampleL1TokenList,
|
|
5
|
+
} from './fixtures'
|
|
6
|
+
import { verifyExtensions } from './verify'
|
|
7
|
+
|
|
8
|
+
describe(verifyExtensions, () => {
|
|
9
|
+
it('passes list without extensions', () => {
|
|
10
|
+
expect(verifyExtensions(sampleL1TokenList)).toBe(sampleL1TokenList)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('passes a list with extensions', () => {
|
|
14
|
+
expect(verifyExtensions(polygonedSampleTokenList)).toBe(
|
|
15
|
+
polygonedSampleTokenList
|
|
16
|
+
)
|
|
17
|
+
expect(verifyExtensions(arbedSampleTokenList)).toBe(arbedSampleTokenList)
|
|
18
|
+
})
|
|
19
|
+
})
|