@uniswap/token-list-bridge-utils 1.0.7 → 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/abis/erc20.d.ts +3 -0
- 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 +673 -237
- 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 +672 -237
- package/dist/token-list-bridge-utils.esm.js.map +1 -1
- package/dist/utils.d.ts +4 -0
- package/package.json +4 -2
- package/src/abis/erc20.ts +226 -0
- package/src/arbitrum/set_rpc.ts +4 -3
- 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 +167 -62
- package/src/utils.ts +28 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { AbiItem } from 'web3-utils'
|
|
2
|
+
|
|
3
|
+
const abi: AbiItem[] = [
|
|
4
|
+
{
|
|
5
|
+
constant: true,
|
|
6
|
+
inputs: [],
|
|
7
|
+
name: 'name',
|
|
8
|
+
outputs: [
|
|
9
|
+
{
|
|
10
|
+
name: '',
|
|
11
|
+
type: 'string',
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
payable: false,
|
|
15
|
+
stateMutability: 'view',
|
|
16
|
+
type: 'function',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
constant: false,
|
|
20
|
+
inputs: [
|
|
21
|
+
{
|
|
22
|
+
name: '_spender',
|
|
23
|
+
type: 'address',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: '_value',
|
|
27
|
+
type: 'uint256',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
name: 'approve',
|
|
31
|
+
outputs: [
|
|
32
|
+
{
|
|
33
|
+
name: '',
|
|
34
|
+
type: 'bool',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
payable: false,
|
|
38
|
+
stateMutability: 'nonpayable',
|
|
39
|
+
type: 'function',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
constant: true,
|
|
43
|
+
inputs: [],
|
|
44
|
+
name: 'totalSupply',
|
|
45
|
+
outputs: [
|
|
46
|
+
{
|
|
47
|
+
name: '',
|
|
48
|
+
type: 'uint256',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
payable: false,
|
|
52
|
+
stateMutability: 'view',
|
|
53
|
+
type: 'function',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
constant: false,
|
|
57
|
+
inputs: [
|
|
58
|
+
{
|
|
59
|
+
name: '_from',
|
|
60
|
+
type: 'address',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: '_to',
|
|
64
|
+
type: 'address',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: '_value',
|
|
68
|
+
type: 'uint256',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
name: 'transferFrom',
|
|
72
|
+
outputs: [
|
|
73
|
+
{
|
|
74
|
+
name: '',
|
|
75
|
+
type: 'bool',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
payable: false,
|
|
79
|
+
stateMutability: 'nonpayable',
|
|
80
|
+
type: 'function',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
constant: true,
|
|
84
|
+
inputs: [],
|
|
85
|
+
name: 'decimals',
|
|
86
|
+
outputs: [
|
|
87
|
+
{
|
|
88
|
+
name: '',
|
|
89
|
+
type: 'uint8',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
payable: false,
|
|
93
|
+
stateMutability: 'view',
|
|
94
|
+
type: 'function',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
constant: true,
|
|
98
|
+
inputs: [
|
|
99
|
+
{
|
|
100
|
+
name: '_owner',
|
|
101
|
+
type: 'address',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
name: 'balanceOf',
|
|
105
|
+
outputs: [
|
|
106
|
+
{
|
|
107
|
+
name: 'balance',
|
|
108
|
+
type: 'uint256',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
payable: false,
|
|
112
|
+
stateMutability: 'view',
|
|
113
|
+
type: 'function',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
constant: true,
|
|
117
|
+
inputs: [],
|
|
118
|
+
name: 'symbol',
|
|
119
|
+
outputs: [
|
|
120
|
+
{
|
|
121
|
+
name: '',
|
|
122
|
+
type: 'string',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
payable: false,
|
|
126
|
+
stateMutability: 'view',
|
|
127
|
+
type: 'function',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
constant: false,
|
|
131
|
+
inputs: [
|
|
132
|
+
{
|
|
133
|
+
name: '_to',
|
|
134
|
+
type: 'address',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: '_value',
|
|
138
|
+
type: 'uint256',
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
name: 'transfer',
|
|
142
|
+
outputs: [
|
|
143
|
+
{
|
|
144
|
+
name: '',
|
|
145
|
+
type: 'bool',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
payable: false,
|
|
149
|
+
stateMutability: 'nonpayable',
|
|
150
|
+
type: 'function',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
constant: true,
|
|
154
|
+
inputs: [
|
|
155
|
+
{
|
|
156
|
+
name: '_owner',
|
|
157
|
+
type: 'address',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: '_spender',
|
|
161
|
+
type: 'address',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
name: 'allowance',
|
|
165
|
+
outputs: [
|
|
166
|
+
{
|
|
167
|
+
name: '',
|
|
168
|
+
type: 'uint256',
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
payable: false,
|
|
172
|
+
stateMutability: 'view',
|
|
173
|
+
type: 'function',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
payable: true,
|
|
177
|
+
stateMutability: 'payable',
|
|
178
|
+
type: 'fallback',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
anonymous: false,
|
|
182
|
+
inputs: [
|
|
183
|
+
{
|
|
184
|
+
indexed: true,
|
|
185
|
+
name: 'owner',
|
|
186
|
+
type: 'address',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
indexed: true,
|
|
190
|
+
name: 'spender',
|
|
191
|
+
type: 'address',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
indexed: false,
|
|
195
|
+
name: 'value',
|
|
196
|
+
type: 'uint256',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
name: 'Approval',
|
|
200
|
+
type: 'event',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
anonymous: false,
|
|
204
|
+
inputs: [
|
|
205
|
+
{
|
|
206
|
+
indexed: true,
|
|
207
|
+
name: 'from',
|
|
208
|
+
type: 'address',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
indexed: true,
|
|
212
|
+
name: 'to',
|
|
213
|
+
type: 'address',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
indexed: false,
|
|
217
|
+
name: 'value',
|
|
218
|
+
type: 'uint256',
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
name: 'Transfer',
|
|
222
|
+
type: 'event',
|
|
223
|
+
},
|
|
224
|
+
]
|
|
225
|
+
|
|
226
|
+
export default abi
|
package/src/arbitrum/set_rpc.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { config } from 'dotenv'
|
|
2
|
+
import { ChainId } from '../constants/chainId'
|
|
3
|
+
import { getRpcUrl } from '../utils'
|
|
2
4
|
|
|
3
|
-
// fall back to https://cloudflare-eth.com/ if MAINNET_RPC not provided in an .env file
|
|
5
|
+
// MAINNET_RPC value is required for arb-sdk. fall back to https://cloudflare-eth.com/ if MAINNET_RPC not provided in an .env file
|
|
4
6
|
config()
|
|
5
|
-
process.env.MAINNET_RPC
|
|
6
|
-
process.env.MAINNET_RPC || 'https://cloudflare-eth.com/'
|
|
7
|
+
process.env.MAINNET_RPC ??= getRpcUrl(ChainId.MAINNET)
|
package/src/constants/chainId.ts
CHANGED
package/src/constants/types.ts
CHANGED
|
@@ -25,4 +25,8 @@ export interface PolygonMappedToken {
|
|
|
25
25
|
updated_at: string
|
|
26
26
|
uri: string
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
export type GenericMappedTokenData = { [key: string]: string | undefined }
|
|
30
|
+
|
|
31
|
+
// Polygon has its own type here since their api gives us more info than just the mapped token address
|
|
32
|
+
export type PolygonMappedTokenData = { [key: string]: PolygonMappedToken }
|
package/src/index.test.ts
CHANGED
|
@@ -20,7 +20,7 @@ jest.setTimeout(15000)
|
|
|
20
20
|
describe(chainifyTokenList, () => {
|
|
21
21
|
it('outputs Optimism list correctly', async () => {
|
|
22
22
|
const tokenList = await chainifyTokenList(
|
|
23
|
-
ChainId.OPTIMISM,
|
|
23
|
+
[ChainId.OPTIMISM],
|
|
24
24
|
sampleL1TokenList
|
|
25
25
|
)
|
|
26
26
|
|
|
@@ -40,7 +40,7 @@ describe(chainifyTokenList, () => {
|
|
|
40
40
|
|
|
41
41
|
it('outputs arbitrum list correctly', async () => {
|
|
42
42
|
const tokenList = await chainifyTokenList(
|
|
43
|
-
ChainId.ARBITRUM_ONE,
|
|
43
|
+
[ChainId.ARBITRUM_ONE],
|
|
44
44
|
sampleL1TokenList
|
|
45
45
|
)
|
|
46
46
|
|
|
@@ -64,7 +64,7 @@ describe(chainifyTokenList, () => {
|
|
|
64
64
|
|
|
65
65
|
it('outputs polygon list correctly', async () => {
|
|
66
66
|
const tokenList = await chainifyTokenList(
|
|
67
|
-
ChainId.POLYGON,
|
|
67
|
+
[ChainId.POLYGON],
|
|
68
68
|
sampleL1TokenList
|
|
69
69
|
)
|
|
70
70
|
|
package/src/index.ts
CHANGED
|
@@ -18,39 +18,33 @@ export async function chainify(
|
|
|
18
18
|
l1TokenListOrPathOrUrl: TokenListOrFetchableTokenList
|
|
19
19
|
): Promise<TokenList> {
|
|
20
20
|
const l1TokenList = await getTokenList(l1TokenListOrPathOrUrl)
|
|
21
|
-
|
|
22
|
-
const optimisimed = await chainifyTokenList(
|
|
23
|
-
ChainId.OPTIMISM,
|
|
24
|
-
l1TokenListOrPathOrUrl
|
|
25
|
-
)
|
|
26
|
-
const polygoned = await chainifyTokenList(
|
|
21
|
+
const l2Chains = [
|
|
27
22
|
ChainId.POLYGON,
|
|
28
|
-
l1TokenListOrPathOrUrl
|
|
29
|
-
)
|
|
30
|
-
const arbified = await chainifyTokenList(
|
|
31
23
|
ChainId.ARBITRUM_ONE,
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
ChainId.OPTIMISM,
|
|
25
|
+
ChainId.CELO,
|
|
26
|
+
]
|
|
34
27
|
|
|
28
|
+
const chainified = await chainifyTokenList(l2Chains, l1TokenListOrPathOrUrl)
|
|
35
29
|
return mergeTokenLists(
|
|
36
30
|
l1TokenList, // providing l1 first to make sure duplicated tokens resolve to this list
|
|
37
|
-
|
|
31
|
+
chainified
|
|
38
32
|
)
|
|
39
33
|
}
|
|
40
34
|
|
|
41
35
|
/**
|
|
42
36
|
* Given a network and a TokenList, returns the TokenList with `extensions` filled.
|
|
43
|
-
* @param
|
|
37
|
+
* @param l2ChainIds layer 2 chainIds to operate on
|
|
44
38
|
* @param l1TokenListOrPathOrUrl either an L1 TokenList object or a path/url to a TokenList
|
|
45
39
|
* @returns L1 TokenList with `extensions` filled for the given network
|
|
46
40
|
*/
|
|
47
41
|
export async function chainifyTokenList(
|
|
48
|
-
|
|
42
|
+
l2ChainIds: Array<ChainId>,
|
|
49
43
|
l1TokenListOrPathOrUrl: TokenListOrFetchableTokenList
|
|
50
44
|
): Promise<TokenList> {
|
|
51
45
|
try {
|
|
52
46
|
const l1TokenList = await getTokenList(l1TokenListOrPathOrUrl)
|
|
53
|
-
const tokenList = await buildList(
|
|
47
|
+
const tokenList = await buildList(l2ChainIds, l1TokenList)
|
|
54
48
|
return verifyExtensions(tokenList)
|
|
55
49
|
} catch (e) {
|
|
56
50
|
throw new Error(`An error occured: ${e}`)
|
|
@@ -2,6 +2,7 @@ import { MappingProvider } from './MappingProvider'
|
|
|
2
2
|
import { getNetworkConfig } from '../arbitrum/instantiate_bridge'
|
|
3
3
|
import { getL2TokenAddressesFromL1 } from '../arbitrum/gateway'
|
|
4
4
|
import { TokenList } from '@uniswap/token-lists'
|
|
5
|
+
import { GenericMappedTokenData } from '../constants/types'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* This provider provides the l1->l2(Arbitrum) address mappings using the arbitrum SDK.
|
|
@@ -13,7 +14,7 @@ export class ArbitrumMappingProvider implements MappingProvider {
|
|
|
13
14
|
this.l1TokenList = l1TokenList
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
async provide(): Promise<
|
|
17
|
+
async provide(): Promise<GenericMappedTokenData> {
|
|
17
18
|
let tokens: { [key: string]: string | undefined } = {}
|
|
18
19
|
|
|
19
20
|
const { l1, l2 } = await getNetworkConfig()
|
|
@@ -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 |
|
|
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<
|
|
16
|
+
async provide(): Promise<GenericMappedTokenData> {
|
|
16
17
|
const tokens: { [key: string]: string | undefined } = {}
|
|
17
18
|
|
|
18
19
|
let optimismTokens = await getTokenList(optimismTokenListURL)
|
package/src/providers/index.ts
CHANGED
|
@@ -1,88 +1,133 @@
|
|
|
1
1
|
import { ChainId } from '../constants/chainId'
|
|
2
|
+
import { Contract } from 'web3-eth-contract'
|
|
2
3
|
import { ArbitrumMappingProvider } from './ArbitrumMappingProvider'
|
|
3
4
|
import { OptimismMappingProvider } from './OptimismMappingProvider'
|
|
4
5
|
import { PolygonMappingProvider } from './PolygonMappingProvider'
|
|
5
6
|
import { TokenInfo, TokenList } from '@uniswap/token-lists'
|
|
6
|
-
import { compareTokenInfos } from '../utils'
|
|
7
7
|
import { ethers } from 'ethers'
|
|
8
|
+
import {
|
|
9
|
+
compareTokenInfos,
|
|
10
|
+
getRpcUrl,
|
|
11
|
+
getTokenSymbolFromContract,
|
|
12
|
+
} from '../utils'
|
|
13
|
+
import ERC20Abi from '../abis/erc20'
|
|
14
|
+
// TODO: use ethers for contract calls (without performance reduction).
|
|
15
|
+
import Web3 from 'web3'
|
|
16
|
+
import {
|
|
17
|
+
GenericMappedTokenData,
|
|
18
|
+
PolygonMappedTokenData,
|
|
19
|
+
} from '../constants/types'
|
|
20
|
+
|
|
21
|
+
const web3 = new Web3()
|
|
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
|
+
]
|
|
8
29
|
|
|
9
30
|
export async function buildList(
|
|
10
|
-
|
|
31
|
+
l2ChainIds: Array<ChainId>,
|
|
11
32
|
l1TokenList: TokenList
|
|
12
33
|
): Promise<TokenList> {
|
|
13
|
-
const
|
|
14
|
-
|
|
34
|
+
const multiChainedTokens: TokenInfo[] = []
|
|
35
|
+
const chainIdToMappingsMap = await generateTokenMappings(
|
|
36
|
+
l2ChainIds,
|
|
15
37
|
l1TokenList
|
|
16
|
-
)
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
[chainId]: {
|
|
44
|
-
tokenAddress: ethers.utils.getAddress(
|
|
45
|
-
typeof childToken === 'object'
|
|
46
|
-
? childToken.child_token
|
|
47
|
-
: childToken!
|
|
48
|
-
),
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
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
|
+
}
|
|
52
65
|
}
|
|
53
|
-
|
|
66
|
+
})
|
|
67
|
+
)
|
|
54
68
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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)
|
|
59
100
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
chainId: chainId,
|
|
64
|
-
address: ethers.utils.getAddress(
|
|
65
|
-
typeof childToken === 'object'
|
|
66
|
-
? childToken.child_token
|
|
67
|
-
: childToken!
|
|
68
|
-
),
|
|
69
|
-
...toRootExtensions,
|
|
70
|
-
} as unknown as TokenInfo
|
|
71
|
-
mappedTokens.push(childTokenInfo)
|
|
72
|
-
}
|
|
73
|
-
mappedTokens.push(rootTokenInfo)
|
|
101
|
+
multiChainedTokens.push(tokenInfo)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
74
104
|
}
|
|
75
105
|
}
|
|
106
|
+
|
|
107
|
+
// build and return final chainified token list
|
|
76
108
|
const tokenList = {
|
|
77
|
-
name: `(
|
|
109
|
+
name: `(ChainIds: ${l2ChainIds}) ${l1TokenList.name}`,
|
|
78
110
|
timestamp: new Date().toISOString(),
|
|
79
111
|
version: l1TokenList.version,
|
|
80
|
-
tokens:
|
|
112
|
+
tokens: multiChainedTokens.sort(compareTokenInfos),
|
|
81
113
|
}
|
|
82
114
|
|
|
83
115
|
return tokenList
|
|
84
116
|
}
|
|
85
117
|
|
|
118
|
+
// using a symbol lookup contract call to check whether the token exists on the L2
|
|
119
|
+
async function hasExistingTokenContract(address: string, chainId: ChainId) {
|
|
120
|
+
web3.setProvider(getRpcUrl(chainId))
|
|
121
|
+
try {
|
|
122
|
+
const contract: Contract = new web3.eth.Contract(ERC20Abi, address)
|
|
123
|
+
await getTokenSymbolFromContract(contract)
|
|
124
|
+
} catch {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
|
|
86
131
|
function getMappingProvider(chainId: ChainId, l1TokenList: TokenList) {
|
|
87
132
|
switch (chainId) {
|
|
88
133
|
case ChainId.ARBITRUM_ONE:
|
|
@@ -92,6 +137,66 @@ function getMappingProvider(chainId: ChainId, l1TokenList: TokenList) {
|
|
|
92
137
|
case ChainId.POLYGON:
|
|
93
138
|
return new PolygonMappingProvider()
|
|
94
139
|
default:
|
|
95
|
-
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,
|
|
96
201
|
}
|
|
97
202
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TokenInfo, TokenList } from '@uniswap/token-lists'
|
|
2
2
|
import { readFileSync, existsSync } from 'fs'
|
|
3
3
|
import axios from 'axios'
|
|
4
|
+
import { ChainId } from './constants/chainId'
|
|
5
|
+
import { Contract } from 'web3-eth-contract'
|
|
4
6
|
|
|
5
7
|
export type TokenListOrFetchableTokenList = TokenList | string
|
|
6
8
|
|
|
@@ -89,3 +91,29 @@ export const isTokenList = (obj: any) => {
|
|
|
89
91
|
throw new Error('tokenlist typeguard error: token missing required key')
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
|
|
95
|
+
export function getRpcUrl(chainId: ChainId): string {
|
|
96
|
+
switch (chainId) {
|
|
97
|
+
case ChainId.MAINNET:
|
|
98
|
+
return 'https://cloudflare-eth.com/'
|
|
99
|
+
case ChainId.OPTIMISM:
|
|
100
|
+
return 'https://rpc.ankr.com/optimism' // seems to have higher rate limit than https://mainnet.optimism.io/
|
|
101
|
+
case ChainId.OPTIMISTIC_KOVAN:
|
|
102
|
+
return 'https://kovan.optimism.io'
|
|
103
|
+
case ChainId.ARBITRUM_ONE:
|
|
104
|
+
return 'https://arb1.arbitrum.io/rpc'
|
|
105
|
+
case ChainId.ARBITRUM_RINKEBY:
|
|
106
|
+
return 'https://rinkeby.arbitrum.io/rpc'
|
|
107
|
+
case ChainId.POLYGON:
|
|
108
|
+
return 'https://polygon-rpc.com/'
|
|
109
|
+
case ChainId.POLYGON_MUMBAI:
|
|
110
|
+
return 'https://rpc-endpoints.superfluid.dev/mumbai'
|
|
111
|
+
default:
|
|
112
|
+
}
|
|
113
|
+
throw new Error('Unsupported ChainId')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function getTokenSymbolFromContract(tokenContract: Contract) {
|
|
117
|
+
const symbol = await Promise.all([tokenContract.methods.symbol().call()])
|
|
118
|
+
return symbol
|
|
119
|
+
}
|