@uniswap/token-list-bridge-utils 2.0.0 → 2.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.
@@ -10,6 +10,9 @@ 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,
13
16
  } from '../constants/tokens'
14
17
  import { compareTokenInfos } from '../utils'
15
18
 
@@ -17,9 +20,13 @@ export const Tokens: Partial<Record<ChainId, Record<string, TokenInfo>>> = {
17
20
  [ChainId.MAINNET]: {
18
21
  DAI: tokenToTokenInfo(DAI),
19
22
  USDT: tokenToTokenInfo(USDT),
23
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(COINBASE_WRAPPED_STAKED_ETH),
20
24
  },
21
25
  [ChainId.ARBITRUM_ONE]: {
22
26
  DAI: tokenToTokenInfo(DAI_ARBITRUM_ONE),
27
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
28
+ COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE
29
+ ),
23
30
  },
24
31
  [ChainId.POLYGON]: {
25
32
  DAI: tokenToTokenInfo(DAI_POLYGON),
@@ -34,6 +41,11 @@ export const Tokens: Partial<Record<ChainId, Record<string, TokenInfo>>> = {
34
41
  [ChainId.AVALANCHE]: {
35
42
  DAI: tokenToTokenInfo(DAI_AVALANCHE),
36
43
  },
44
+ [ChainId.BASE_GOERLI]: {
45
+ COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
46
+ COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI
47
+ ),
48
+ },
37
49
  }
38
50
 
39
51
  export const sampleL1TokenList: TokenList = {
@@ -58,6 +70,17 @@ export const sampleL1TokenList_2: TokenList = {
58
70
  tokens: [Tokens[ChainId.MAINNET]!.USDT],
59
71
  }
60
72
 
73
+ export const sampleL1TokenList_3: TokenList = {
74
+ name: 'Sample_3',
75
+ timestamp: new Date(1646146610).toISOString(),
76
+ version: {
77
+ major: 1,
78
+ minor: 0,
79
+ patch: 0,
80
+ },
81
+ tokens: [Tokens[ChainId.MAINNET]!.COINBASE_WRAPPED_STAKED_ETH],
82
+ }
83
+
61
84
  export const arbBridgeL2Address = '0x467194771dae2967aef3ecbedd3bf9a310c76c65'
62
85
  export const arbBridgeL1Address = '0xd3b5b60020504bc3489d6949d545893982ba3011'
63
86
 
@@ -199,6 +222,34 @@ export const bnbedSampleTokenList_2 = {
199
222
  ].sort(compareTokenInfos),
200
223
  }
201
224
 
225
+ export const baseGoerliSampleTokenList_3 = {
226
+ ...sampleL1TokenList_3,
227
+ name: 'Base Goerli Sample_3',
228
+ tokens: [
229
+ {
230
+ ...Tokens[ChainId.BASE_GOERLI]!.COINBASE_WRAPPED_STAKED_ETH,
231
+ extensions: {
232
+ bridgeInfo: {
233
+ [ChainId.MAINNET]: {
234
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
235
+ },
236
+ },
237
+ },
238
+ } as unknown as TokenInfo,
239
+ {
240
+ ...(Tokens[ChainId.MAINNET]!
241
+ .COINBASE_WRAPPED_STAKED_ETH as unknown as TokenInfo),
242
+ extensions: {
243
+ bridgeInfo: {
244
+ [ChainId.BASE_GOERLI]: {
245
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI.address,
246
+ },
247
+ },
248
+ },
249
+ } as unknown as TokenInfo,
250
+ ].sort(compareTokenInfos),
251
+ }
252
+
202
253
  export const optimizedSampleTokenList = {
203
254
  ...sampleL1TokenList,
204
255
  name: 'Optimized Sample',
package/src/index.test.ts CHANGED
@@ -2,6 +2,9 @@ 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_GOERLI,
5
8
  DAI,
6
9
  DAI_ARBITRUM_ONE,
7
10
  DAI_AVALANCHE,
@@ -19,6 +22,8 @@ import {
19
22
  sampleL1TokenList_2,
20
23
  bnbedSampleTokenList_2,
21
24
  avalanchedSampleTokenList,
25
+ sampleL1TokenList_3,
26
+ baseGoerliSampleTokenList_3,
22
27
  } from './fixtures'
23
28
 
24
29
  jest.setTimeout(15000)
@@ -136,6 +141,25 @@ it('outputs bnb list correctly with different decimals', async () => {
136
141
  )
137
142
  })
