@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.
Files changed (61) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +17 -0
  3. package/dist/arb-token-lists/server/app.d.ts +1 -0
  4. package/dist/arb-token-lists/src/lib/getClargs.d.ts +8 -0
  5. package/dist/arb-token-lists/src/lib/graph.d.ts +2 -0
  6. package/dist/arb-token-lists/src/lib/instantiate_bridge.d.ts +15 -0
  7. package/dist/arb-token-lists/src/lib/token_list_gen.d.ts +23 -0
  8. package/dist/arb-token-lists/src/lib/types.d.ts +36 -0
  9. package/dist/arb-token-lists/src/lib/utils.d.ts +18 -0
  10. package/dist/arb-token-lists/src/main.d.ts +1 -0
  11. package/dist/constants/chainId.d.ts +10 -0
  12. package/dist/constants/tokens.d.ts +5 -0
  13. package/dist/fixtures/index.d.ts +24 -0
  14. package/dist/index.d.ts +18 -0
  15. package/dist/index.js +8 -0
  16. package/dist/providers/ArbitrumMappingProvider.d.ts +11 -0
  17. package/dist/providers/MappingProvider.d.ts +4 -0
  18. package/dist/providers/OptimismMappingProvider.d.ts +10 -0
  19. package/dist/providers/PolygonMappingProvider.d.ts +9 -0
  20. package/dist/providers/index.d.ts +5 -0
  21. package/dist/token-list-bridge-utils.cjs.development.js +2217 -0
  22. package/dist/token-list-bridge-utils.cjs.development.js.map +1 -0
  23. package/dist/token-list-bridge-utils.cjs.production.min.js +2 -0
  24. package/dist/token-list-bridge-utils.cjs.production.min.js.map +1 -0
  25. package/dist/token-list-bridge-utils.esm.js +2201 -0
  26. package/dist/token-list-bridge-utils.esm.js.map +1 -0
  27. package/dist/utils.d.ts +4 -0
  28. package/dist/verify.d.ts +9 -0
  29. package/package.json +68 -0
  30. package/src/arb-token-lists/.env.sample +4 -0
  31. package/src/arb-token-lists/.eslintrc.js +43 -0
  32. package/src/arb-token-lists/.gitignore +4 -0
  33. package/src/arb-token-lists/package.json +40 -0
  34. package/src/arb-token-lists/readme.md +14 -0
  35. package/src/arb-token-lists/server/app.ts +21 -0
  36. package/src/arb-token-lists/src/Assets/coingecko_uris.json +5108 -0
  37. package/src/arb-token-lists/src/Assets/logo_uris.json +44 -0
  38. package/src/arb-token-lists/src/WarningList/warningTokens.json +290 -0
  39. package/src/arb-token-lists/src/lib/getClargs.ts +32 -0
  40. package/src/arb-token-lists/src/lib/graph.ts +116 -0
  41. package/src/arb-token-lists/src/lib/instantiate_bridge.ts +35 -0
  42. package/src/arb-token-lists/src/lib/token_list_gen.ts +334 -0
  43. package/src/arb-token-lists/src/lib/types.ts +44 -0
  44. package/src/arb-token-lists/src/lib/utils.ts +285 -0
  45. package/src/arb-token-lists/src/main.ts +54 -0
  46. package/src/arb-token-lists/tsconfig.json +20 -0
  47. package/src/arb-token-lists/update_all +6 -0
  48. package/src/arb-token-lists/yarn.lock +4273 -0
  49. package/src/constants/chainId.ts +13 -0
  50. package/src/constants/tokens.ts +31 -0
  51. package/src/fixtures/index.ts +104 -0
  52. package/src/index.test.ts +211 -0
  53. package/src/index.ts +92 -0
  54. package/src/providers/ArbitrumMappingProvider.ts +28 -0
  55. package/src/providers/MappingProvider.ts +5 -0
  56. package/src/providers/OptimismMappingProvider.ts +13 -0
  57. package/src/providers/PolygonMappingProvider.ts +127 -0
  58. package/src/providers/index.ts +17 -0
  59. package/src/utils.ts +21 -0
  60. package/src/verify.test.ts +19 -0
  61. package/src/verify.ts +60 -0
@@ -0,0 +1,13 @@
1
+ export enum ChainId {
2
+ MAINNET = 1,
3
+ RINKEBY = 4,
4
+
5
+ ARBITRUM_ONE = 42161,
6
+ ARBITRUM_RINKEBY = 421611,
7
+
8
+ OPTIMISM = 10,
9
+ OPTIMISTIC_KOVAN = 69,
10
+
11
+ POLYGON = 137,
12
+ POLYGON_MUMBAI = 80001,
13
+ }
@@ -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,5 @@
1
+ import { TokenList } from '@uniswap/token-lists'
2
+
3
+ export interface MappingProvider {
4
+ provide(l1TokenList: TokenList): Promise<TokenList>
5
+ }
@@ -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
+ })