@uniswap/token-list-bridge-utils 2.0.0 → 2.2.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.
@@ -3,6 +3,14 @@ import { providers } from 'ethers'
3
3
 
4
4
  const networkID = 42161
5
5
 
6
+ const l2Rpc = (chainId: number) => {
7
+ if (chainId === 42161) return 'https://arb1.arbitrum.io/rpc'
8
+ else if (chainId === 421611) return 'https://rinkeby.arbitrum.io/rpc'
9
+ else if (chainId === 42170) return 'https://nova.arbitrum.io/rpc'
10
+ else if (chainId === 421613) return 'https://goerli-rollup.arbitrum.io/rpc'
11
+ throw new Error('No L2 RPC detected')
12
+ }
13
+
6
14
  // ref: https://github.com/OffchainLabs/arb-token-lists/blob/master/src/lib/instantiate_bridge.ts
7
15
  export const getNetworkConfig = async () => {
8
16
  const { getL1Network, getL2Network, MultiCaller } = await import(
@@ -11,8 +19,8 @@ export const getNetworkConfig = async () => {
11
19
  const l2Network = await getL2Network(networkID)
12
20
  const l1Network = await getL1Network(l2Network.partnerChainID)
13
21
 
14
- const arbProvider = new providers.JsonRpcProvider(l2Network.rpcURL)
15
- const ethProvider = new providers.JsonRpcProvider(l1Network.rpcURL)
22
+ const arbProvider = new providers.JsonRpcProvider(l2Rpc(l2Network.chainID))
23
+ const ethProvider = new providers.JsonRpcProvider(process.env.MAINNET_RPC)
16
24
 
17
25
  const l1MultiCaller = await MultiCaller.fromProvider(ethProvider)
18
26
  const l2MultiCaller = await MultiCaller.fromProvider(arbProvider)
@@ -15,4 +15,7 @@ export enum ChainId {
15
15
  BNB = 56,
16
16
 
17
17
  AVALANCHE = 43114,
18
+
19
+ BASE = 8453,
20
+ BASE_GOERLI = 84531,
18
21
  }
@@ -1,6 +1,46 @@
1
1
  import { Token } from '@uniswap/sdk-core'
2
2
  import { ChainId } from './chainId'
3
3
 
4
+ export const COINBASE_WRAPPED_STAKED_ETH = new Token(
5
+ ChainId.MAINNET,
6
+ '0xbe9895146f7af43049ca1c1ae358b0541ea49704',
7
+ 18,
8
+ 'cbETH',
9
+ 'Coinbase Wrapped Staked ETH'
10
+ )
11
+
12
+ export const COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI = new Token(
13
+ ChainId.BASE_GOERLI,
14
+ '0x4fC531f8Ae7A7808E0dccCA08F1e3c7694582950',
15
+ 18,
16
+ 'cbETH',
17
+ 'Coinbase Wrapped Staked ETH'
18
+ )
19
+
20
+ export const COINBASE_WRAPPED_STAKED_ETH_BASE = new Token(
21
+ ChainId.BASE,
22
+ '0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22',
23
+ 18,
24
+ 'cbETH',
25
+ 'Coinbase Wrapped Staked ETH'
26
+ )
27
+
28
+ export const COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE = new Token(
29
+ ChainId.ARBITRUM_ONE,
30
+ '0x1DEBd73E752bEaF79865Fd6446b0c970EaE7732f',
31
+ 18,
32
+ 'cbETH',
33
+ 'Coinbase Wrapped Staked ETH'
34
+ )
35
+
36
+ export const COINBASE_WRAPPED_STAKED_ETH_OPTIMISM = new Token(
37
+ ChainId.OPTIMISM,
38
+ '0xadDb6A0412DE1BA0F936DCaeb8Aaa24578dcF3B2',
39
+ 18,
40
+ 'cbETH',
41
+ 'Coinbase Wrapped Staked ETH'
42
+ )
43
+
4
44
  export const DAI = new Token(
5
45
  ChainId.MAINNET,
6
46
  '0x6B175474E89094C44Da98b954EedeAC495271d0F',
@@ -46,6 +86,22 @@ export const DAI_AVALANCHE = new Token(
46
86
  'DAI.e'
47
87
  )
48
88
 
89
+ export const DAI_BASE = new Token(
90
+ ChainId.BASE,
91
+ '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
92
+ 18,
93
+ 'DAI',
94
+ 'Dai Stablecoin'
95
+ )
96
+
97
+ export const DAI_BASE_GOERLI = new Token(
98
+ ChainId.BASE_GOERLI,
99
+ '0x174956bDfbCEb6e53089297cce4fE2825E58d92C',
100
+ 18,
101
+ 'DAI',
102
+ 'Dai Stablecoin'
103
+ )
104
+
49
105
  export const USDT = new Token(
50
106
  ChainId.MAINNET,
51
107
  '0xdAC17F958D2ee523a2206206994597C13D831ec7',
@@ -10,6 +10,13 @@ import {
10
10
  DAI_POLYGON,
11
11
  USDT,
12
12
  USDT_BNB,
13
+ COINBASE_WRAPPED_STAKED_ETH,
14
+ COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI,
15
+ COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE,
16
+ COINBASE_WRAPPED_STAKED_ETH_BASE,
17
+ DAI_BASE_GOERLI,
18
+ COINBASE_WRAPPED_STAKED_ETH_OPTIMISM,
19
+ DAI_BASE,
13
20
  } from '../constants/tokens'
14
21
  import { compareTokenInfos } from '../utils'
15
22
 
@@ -17,15 +24,22 @@ export const Tokens: Partial<Record<ChainId, Record<string, TokenInfo>>> = {
17
24
  [ChainId.MAINNET]: {
18
25
  DAI: tokenToTokenInfo(DAI),
19
26
  USDT: tokenToTokenInfo(USDT),
27
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(COINBASE_WRAPPED_STAKED_ETH),
20
28
  },
21
29
  [ChainId.ARBITRUM_ONE]: {
22
30
  DAI: tokenToTokenInfo(DAI_ARBITRUM_ONE),
31
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
32
+ COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE
33
+ ),
23
34
  },
24
35
  [ChainId.POLYGON]: {
25
36
  DAI: tokenToTokenInfo(DAI_POLYGON),
26
37
  },
27
38
  [ChainId.OPTIMISM]: {
28
39
  DAI: tokenToTokenInfo(DAI_OPTIMISM),
40
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
41
+ COINBASE_WRAPPED_STAKED_ETH_OPTIMISM
42
+ ),
29
43
  },
30
44
  [ChainId.BNB]: {
31
45
  DAI: tokenToTokenInfo(DAI_BNB),
@@ -34,6 +48,18 @@ export const Tokens: Partial<Record<ChainId, Record<string, TokenInfo>>> = {
34
48
  [ChainId.AVALANCHE]: {
35
49
  DAI: tokenToTokenInfo(DAI_AVALANCHE),
36
50
  },
51
+ [ChainId.BASE]: {
52
+ DAI: tokenToTokenInfo(DAI_BASE),
53
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
54
+ COINBASE_WRAPPED_STAKED_ETH_BASE
55
+ ),
56
+ },
57
+ [ChainId.BASE_GOERLI]: {
58
+ DAI: tokenToTokenInfo(DAI_BASE_GOERLI),
59
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
60
+ COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI
61
+ ),
62
+ },
37
63
  }
38
64
 
39
65
  export const sampleL1TokenList: TokenList = {
@@ -58,6 +84,17 @@ export const sampleL1TokenList_2: TokenList = {
58
84
  tokens: [Tokens[ChainId.MAINNET]!.USDT],
59
85
  }
60
86
 
87
+ export const sampleL1TokenList_3: TokenList = {
88
+ name: 'Sample_3',
89
+ timestamp: new Date(1646146610).toISOString(),
90
+ version: {
91
+ major: 1,
92
+ minor: 0,
93
+ patch: 0,
94
+ },
95
+ tokens: [Tokens[ChainId.MAINNET]!.COINBASE_WRAPPED_STAKED_ETH],
96
+ }
97
+
61
98
  export const arbBridgeL2Address = '0x467194771dae2967aef3ecbedd3bf9a310c76c65'
62
99
  export const arbBridgeL1Address = '0xd3b5b60020504bc3489d6949d545893982ba3011'
63
100
 
@@ -199,6 +236,62 @@ export const bnbedSampleTokenList_2 = {
199
236
  ].sort(compareTokenInfos),
200
237
  }
201
238
 
239
+ export const baseGoerliSampleTokenList_3 = {
240
+ ...sampleL1TokenList_3,
241
+ name: 'Base Goerli Sample_3',
242
+ tokens: [
243
+ {
244
+ ...Tokens[ChainId.BASE_GOERLI]!.COINBASE_WRAPPED_STAKED_ETH,
245
+ extensions: {
246
+ bridgeInfo: {
247
+ [ChainId.MAINNET]: {
248
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
249
+ },
250
+ },
251
+ },
252
+ } as unknown as TokenInfo,
253
+ {
254
+ ...(Tokens[ChainId.MAINNET]!
255
+ .COINBASE_WRAPPED_STAKED_ETH as unknown as TokenInfo),
256
+ extensions: {
257
+ bridgeInfo: {
258
+ [ChainId.BASE_GOERLI]: {
259
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI.address,
260
+ },
261
+ },
262
+ },
263
+ } as unknown as TokenInfo,
264
+ ].sort(compareTokenInfos),
265
+ }
266
+
267
+ export const baseSampleTokenList_3 = {
268
+ ...sampleL1TokenList_3,
269
+ name: 'Base Sample_3',
270
+ tokens: [
271
+ {
272
+ ...Tokens[ChainId.BASE]!.COINBASE_WRAPPED_STAKED_ETH,
273
+ extensions: {
274
+ bridgeInfo: {
275
+ [ChainId.MAINNET]: {
276
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
277
+ },
278
+ },
279
+ },
280
+ } as unknown as TokenInfo,
281
+ {
282
+ ...(Tokens[ChainId.MAINNET]!
283
+ .COINBASE_WRAPPED_STAKED_ETH as unknown as TokenInfo),
284
+ extensions: {
285
+ bridgeInfo: {
286
+ [ChainId.BASE]: {
287
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_BASE.address,
288
+ },
289
+ },
290
+ },
291
+ } as unknown as TokenInfo,
292
+ ].sort(compareTokenInfos),
293
+ }
294
+
202
295
  export const optimizedSampleTokenList = {
203
296
  ...sampleL1TokenList,
204
297
  name: 'Optimized Sample',
package/src/index.test.ts CHANGED
@@ -2,9 +2,16 @@ import { TokenInfo } from '@uniswap/token-lists'
2
2
  import { chainify, chainifyTokenList, mergeTokenLists } from '.'
3
3
  import { ChainId } from './constants/chainId'
4
4
  import {
5
+ COINBASE_WRAPPED_STAKED_ETH,
6
+ COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE,
7
+ COINBASE_WRAPPED_STAKED_ETH_BASE,
8
+ COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI,
9
+ COINBASE_WRAPPED_STAKED_ETH_OPTIMISM,
5
10
  DAI,
6
11
  DAI_ARBITRUM_ONE,
7
12
  DAI_AVALANCHE,
13
+ DAI_BASE,
14
+ DAI_BASE_GOERLI,
8
15
  DAI_BNB,
9
16
  DAI_OPTIMISM,
10
17
  DAI_POLYGON,
@@ -19,6 +26,9 @@ import {
19
26
  sampleL1TokenList_2,
20
27
  bnbedSampleTokenList_2,
21
28
  avalanchedSampleTokenList,
29
+ sampleL1TokenList_3,
30
+ baseGoerliSampleTokenList_3,
31
+ baseSampleTokenList_3,
22
32
  } from './fixtures'
23
33
 
24
34
  jest.setTimeout(15000)
@@ -136,6 +146,41 @@ it('outputs bnb list correctly with different decimals', async () => {
136
146
  )
137
147
  })
138
148
 
149
+ it('outputs base goerli list correctly', async () => {
150
+ const tokenList = await chainifyTokenList(
151
+ [ChainId.BASE_GOERLI],
152
+ sampleL1TokenList_3
153
+ )
154
+ expect(tokenList).toBeDefined()
155
+ expect(tokenList?.version).toEqual(baseGoerliSampleTokenList_3.version)
156
+ expect(
157
+ tokenList?.tokens.map((t) => [t.address, t.chainId, t.extensions])
158
+ ).toEqual(
159
+ // ignores other metadata
160
+ baseGoerliSampleTokenList_3.tokens.map((t) => [
161
+ t.address,
162
+ t.chainId,
163
+ t.extensions,
164
+ ])
165
+ )
166
+ })
167
+
168
+ it('outputs base list correctly', async () => {
169
+ const tokenList = await chainifyTokenList([ChainId.BASE], sampleL1TokenList_3)
170
+ expect(tokenList).toBeDefined()
171
+ expect(tokenList?.version).toEqual(baseSampleTokenList_3.version)
172
+ expect(
173
+ tokenList?.tokens.map((t) => [t.address, t.chainId, t.extensions])
174
+ ).toEqual(
175
+ // ignores other metadata
176
+ baseSampleTokenList_3.tokens.map((t) => [
177
+ t.address,
178
+ t.chainId,
179
+ t.extensions,
180
+ ])
181
+ )
182
+ })
183
+
139
184
  describe(mergeTokenLists, () => {
140
185
  it('correctly deduplicates', () => {
141
186
  const merged = mergeTokenLists(sampleL1TokenList, sampleL1TokenList)
@@ -256,6 +301,12 @@ describe(chainify, () => {
256
301
  // destBridgeAddress: arbBridgeL2Address,
257
302
  // originBridgeAddress: arbBridgeL1Address,
258
303
  },
304
+ [ChainId.BASE]: {
305
+ tokenAddress: DAI_BASE.address,
306
+ },
307
+ [ChainId.BASE_GOERLI]: {
308
+ tokenAddress: DAI_BASE_GOERLI.address,
309
+ },
259
310
  },
260
311
  },
261
312
  },
@@ -290,6 +341,16 @@ describe(chainify, () => {
290
341
  },
291
342
  },
292
343
  },
344
+ {
345
+ ...Tokens[ChainId.BASE]!.DAI,
346
+ extensions: {
347
+ bridgeInfo: {
348
+ [ChainId.MAINNET]: {
349
+ tokenAddress: DAI.address,
350
+ },
351
+ },
352
+ },
353
+ },
293
354
  {
294
355
  ...Tokens[ChainId.ARBITRUM_ONE]!.DAI,
295
356
  extensions: {
@@ -315,6 +376,86 @@ describe(chainify, () => {
315
376
  },
316
377
  },
317
378
  },
379
+ {
380
+ ...Tokens[ChainId.BASE_GOERLI]!.DAI,
381
+ extensions: {
382
+ bridgeInfo: {
383
+ [ChainId.MAINNET]: {
384
+ tokenAddress: DAI.address,
385
+ },
386
+ },
387
+ },
388
+ },
389
+ ])
390
+ })
391
+
392
+ it('provides bridge extensions for base list', async () => {
393
+ const chainified = await chainify(sampleL1TokenList_3)
394
+
395
+ expect(chainified.tokens).toEqual([
396
+ {
397
+ ...Tokens[ChainId.MAINNET]!.COINBASE_WRAPPED_STAKED_ETH,
398
+ extensions: {
399
+ bridgeInfo: {
400
+ [ChainId.OPTIMISM]: {
401
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_OPTIMISM.address,
402
+ },
403
+ [ChainId.BASE]: {
404
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_BASE.address,
405
+ },
406
+ [ChainId.BASE_GOERLI]: {
407
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI.address,
408
+ },
409
+ [ChainId.ARBITRUM_ONE]: {
410
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE.address,
411
+ },
412
+ },
413
+ },
414
+ },
415
+ {
416
+ ...Tokens[ChainId.OPTIMISM]!.COINBASE_WRAPPED_STAKED_ETH,
417
+ name: 'Coinbase Wrapped Staked ETH',
418
+ extensions: {
419
+ bridgeInfo: {
420
+ [ChainId.MAINNET]: {
421
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
422
+ },
423
+ },
424
+ },
425
+ },
426
+ {
427
+ ...Tokens[ChainId.BASE]!.COINBASE_WRAPPED_STAKED_ETH,
428
+ name: 'Coinbase Wrapped Staked ETH',
429
+ extensions: {
430
+ bridgeInfo: {
431
+ [ChainId.MAINNET]: {
432
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
433
+ },
434
+ },
435
+ },
436
+ },
437
+ {
438
+ ...Tokens[ChainId.ARBITRUM_ONE]!.COINBASE_WRAPPED_STAKED_ETH,
439
+ name: 'Coinbase Wrapped Staked ETH',
440
+ extensions: {
441
+ bridgeInfo: {
442
+ [ChainId.MAINNET]: {
443
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
444
+ },
445
+ },
446
+ },
447
+ },
448
+ {
449
+ ...Tokens[ChainId.BASE_GOERLI]!.COINBASE_WRAPPED_STAKED_ETH,
450
+ name: 'Coinbase Wrapped Staked ETH',
451
+ extensions: {
452
+ bridgeInfo: {
453
+ [ChainId.MAINNET]: {
454
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
455
+ },
456
+ },
457
+ },
458
+ },
318
459
  ])
319
460
  })
320
461
  })
