@uniswap/token-list-bridge-utils 1.0.8 → 1.1.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.
@@ -1,7 +1,7 @@
1
- import { PolygonMappedTokenData } from '../constants/types'
1
+ import { GenericMappedTokenData, PolygonMappedTokenData } from '../constants/types'
2
2
 
3
3
  export interface MappingProvider {
4
4
  provide(): Promise<
5
- PolygonMappedTokenData | { [key: string]: string | undefined }
5
+ PolygonMappedTokenData | GenericMappedTokenData
6
6
  >
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import { MappingProvider } from './MappingProvider'
2
2
  import { ChainId } from '../constants/chainId'
3
3
  import { getTokenList } from '../utils'
4
+ import { GenericMappedTokenData } from '../constants/types'
4
5
 
5
6
  const optimismTokenListURL =
6
7
  'https://raw.githubusercontent.com/' +
@@ -12,7 +13,7 @@ const optimismTokenListURL =
12
13
  * This provider provides the l1->l2(Optimism) token mappings.
13
14
  */
14
15
  export class OptimismMappingProvider implements MappingProvider {
15
- async provide(): Promise<{ [key: string]: string | undefined }> {
16
+ async provide(): Promise<GenericMappedTokenData> {
16
17
  const tokens: { [key: string]: string | undefined } = {}
17
18
 
18
19
  let optimismTokens = await getTokenList(optimismTokenListURL)
@@ -2,8 +2,9 @@ import axios from 'axios'
2
2
  import { MappingProvider } from './MappingProvider'
3
3
  import { PolygonMappedTokenData } from '../constants/types'
4
4
 
5
- const url = 'https://tokenmapper.api.matic.today/api/v1/mapping?'
6
- const params = 'map_type=[%22POS%22]&chain_id=137&limit=200&offset='
5
+ // called from https://mapper.polygon.technology
6
+ const url = 'https://open-api.polygon.technology/api/v1/info/all-mappings'
7
+ const access_token = '504afd90-3228-4df9-9d88-9b4d70646101'
7
8
 
8
9
  /**
9
10
  * The Polygon team manually maintains the mapping via user submissions at
@@ -13,22 +14,15 @@ const params = 'map_type=[%22POS%22]&chain_id=137&limit=200&offset='
13
14
  */
14
15
  export class PolygonMappingProvider implements MappingProvider {
15
16
  async provide(): Promise<PolygonMappedTokenData> {
16
- let offset = 0
17
+ const response = await axios.get(url, {
18
+ headers: { 'x-access-token': access_token },
19
+ })
17
20
  const tokens: PolygonMappedTokenData = {}
18
- while (true) {
19
- const response = await axios.get(`${url}${params}${offset}`)
20
21
 
21
- if (response.data.message === 'success') {
22
- for (const token of response.data.data.mapping) {
23
- tokens[token.root_token.toLowerCase()] = token
24
- }
25
-
26
- if (response.data.data.has_next_page === true) {
27
- offset += 200
28
- continue
29
- }
22
+ for (const token of response.data) {
23
+ if (token.isPos) {
24
+ tokens[token.rootToken.toLowerCase()] = token
30
25
  }
31
- break
32
26
  }
33
27
  return tokens
34
28
  }
@@ -13,80 +13,103 @@ import {
13
13
  import ERC20Abi from '../abis/erc20'
14
14
  // TODO: use ethers for contract calls (without performance reduction).
15
15
  import Web3 from 'web3'
16
+ import {
17
+ GenericMappedTokenData,
18
+ PolygonMappedTokenData,
19
+ } from '../constants/types'
16
20
 
17
21
  const web3 = new Web3()
18
22
 
23
+ // chains we support fetching mappings for (can be different than the l2ChainIds arg for buildList)
24
+ const SUPPORTED_L2_CHAINS = [
25
+ ChainId.ARBITRUM_ONE,
26
+ ChainId.POLYGON,
27
+ ChainId.OPTIMISM,
28
+ ]
29
+
19
30
  export async function buildList(
20
- chainId: ChainId,
31
+ l2ChainIds: Array<ChainId>,
21
32
  l1TokenList: TokenList
22
33
  ): Promise<TokenList> {
23
- web3.setProvider(getRpcUrl(chainId))
24
- const tokenAddressMap = await getMappingProvider(
25
- chainId,
34
+ const multiChainedTokens: TokenInfo[] = []
35
+ const chainIdToMappingsMap = await generateTokenMappings(
36
+ l2ChainIds,
26
37
  l1TokenList
27
- ).provide()
28
- const mappedTokens = []
29
- for (const rootToken of l1TokenList.tokens) {
30
- const childToken = tokenAddressMap[rootToken.address.toLowerCase()]
31
- const childTokenAddress = childToken
32
- ? ethers.utils.getAddress(
33
- typeof childToken === 'object' ? childToken.child_token : childToken
34
- )
35
- : undefined
36
- const childTokenValid = Boolean(
37
- childTokenAddress &&
38
- (typeof childToken === 'object' ? !childToken.deleted : true) &&
39
- (await hasExistingTokenContract(childTokenAddress, chainId))
40
- )
41
-
42
- if (rootToken.chainId === ChainId.MAINNET) {
43
- // build extension info if available
44
- const toRootExtensions = childTokenValid
45
- ? {
46
- extensions: {
47
- bridgeInfo: {
48
- [rootToken.chainId]: {
49
- tokenAddress: ethers.utils.getAddress(rootToken.address),
50
- },
51
- },
52
- },
53
- }
54
- : {}
55
-
56
- const toChildExtensions = childTokenValid
57
- ? {
58
- extensions: {
59
- bridgeInfo: {
60
- [chainId]: {
61
- tokenAddress: childTokenAddress,
62
- },
63
- },
64
- },
38
+ )
39
+ for (const l1Token of l1TokenList.tokens) {
40
+ if (l1Token.chainId === ChainId.MAINNET) {
41
+ const chainIdToChildTokenDetailsMap: {
42
+ [key: number]: {
43
+ childTokenValid: boolean
44
+ childTokenAddress: string | undefined
45
+ }
46
+ } = {}
47
+ const l2MappingExtension = {
48
+ extensions: {
49
+ bridgeInfo: {},
50
+ },
51
+ }
52
+ // build out the extensions.bridgeInfo data containing mappings for each L2 chain
53
+ await Promise.all(
54
+ l2ChainIds.map(async (chainId) => {
55
+ chainIdToChildTokenDetailsMap[chainId] = await getChildTokenDetails(
56
+ l1Token,
57
+ chainId,
58
+ chainIdToMappingsMap
59
+ )
60
+ if (chainIdToChildTokenDetailsMap[chainId].childTokenValid) {
61
+ l2MappingExtension.extensions.bridgeInfo[chainId] = {
62
+ tokenAddress:
63
+ chainIdToChildTokenDetailsMap[chainId].childTokenAddress,
64
+ }
65
65
  }
66
- : {}
66
+ })
67
+ )
67
68
 
68
- const rootTokenInfo: TokenInfo = {
69
- ...rootToken,
70
- ...toChildExtensions,
71
- } as unknown as TokenInfo
69
+ // build the TokenInfo objects with bridgeInfo extension
70
+ l2ChainIds.concat([ChainId.MAINNET]).forEach((chainId) => {
71
+ if (
72
+ chainId === ChainId.MAINNET ||
73
+ chainIdToChildTokenDetailsMap[chainId].childTokenValid
74
+ ) {
75
+ const tokenInfo: TokenInfo =
76
+ chainId === ChainId.MAINNET
77
+ ? ({
78
+ ...l1Token,
79
+ extensions:
80
+ Object.keys(l2MappingExtension.extensions.bridgeInfo)
81
+ .length > 0
82
+ ? {
83
+ bridgeInfo: l2MappingExtension.extensions.bridgeInfo,
84
+ }
85
+ : undefined,
86
+ } as unknown as TokenInfo)
87
+ : ({
88
+ ...l1Token,
89
+ chainId: chainId,
90
+ address:
91
+ chainIdToChildTokenDetailsMap[chainId].childTokenAddress,
92
+ extensions: {
93
+ bridgeInfo: {
94
+ [ChainId.MAINNET]: {
95
+ tokenAddress: ethers.utils.getAddress(l1Token.address),
96
+ },
97
+ },
98
+ },
99
+ } as unknown as TokenInfo)
72
100
 
73
- if (childTokenValid) {
74
- const childTokenInfo: TokenInfo = {
75
- ...rootToken,
76
- chainId: chainId,
77
- address: childTokenAddress,
78
- ...toRootExtensions,
79
- } as unknown as TokenInfo
80
- mappedTokens.push(childTokenInfo)
81
- }
82
- mappedTokens.push(rootTokenInfo)
101
+ multiChainedTokens.push(tokenInfo)
102
+ }
103
+ })
83
104
  }
84
105
  }
106
+
107
+ // build and return final chainified token list
85
108
  const tokenList = {
86
- name: `(ChainId: ${chainId}) ${l1TokenList.name}`,
109
+ name: `(ChainIds: ${l2ChainIds}) ${l1TokenList.name}`,
87
110
  timestamp: new Date().toISOString(),
88
111
  version: l1TokenList.version,
89
- tokens: mappedTokens.sort(compareTokenInfos),
112
+ tokens: multiChainedTokens.sort(compareTokenInfos),
90
113
  }
91
114
 
92
115
  return tokenList
@@ -94,12 +117,14 @@ export async function buildList(
94
117
 
95
118
  // using a symbol lookup contract call to check whether the token exists on the L2
96
119
  async function hasExistingTokenContract(address: string, chainId: ChainId) {
120
+ web3.setProvider(getRpcUrl(chainId))
97
121
  try {
98
122
  const contract: Contract = new web3.eth.Contract(ERC20Abi, address)
99
123
  await getTokenSymbolFromContract(contract)
100
124
  } catch {
101
125
  return false
102
126
  }
127
+
103
128
  return true
104
129
  }
105
130
 
@@ -112,6 +137,65 @@ function getMappingProvider(chainId: ChainId, l1TokenList: TokenList) {
112
137
  case ChainId.POLYGON:
113
138
  return new PolygonMappingProvider()
114
139
  default:
115
- throw new Error(`Chain ${chainId} not supported.`)
140
+ throw new Error(`Chain ${chainId} not supported for fetching mappings.`)
141
+ }
142
+ }
143
+
144
+ async function generateTokenMappings(
145
+ chainIds: ChainId[],
146
+ l1TokenList: TokenList
147
+ ) {
148
+ const chainIdToMappingsMap: {
149
+ [key: number]: PolygonMappedTokenData | GenericMappedTokenData
150
+ } = {}
151
+
152
+ for (const chainId of chainIds) {
153
+ if (SUPPORTED_L2_CHAINS.includes(chainId)) {
154
+ chainIdToMappingsMap[chainId] = await getMappingProvider(
155
+ chainId,
156
+ l1TokenList
157
+ ).provide()
158
+ }
159
+ }
160
+
161
+ return chainIdToMappingsMap
162
+ }
163
+
164
+ // handles both string and object cases for childToken (Polygon mappings return object)
165
+ async function getChildTokenDetails(
166
+ l1Token: TokenInfo,
167
+ chainId: ChainId,
168
+ chainIdToMappingsMap: {
169
+ [key: number]: PolygonMappedTokenData | GenericMappedTokenData
170
+ }
171
+ ): Promise<{
172
+ childTokenValid: boolean
173
+ childTokenAddress: string | undefined
174
+ }> {
175
+ const existingMapping: undefined | string =
176
+ l1Token?.extensions?.bridgeInfo?.[chainId]?.tokenAddress
177
+ // use the externally fetched mappings if manual entry doesn't exist for the token/chain mapping
178
+ // and the given L2 chain is supported for fetching mappings
179
+ if (SUPPORTED_L2_CHAINS.includes(chainId) && existingMapping === undefined) {
180
+ const childToken =
181
+ chainIdToMappingsMap[chainId][l1Token.address.toLowerCase()]
182
+
183
+ const childTokenAddress = childToken
184
+ ? ethers.utils.getAddress(
185
+ typeof childToken === 'object' ? childToken.childToken : childToken
186
+ )
187
+ : undefined
188
+ const childTokenValid = Boolean(
189
+ childTokenAddress &&
190
+ (await hasExistingTokenContract(childTokenAddress, chainId))
191
+ )
192
+ return {
193
+ childTokenValid: childTokenValid,
194
+ childTokenAddress: childTokenAddress,
195
+ }
196
+ }
197
+ return {
198
+ childTokenValid: !!existingMapping,
199
+ childTokenAddress: existingMapping,
116
200
  }
117
201
  }
package/src/utils.ts CHANGED
@@ -97,7 +97,7 @@ export function getRpcUrl(chainId: ChainId): string {
97
97
  case ChainId.MAINNET:
98
98
  return 'https://cloudflare-eth.com/'
99
99
  case ChainId.OPTIMISM:
100
- return 'https://mainnet.optimism.io'
100
+ return 'https://rpc.ankr.com/optimism' // seems to have higher rate limit than https://mainnet.optimism.io/
101
101
  case ChainId.OPTIMISTIC_KOVAN:
102
102
  return 'https://kovan.optimism.io'
103
103
  case ChainId.ARBITRUM_ONE: