@uniswap/token-list-bridge-utils 1.0.8 → 1.1.0
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/dist/constants/chainId.d.ts +2 -1
- package/dist/constants/types.d.ts +3 -0
- package/dist/index.d.ts +2 -2
- package/dist/providers/ArbitrumMappingProvider.d.ts +2 -3
- package/dist/providers/MappingProvider.d.ts +2 -4
- package/dist/providers/OptimismMappingProvider.d.ts +2 -3
- package/dist/providers/index.d.ts +1 -1
- package/dist/token-list-bridge-utils.cjs.development.js +250 -107
- package/dist/token-list-bridge-utils.cjs.development.js.map +1 -1
- package/dist/token-list-bridge-utils.cjs.production.min.js +1 -1
- package/dist/token-list-bridge-utils.cjs.production.min.js.map +1 -1
- package/dist/token-list-bridge-utils.esm.js +250 -107
- package/dist/token-list-bridge-utils.esm.js.map +1 -1
- package/package.json +2 -1
- package/src/constants/chainId.ts +2 -0
- package/src/constants/types.ts +5 -1
- package/src/index.test.ts +3 -3
- package/src/index.ts +9 -15
- package/src/providers/ArbitrumMappingProvider.ts +2 -1
- package/src/providers/MappingProvider.ts +2 -2
- package/src/providers/OptimismMappingProvider.ts +2 -1
- package/src/providers/index.ts +145 -60
- package/src/utils.ts +1 -1
package/src/providers/index.ts
CHANGED
|
@@ -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
|
-
|
|
31
|
+
l2ChainIds: Array<ChainId>,
|
|
21
32
|
l1TokenList: TokenList
|
|
22
33
|
): Promise<TokenList> {
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
34
|
+
const multiChainedTokens: TokenInfo[] = []
|
|
35
|
+
const chainIdToMappingsMap = await generateTokenMappings(
|
|
36
|
+
l2ChainIds,
|
|
26
37
|
l1TokenList
|
|
27
|
-
)
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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: `(
|
|
109
|
+
name: `(ChainIds: ${l2ChainIds}) ${l1TokenList.name}`,
|
|
87
110
|
timestamp: new Date().toISOString(),
|
|
88
111
|
version: l1TokenList.version,
|
|
89
|
-
tokens:
|
|
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,66 @@ 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.child_token : childToken
|
|
186
|
+
)
|
|
187
|
+
: undefined
|
|
188
|
+
const childTokenValid = Boolean(
|
|
189
|
+
childTokenAddress &&
|
|
190
|
+
(typeof childToken === 'object' ? !childToken.deleted : true) &&
|
|
191
|
+
(await hasExistingTokenContract(childTokenAddress, chainId))
|
|
192
|
+
)
|
|
193
|
+
return {
|
|
194
|
+
childTokenValid: childTokenValid,
|
|
195
|
+
childTokenAddress: childTokenAddress,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
childTokenValid: !!existingMapping,
|
|
200
|
+
childTokenAddress: existingMapping,
|
|
116
201
|
}
|
|
117
202
|
}
|
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:
|