package/src/index.ts CHANGED
@@ -25,13 +25,17 @@ export async function chainify(
25
25
  ChainId.CELO,
26
26
  ChainId.BNB,
27
27
  ChainId.AVALANCHE,
28
+ ChainId.BASE,
29
+ ChainId.BASE_GOERLI,
28
30
  ]
29
31
 
30
32
  const chainified = await chainifyTokenList(l2Chains, l1TokenListOrPathOrUrl)
31
- return mergeTokenLists(
33
+ const merged = mergeTokenLists(
32
34
  l1TokenList, // providing l1 first to make sure duplicated tokens resolve to this list
33
35
  chainified
34
36
  )
37
+
38
+ return merged
35
39
  }
36
40
 
37
41
  /**
@@ -72,7 +76,10 @@ export function mergeTokenLists(
72
76
  }
73
77
 
74
78
  const merged = merge(group[0], group[1])
75
- if (merged.extensions?.bridgeInfo) {
79
+ if (
80
+ merged.extensions?.bridgeInfo &&
81
+ typeof merged.extensions.bridgeInfo === 'object'
82
+ ) {
76
83
  // remove reference to self-chain from merge
77
84
  delete merged.extensions.bridgeInfo[merged.chainId]
78
85
  }
@@ -0,0 +1,43 @@
1
+ import { MappingProvider } from './MappingProvider'
2
+ import { ChainId } from '../constants/chainId'
3
+ import { getTokenList } from '../utils'
4
+ import { GenericMappedTokenData } from '../constants/types'
5
+
6
+ const baseGoerliTokenListURL =
7
+ 'https://raw.githubusercontent.com/' +
8
+ 'ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'
9
+
10
+ /**
11
+ * The Base Goerli mapping (linked above) is manually maintained by the Coinbase team
12
+ * in this repository: https://github.com/ethereum-optimism/ethereum-optimism.github.io.
13
+ */
14
+ export class BaseGoerliMappingProvider implements MappingProvider {
15
+ async provide(): Promise<GenericMappedTokenData> {
16
+ const tokens: { [key: string]: string | undefined } = {}
17
+
18
+ let allTokens = await getTokenList(baseGoerliTokenListURL)
19
+
20
+ let opTokenId_baseGoerliAddressMap: Record<string, string> = {}
21
+ allTokens.tokens.forEach((token) => {
22
+ if (token.chainId === ChainId.BASE_GOERLI) {
23
+ if (typeof token.extensions?.opTokenId === 'string') {
24
+ opTokenId_baseGoerliAddressMap[token.extensions.opTokenId] =
25
+ token.address
26
+ }
27
+ }
28
+ })
29
+
30
+ allTokens.tokens.forEach((token) => {
31
+ if (
32
+ token.chainId === ChainId.MAINNET &&
33
+ typeof token.extensions?.opTokenId === 'string' &&
34
+ token.extensions!.opTokenId in opTokenId_baseGoerliAddressMap
35
+ ) {
36
+ tokens[token.address.toLowerCase()] =
37
+ opTokenId_baseGoerliAddressMap[token.extensions!.opTokenId]
38
+ }
39
+ })
40
+
41
+ return tokens
42
+ }
43
+ }
@@ -0,0 +1,42 @@
1
+ import { MappingProvider } from './MappingProvider'
2
+ import { ChainId } from '../constants/chainId'
3
+ import { getTokenList } from '../utils'
4
+ import { GenericMappedTokenData } from '../constants/types'
5
+
6
+ const baseGoerliTokenListURL =
7
+ 'https://raw.githubusercontent.com/' +
8
+ 'ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'
9
+
10
+ /**
11
+ * The Base mapping (linked above) is manually maintained by the Coinbase team
12
+ * in this repository: https://github.com/ethereum-optimism/ethereum-optimism.github.io.
13
+ */
14
+ export class BaseMappingProvider implements MappingProvider {
15
+ async provide(): Promise<GenericMappedTokenData> {
16
+ const tokens: { [key: string]: string | undefined } = {}
17
+
18
+ let allTokens = await getTokenList(baseGoerliTokenListURL)
19
+
20
+ let opTokenId_baseAddressMap: Record<string, string> = {}
21
+ allTokens.tokens.forEach((token) => {
22
+ if (token.chainId === ChainId.BASE) {
23
+ if (typeof token.extensions?.opTokenId === 'string') {
24
+ opTokenId_baseAddressMap[token.extensions.opTokenId] = token.address
25
+ }
26
+ }
27
+ })
28
+
29
+ allTokens.tokens.forEach((token) => {
30
+ if (
31
+ token.chainId === ChainId.MAINNET &&
32
+ typeof token.extensions?.opTokenId === 'string' &&
33
+ token.extensions!.opTokenId in opTokenId_baseAddressMap
34
+ ) {
35
+ tokens[token.address.toLowerCase()] =
36
+ opTokenId_baseAddressMap[token.extensions!.opTokenId]
37
+ }
38
+ })
39
+
40
+ return tokens
41
+ }
42
+ }
@@ -5,7 +5,7 @@ import { GenericMappedTokenData } from '../constants/types'
5
5
 