138
143
 
144
+ it('outputs base goerli list correctly', async () => {
145
+ const tokenList = await chainifyTokenList(
146
+ [ChainId.BASE_GOERLI],
147
+ sampleL1TokenList_3
148
+ )
149
+ expect(tokenList).toBeDefined()
150
+ expect(tokenList?.version).toEqual(baseGoerliSampleTokenList_3.version)
151
+ expect(
152
+ tokenList?.tokens.map((t) => [t.address, t.chainId, t.extensions])
153
+ ).toEqual(
154
+ // ignores other metadata
155
+ baseGoerliSampleTokenList_3.tokens.map((t) => [
156
+ t.address,
157
+ t.chainId,
158
+ t.extensions,
159
+ ])
160
+ )
161
+ })
162
+
139
163
  describe(mergeTokenLists, () => {
140
164
  it('correctly deduplicates', () => {
141
165
  const merged = mergeTokenLists(sampleL1TokenList, sampleL1TokenList)
@@ -231,6 +255,93 @@ describe(mergeTokenLists, () => {
231
255
  })
232
256
  })
233
257
  describe(chainify, () => {
258
+ it('creates tokenMap correctly in chainify', async () => {
259
+ const chainified = await chainify(sampleL1TokenList)
260
+
261
+ expect(chainified.tokenMap).toEqual({
262
+ '10_0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1': {
263
+ ...Tokens[ChainId.OPTIMISM]!.DAI,
264
+ name: 'Dai Stablecoin',
265
+ extensions: {
266
+ bridgeInfo: {
267
+ [ChainId.MAINNET]: {
268
+ tokenAddress: DAI.address,
269
+ },
270
+ },
271
+ },
272
+ },
273
+ '137_0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063': {
274
+ ...Tokens[ChainId.POLYGON]!.DAI,
275
+ extensions: {
276
+ bridgeInfo: {
277
+ [ChainId.MAINNET]: {
278
+ tokenAddress: DAI.address,
279
+ },
280
+ },
281
+ },
282
+ },
283
+ '1_0x6B175474E89094C44Da98b954EedeAC495271d0F': {
284
+ ...Tokens[ChainId.MAINNET]!.DAI,
285
+ extensions: {
286
+ bridgeInfo: {
287
+ [ChainId.BNB]: {
288
+ tokenAddress: DAI_BNB.address,
289
+ },
290
+ [ChainId.AVALANCHE]: {
291
+ tokenAddress: DAI_AVALANCHE.address,
292
+ },
293
+ [ChainId.OPTIMISM]: {
294
+ tokenAddress: DAI_OPTIMISM.address,
295
+ },
296
+ [ChainId.POLYGON]: {
297
+ tokenAddress: DAI_POLYGON.address,
298
+ },
299
+ [ChainId.ARBITRUM_ONE]: {
300
+ tokenAddress: DAI_ARBITRUM_ONE.address,
301
+ // destBridgeAddress: arbBridgeL2Address,
302
+ // originBridgeAddress: arbBridgeL1Address,
303
+ },
304
+ },
305
+ },
306
+ },
307
+ '42161_0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1': {
308
+ ...Tokens[ChainId.ARBITRUM_ONE]!.DAI,
309
+ extensions: {
310
+ bridgeInfo: {
311
+ [ChainId.MAINNET]: {
312
+ tokenAddress: DAI.address,
313
+ // originBridgeAddress: arbBridgeL2Address,
314
+ // destBridgeAddress: arbBridgeL1Address,
315
+ },
316
+ },
317
+ },
318
+ name: 'Dai Stablecoin',
319
+ },
320
+ '43114_0xd586E7F844cEa2F87f50152665BCbc2C279D8d70': {
321
+ ...Tokens[ChainId.AVALANCHE]!.DAI,
322
+ name: 'Dai Stablecoin',
323
+ symbol: 'DAI',
324
+ extensions: {
325
+ bridgeInfo: {
326
+ [ChainId.MAINNET]: {
327
+ tokenAddress: DAI.address,
328
+ },
329
+ },
330
+ },
331
+ },
332
+ '56_0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3': {
333
+ ...Tokens[ChainId.BNB]!.DAI,
334
+ extensions: {
335
+ bridgeInfo: {
336
+ [ChainId.MAINNET]: {
337
+ tokenAddress: DAI.address,
338
+ },
339
+ },
340
+ },
341
+ },
342
+ })
343
+ })
344
+
234
345
  it('provides bridge extensions', async () => {
235
346
  const chainified = await chainify(sampleL1TokenList)
236
347
 
@@ -317,4 +428,46 @@ describe(chainify, () => {
317
428
  },
318
429
  ])
319
430
  })
431
+
432
+ it('provides bridge extensions for base goerli list', async () => {
433
+ const chainified = await chainify(sampleL1TokenList_3)
434
+
435
+ expect(chainified.tokens).toEqual([
436
+ {
437
+ ...Tokens[ChainId.MAINNET]!.COINBASE_WRAPPED_STAKED_ETH,
438
+ extensions: {
439
+ bridgeInfo: {
440
+ [ChainId.ARBITRUM_ONE]: {
441
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE.address,
442
+ },
443
+ [ChainId.BASE_GOERLI]: {
444
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI.address,
445
+ },
446
+ },
447
+ },
448
+ },
449
+ {
450
+ ...Tokens[ChainId.ARBITRUM_ONE]!.COINBASE_WRAPPED_STAKED_ETH,
451
+ name: 'Coinbase Wrapped Staked ETH',
452
+ extensions: {
453
+ bridgeInfo: {
454
+ [ChainId.MAINNET]: {
455
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
456
+ },
457
+ },
458
+ },
459
+ },
460
+ {
461
+ ...Tokens[ChainId.BASE_GOERLI]!.COINBASE_WRAPPED_STAKED_ETH,
462
+ name: 'Coinbase Wrapped Staked ETH',
463
+ extensions: {
464
+ bridgeInfo: {
465
+ [ChainId.MAINNET]: {
466
+ tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
467
+ },
468
+ },
469
+ },
470
+ },
471
+ ])
472
+ })
320
473
  })
package/src/index.ts CHANGED
@@ -25,13 +25,22 @@ export async function chainify(
25
25
  ChainId.CELO,
26
26
  ChainId.BNB,
27
27
  ChainId.AVALANCHE,
28
+ ChainId.BASE_GOERLI,
28
29
  ]
29
30
 
30
31
  const chainified = await chainifyTokenList(l2Chains, l1TokenListOrPathOrUrl)
31
- return mergeTokenLists(
32
+ const merged = mergeTokenLists(
32
33
  l1TokenList, // providing l1 first to make sure duplicated tokens resolve to this list
33
34
  chainified
34
35
  )
36
+
37
+ const tokenMap = {}
38
+ if (merged.tokens && merged.tokens.length > 0) {
39
+ merged.tokens.forEach((token) => {
40
+ tokenMap[`${token.chainId}_${token.address}`] = token
41
+ })
42
+ }
43
+ return { ...merged, tokenMap }
35
44
  }
36
45
 
37
46
  /**
@@ -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 = {}
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
+ }
@@ -21,6 +21,7 @@ import {
21
21
  MappedToken,
22
22
  } from '../constants/types'
23
23
  import { AvalancheMappingProvider } from './AvalancheMappingProvider'
24
+ import { BaseGoerliMappingProvider } from './BaseGoerliMappingProvider'
24
25
 
25
26
  const web3 = new Web3()
26
27
 
@@ -31,6 +32,7 @@ const CHAINS_WITH_MAPPING_PROVIDERS = [
31
32
  ChainId.OPTIMISM,
32
33
  ChainId.BNB,
33
34
  ChainId.AVALANCHE,
35
+ ChainId.BASE_GOERLI,
34
36
  ]
35
37
 
36
38
  export async function buildList(
@@ -150,6 +152,8 @@ function getMappingProvider(chainId: ChainId, l1TokenList: TokenList) {
150
152
  return new BnbMappingProvider()
151
153
  case ChainId.AVALANCHE:
152
154
  return new AvalancheMappingProvider()
155
+ case ChainId.BASE_GOERLI:
156
+ return new BaseGoerliMappingProvider()
153
157
  default:
154
158
  throw new Error(`Chain ${chainId} not supported for fetching mappings.`)
155
159
  }
package/src/utils.ts CHANGED
@@ -111,7 +111,9 @@ 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_GOERLI:
116
+ return 'https://goerli.base.org'
115
117
  default:
116
118
  }
117
119
  throw new Error('Unsupported ChainId')