Skip to content

Commit

Permalink
feat: add base mainnet support
Browse files Browse the repository at this point in the history
  • Loading branch information
just-toby committed Aug 2, 2023
1 parent 335446c commit 789cb0a
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/constants/chainId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export enum ChainId {

AVALANCHE = 43114,

BASE_GOERLI = 84531,
BASE = 8453,
BASE_GOERLI = 84531
}
26 changes: 25 additions & 1 deletion src/constants/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,23 @@ export const COINBASE_WRAPPED_STAKED_ETH = new Token(

export const COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI = new Token(
ChainId.BASE_GOERLI,
'0x7c6b91D9Be155A6Db01f749217d76fF02A7227F2',
'0x4fC531f8Ae7A7808E0dccCA08F1e3c7694582950',
18,
'cbETH',
'Coinbase Wrapped Staked ETH'
)

export const COINBASE_WRAPPED_STAKED_ETH_BASE = new Token(
ChainId.BASE,
'0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22',
18,
'cbETH',
'Coinbase Wrapped Staked ETH'
)

export const COINBASE_WRAPPED_STAKED_ETH_OPTIMISM = new Token(
ChainId.OPTIMISM,
'0xadDb6A0412DE1BA0F936DCaeb8Aaa24578dcF3B2',
18,
'cbETH',
'Coinbase Wrapped Staked ETH'
Expand Down Expand Up @@ -70,6 +86,14 @@ export const DAI_AVALANCHE = new Token(
'DAI.e'
)

export const DAI_BASE_GOERLI = new Token(
ChainId.BASE_GOERLI,
"0x174956bDfbCEb6e53089297cce4fE2825E58d92C",
18,
'DAI',
'Dai Stablecoin',
)

export const USDT = new Token(
ChainId.MAINNET,
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
Expand Down
36 changes: 36 additions & 0 deletions src/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
COINBASE_WRAPPED_STAKED_ETH,
COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI,
COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE,
COINBASE_WRAPPED_STAKED_ETH_BASE,
DAI_BASE_GOERLI,
COINBASE_WRAPPED_STAKED_ETH_OPTIMISM,
} from '../constants/tokens'
import { compareTokenInfos } from '../utils'

Expand All @@ -33,6 +36,9 @@ export const Tokens: Partial<Record<ChainId, Record<string, TokenInfo>>> = {
},
[ChainId.OPTIMISM]: {
DAI: tokenToTokenInfo(DAI_OPTIMISM),
COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
COINBASE_WRAPPED_STAKED_ETH_OPTIMISM
)
},
[ChainId.BNB]: {
DAI: tokenToTokenInfo(DAI_BNB),
Expand All @@ -41,7 +47,13 @@ export const Tokens: Partial<Record<ChainId, Record<string, TokenInfo>>> = {
[ChainId.AVALANCHE]: {
DAI: tokenToTokenInfo(DAI_AVALANCHE),
},
[ChainId.BASE]: {
COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
COINBASE_WRAPPED_STAKED_ETH_BASE
)
},
[ChainId.BASE_GOERLI]: {
DAI: tokenToTokenInfo(DAI_BASE_GOERLI),
COINBASE_WRAPPED_STAKED_ETH: tokenToTokenInfo(
COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI
),
Expand Down Expand Up @@ -250,6 +262,30 @@ export const baseGoerliSampleTokenList_3 = {
].sort(compareTokenInfos),
}

export const baseSampleTokenList_3 = {
...sampleL1TokenList_3,
name: 'Base Sample_3',
tokens: [
{
...Tokens[ChainId.BASE]!.COINBASE_WRAPPED_STAKED_ETH,
extensions: {
baseBridgeAddress: "0x4200000000000000000000000000000000000010",
opListId: "default",
opTokenId: "cbETH"
}
} as unknown as TokenInfo,
{
...(Tokens[ChainId.MAINNET]!.COINBASE_WRAPPED_STAKED_ETH as unknown as TokenInfo),
extensions: {
optimismBridgeAddress: "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1",
baseBridgeAddress: "0x3154Cf16ccdb4C6d922629664174b904d80F2C35",
opListId: "default",
opTokenId: "cbETH"
}
} as unknown as TokenInfo
].sort(compareTokenInfos)
}

export const optimizedSampleTokenList = {
...sampleL1TokenList,
name: 'Optimized Sample',
Expand Down
67 changes: 66 additions & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { ChainId } from './constants/chainId'
import {
COINBASE_WRAPPED_STAKED_ETH,
COINBASE_WRAPPED_STAKED_ETH_ARBITRUM_ONE,
COINBASE_WRAPPED_STAKED_ETH_BASE,
COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI,
COINBASE_WRAPPED_STAKED_ETH_OPTIMISM,
DAI,
DAI_ARBITRUM_ONE,
DAI_AVALANCHE,
DAI_BASE_GOERLI,
DAI_BNB,
DAI_OPTIMISM,
DAI_POLYGON,
Expand All @@ -24,6 +27,7 @@ import {
avalanchedSampleTokenList,
sampleL1TokenList_3,
baseGoerliSampleTokenList_3,
baseSampleTokenList_3,
} from './fixtures'

jest.setTimeout(15000)
Expand Down Expand Up @@ -160,6 +164,25 @@ it('outputs base goerli list correctly', async () => {
)
})

it('outputs base list correctly', async () => {
const tokenList = await chainifyTokenList(
[ChainId.BASE],
sampleL1TokenList_3
)
expect(tokenList).toBeDefined()
expect(tokenList?.version).toEqual(baseSampleTokenList_3.version)
expect(
tokenList?.tokens.map((t) => [t.address, t.chainId, t.extensions])
).toEqual(
// ignores other metadata
baseSampleTokenList_3.tokens.map((t) => [
t.address,
t.chainId,
t.extensions,
])
)
})

describe(mergeTokenLists, () => {
it('correctly deduplicates', () => {
const merged = mergeTokenLists(sampleL1TokenList, sampleL1TokenList)
Expand Down Expand Up @@ -301,6 +324,9 @@ describe(chainify, () => {
// destBridgeAddress: arbBridgeL2Address,
// originBridgeAddress: arbBridgeL1Address,
},
[ChainId.BASE_GOERLI]: {
tokenAddress: DAI_BASE_GOERLI.address,
}
},
},
},
Expand Down Expand Up @@ -339,6 +365,16 @@ describe(chainify, () => {
},
},
},
'84531_0x174956bDfbCEb6e53089297cce4fE2825E58d92C': {
...Tokens[ChainId.BASE_GOERLI]!.DAI,
extensions: {
bridgeInfo: {
[ChainId.MAINNET]: {
tokenAddress: DAI.address,
}
}
}
}
})
})