6
6
  const optimismTokenListURL =
7
7
  'https://raw.githubusercontent.com/' +
8
- 'ethereum-optimism/ethereum-optimism.github.io/2138386277e4156d159615d1840882cecc398437/optimism.tokenlist.json'
8
+ 'ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'
9
9
 
10
10
  /**
11
11
  * The Optimism L2 mapping (linked above) is manually maintained by the Optimism team.
@@ -16,14 +16,27 @@ export class OptimismMappingProvider implements MappingProvider {
16
16
  async provide(): Promise<GenericMappedTokenData> {
17
17
  const tokens: { [key: string]: string | undefined } = {}
18
18
 
19
- let optimismTokens = await getTokenList(optimismTokenListURL)
19
+ let allTokens = await getTokenList(optimismTokenListURL)
20
20
 
21
- for (const token of optimismTokens.tokens) {
22
- if (token.chainId === ChainId.MAINNET) {
21
+ let opTokenId_baseAddressMap: Record<string, string> = {}
22
+ allTokens.tokens.forEach((token) => {
23
+ if (token.chainId === ChainId.OPTIMISM) {
24
+ if (typeof token.extensions?.opTokenId === 'string') {
25
+ opTokenId_baseAddressMap[token.extensions.opTokenId] = token.address
26
+ }
27
+ }
28
+ })
29
+
30
+ allTokens.tokens.forEach((token) => {
31
+ if (
32
+ token.chainId === ChainId.MAINNET &&
33
+ typeof token.extensions?.opTokenId === 'string' &&
34
+ token.extensions!.opTokenId in opTokenId_baseAddressMap
35
+ ) {
23
36
  tokens[token.address.toLowerCase()] =
24
- token?.extensions?.bridgeInfo![ChainId.OPTIMISM].tokenAddress
37
+ opTokenId_baseAddressMap[token.extensions!.opTokenId]
25
38
  }
26
- }
39
+ })
27
40
 
28
41
  return tokens
29
42
  }
@@ -21,6 +21,8 @@ import {
21
21
  MappedToken,
22
22
  } from '../constants/types'
23
23
  import { AvalancheMappingProvider } from './AvalancheMappingProvider'
24
+ import { BaseGoerliMappingProvider } from './BaseGoerliMappingProvider'
25
+ import { BaseMappingProvider } from './BaseMappingProvider'
24
26
 
25
27
  const web3 = new Web3()
26
28
 
@@ -31,6 +33,8 @@ const CHAINS_WITH_MAPPING_PROVIDERS = [
31
33
  ChainId.OPTIMISM,
32
34
  ChainId.BNB,
33
35
  ChainId.AVALANCHE,
36
+ ChainId.BASE,
37
+ ChainId.BASE_GOERLI,
34
38
  ]
35
39
 
36
40
  export async function buildList(
@@ -53,7 +57,7 @@ export async function buildList(
53
57
  } = {}
54
58
  const l2MappingExtension = {
55
59
  extensions: {
56
- bridgeInfo: {},
60
+ bridgeInfo: {} as any,
57
61
  },
58
62
  }
59
63
  // build out the extensions.bridgeInfo data containing mappings for each L2 chain
@@ -131,7 +135,7 @@ async function hasExistingTokenContract(address: string, chainId: ChainId) {
131
135
  try {
132
136
  const contract: Contract = new web3.eth.Contract(ERC20Abi, address)
133
137
  await getTokenSymbolFromContract(contract)
134
- } catch {
138
+ } catch (e) {
135
139
  return false
136
140
  }
137
141
 
@@ -150,6 +154,10 @@ function getMappingProvider(chainId: ChainId, l1TokenList: TokenList) {
150
154
  return new BnbMappingProvider()
151
155
  case ChainId.AVALANCHE:
152
156
  return new AvalancheMappingProvider()
157
+ case ChainId.BASE:
158
+ return new BaseMappingProvider()
159
+ case ChainId.BASE_GOERLI:
160
+ return new BaseGoerliMappingProvider()
153
161
  default:
154
162
  throw new Error(`Chain ${chainId} not supported for fetching mappings.`)
155
163
  }
@@ -193,8 +201,14 @@ async function getChildTokenDetails(
193
201
  childTokenAddress: string | undefined
194
202
  decimals?: number | undefined
195
203
  }> {
196
- const existingMapping: undefined | string =
197
- l1Token?.extensions?.bridgeInfo?.[chainId]?.tokenAddress
204
+ const bridgeInfo = l1Token?.extensions?.bridgeInfo
205
+ let existingMapping: undefined | string
206
+ if (bridgeInfo && typeof bridgeInfo === 'object') {
207
+ const bridgeInfoForChain = bridgeInfo[chainId]
208
+ if (bridgeInfoForChain && typeof bridgeInfoForChain === 'object') {
209
+ existingMapping = bridgeInfoForChain.tokenAddress as string
210
+ }
211
+ }
198
212
  // use the externally fetched mappings if manual entry doesn't exist for the token/chain mapping
199
213
  // and the given chain is supported for fetching mappings
200
214
  if (
@@ -217,7 +231,6 @@ async function getChildTokenDetails(
217
231
  childToken && (chainId === ChainId.BNB || chainId === ChainId.AVALANCHE)
218
232
  ? (childToken as MappedToken).decimals
219
233
  : undefined
220
-
221
234
  return {
222
235
  childTokenValid: childTokenValid,
223
236
  childTokenAddress: childTokenAddress,
package/src/utils.ts CHANGED
@@ -111,7 +111,11 @@ export function getRpcUrl(chainId: ChainId): string {
111
111
  case ChainId.BNB:
112
112
  return 'https://bsc-dataseed1.binance.org'
113
113
  case ChainId.AVALANCHE:
114
- return 'https://api.avax.network/ext/bc/C/rpc'
114
+ return 'https://api.avax.network/ext/bc/C/rpc'
115
+ case ChainId.BASE:
116
+ return 'https://mainnet.base.org'
117
+ case ChainId.BASE_GOERLI:
118
+ return 'https://goerli.base.org'
115
119
  default:
116
120
  }
117
121
  throw new Error('Unsupported ChainId')