Expand Down Expand Up @@ -367,6 +403,9 @@ describe(chainify, () => {
// destBridgeAddress: arbBridgeL2Address,
// originBridgeAddress: arbBridgeL1Address,
},
[ChainId.BASE_GOERLI]: {
tokenAddress: DAI_BASE_GOERLI.address,
}
},
},
},
Expand Down Expand Up @@ -426,10 +465,20 @@ describe(chainify, () => {
},
},
},
{
...Tokens[ChainId.BASE_GOERLI]!.DAI,
extensions: {
bridgeInfo: {
[ChainId.MAINNET]: {
tokenAddress: DAI.address,
}
}
}
}
])
})

it('provides bridge extensions for base goerli list', async () => {
it('provides bridge extensions for base list', async () => {
const chainified = await chainify(sampleL1TokenList_3)

expect(chainified.tokens).toEqual([
Expand All @@ -443,6 +492,21 @@ describe(chainify, () => {
[ChainId.BASE_GOERLI]: {
tokenAddress: COINBASE_WRAPPED_STAKED_ETH_BASE_GOERLI.address,
},
[ChainId.OPTIMISM]: {
tokenAddress: COINBASE_WRAPPED_STAKED_ETH_OPTIMISM.address,
}
// todo: add base mainnet cbETH when chain id is fixed
},
},
},
{
...Tokens[ChainId.OPTIMISM]!.COINBASE_WRAPPED_STAKED_ETH,
name: 'Coinbase Wrapped Staked ETH',
extensions: {
bridgeInfo: {
[ChainId.MAINNET]: {
tokenAddress: COINBASE_WRAPPED_STAKED_ETH.address,
},
},
},
},
Expand All @@ -457,6 +521,7 @@ describe(chainify, () => {
},
},
},
// todo: add base mainnet cbETH when chain id is fixed
{
...Tokens[ChainId.BASE_GOERLI]!.COINBASE_WRAPPED_STAKED_ETH,
name: 'Coinbase Wrapped Staked ETH',
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export async function chainify(
ChainId.CELO,
ChainId.BNB,
ChainId.AVALANCHE,
ChainId.BASE,
ChainId.BASE_GOERLI,
]

Expand Down
43 changes: 43 additions & 0 deletions src/providers/BaseMappingProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { MappingProvider } from './MappingProvider'
import { ChainId } from '../constants/chainId'
import { getTokenList } from '../utils'
import { GenericMappedTokenData } from '../constants/types'

const baseGoerliTokenListURL =
'https://raw.githubusercontent.com/' +
'ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'

/**
* The Base mapping (linked above) is manually maintained by the Coinbase team
* in this repository: https://github.com/ethereum-optimism/ethereum-optimism.github.io.
*/
export class BaseMappingProvider implements MappingProvider {
async provide(): Promise<GenericMappedTokenData> {
const tokens: { [key: string]: string | undefined } = {}

let allTokens = await getTokenList(baseGoerliTokenListURL)

let opTokenId_baseAddressMap = {}
allTokens.tokens.forEach((token) => {
if (token.chainId === ChainId.BASE) {
if (typeof token.extensions?.opTokenId === 'string') {
opTokenId_baseAddressMap[token.extensions.opTokenId] =
token.address
}
}
})

allTokens.tokens.forEach((token) => {
if (
token.chainId === ChainId.MAINNET &&
typeof token.extensions?.opTokenId === 'string' &&
token.extensions!.opTokenId in opTokenId_baseAddressMap
) {
tokens[token.address.toLowerCase()] =
opTokenId_baseAddressMap[token.extensions!.opTokenId]
}
})

return tokens
}
}
26 changes: 20 additions & 6 deletions src/providers/OptimismMappingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { GenericMappedTokenData } from '../constants/types'

const optimismTokenListURL =
'https://raw.githubusercontent.com/' +
'ethereum-optimism/ethereum-optimism.github.io/2138386277e4156d159615d1840882cecc398437/optimism.tokenlist.json'
'ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'

/**
* The Optimism L2 mapping (linked above) is manually maintained by the Optimism team.
Expand All @@ -16,14 +16,28 @@ export class OptimismMappingProvider implements MappingProvider {
async provide(): Promise<GenericMappedTokenData> {
const tokens: { [key: string]: string | undefined } = {}

let optimismTokens = await getTokenList(optimismTokenListURL)
let allTokens = await getTokenList(optimismTokenListURL)

for (const token of optimismTokens.tokens) {
if (token.chainId === ChainId.MAINNET) {
let opTokenId_baseAddressMap = {}
allTokens.tokens.forEach((token) => {
if (token.chainId === ChainId.OPTIMISM) {
if (typeof token.extensions?.opTokenId === 'string') {
opTokenId_baseAddressMap[token.extensions.opTokenId] =
token.address
}
}
})

allTokens.tokens.forEach((token) => {
if (
token.chainId === ChainId.MAINNET &&
typeof token.extensions?.opTokenId === 'string' &&
token.extensions!.opTokenId in opTokenId_baseAddressMap
) {
tokens[token.address.toLowerCase()] =
token?.extensions?.bridgeInfo![ChainId.OPTIMISM].tokenAddress
opTokenId_baseAddressMap[token.extensions!.opTokenId]
}
}
})

return tokens
}
Expand Down
4 changes: 4 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '../constants/types'
import { AvalancheMappingProvider } from './AvalancheMappingProvider'
import { BaseGoerliMappingProvider } from './BaseGoerliMappingProvider'
import { BaseMappingProvider } from './BaseMappingProvider'

const web3 = new Web3()

Expand All @@ -32,6 +33,7 @@ const CHAINS_WITH_MAPPING_PROVIDERS = [
ChainId.OPTIMISM,
ChainId.BNB,
ChainId.AVALANCHE,
ChainId.BASE,
ChainId.BASE_GOERLI,
]

Expand Down Expand Up @@ -152,6 +154,8 @@ function getMappingProvider(chainId: ChainId, l1TokenList: TokenList) {
return new BnbMappingProvider()
case ChainId.AVALANCHE:
return new AvalancheMappingProvider()
case ChainId.BASE:
return new BaseMappingProvider()
case ChainId.BASE_GOERLI:
return new BaseGoerliMappingProvider()
default:
Expand Down
2 changes: 2 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export function getRpcUrl(chainId: ChainId): string {
return 'https://bsc-dataseed1.binance.org'
case ChainId.AVALANCHE:
return 'https://api.avax.network/ext/bc/C/rpc'
case ChainId.BASE:
return "https://https://mainnet.base.org"
case ChainId.BASE_GOERLI:
return 'https://goerli.base.org'
default:
Expand Down

0 comments on commit 789cb0a

Please sign in to comment.