-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathTokenUtils.ts
157 lines (143 loc) · 6.35 KB
/
TokenUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import { BlockTag } from "@ethersproject/abstract-provider";
import { Contract, providers, Signer } from "ethers";
import * as constants from "../constants";
import { L1Token } from "../interfaces";
import { ERC20__factory } from "../typechain";
import { BigNumber } from "./BigNumberUtils";
import { getNetworkName } from "./NetworkUtils";
import { isDefined } from "./TypeGuards";
import { compareAddressesSimple } from "./AddressUtils";
const { TOKEN_SYMBOLS_MAP, CHAIN_IDs } = constants;
type SignerOrProvider = providers.Provider | Signer;
export async function fetchTokenInfo(address: string, signerOrProvider: SignerOrProvider): Promise<L1Token> {
const token = new Contract(address, ERC20__factory.abi, signerOrProvider);
const [symbol, decimals] = await Promise.all([token.symbol(), token.decimals()]);
return { address, symbol, decimals };
}
export const getL2TokenAddresses = (
l1TokenAddress: string,
l1ChainId = CHAIN_IDs.MAINNET
): { [chainId: number]: string } | undefined => {
return Object.values(TOKEN_SYMBOLS_MAP).find((details) => {
return details.addresses[l1ChainId] === l1TokenAddress;
})?.addresses;
};
/**
* Resolve a token symbol to an L1Token description on a particular chain ID.
* @notice Not to be confused with the HubPool's internal view on the supported origin/destination token for a chain.
* @param symbol Symbol to query.
* @param chainId Chain ID to query on.
* @returns Symbol, decimals and contract address on the requested chain.
*/
export function resolveSymbolOnChain(chainId: number, symbol: string): L1Token {
// @dev Suppress tsc complaints by casting symbol to the expected type.
const token = TOKEN_SYMBOLS_MAP[symbol as keyof typeof TOKEN_SYMBOLS_MAP];
if (!isDefined(token) || !isDefined(token.addresses[chainId])) {
const network = getNetworkName(chainId);
throw new Error(`Unable to find token ${symbol} on ${network} (chain ID ${chainId}`);
}
const { decimals, addresses } = token;
const address = addresses[chainId];
return { symbol, decimals, address };
}
/**
* Returns the contract address for a given token symbol and chainId.
* @param symbol A case-insensitive token symbol.
* @param chainId The chainId to resolve the contract address for.
* @param tokenMapping A parameter to determine where to source token information. Defaults to the constants variant.
* @returns The contract address for the given token symbol and chainId, or undefined if the token symbol is not supported.
*/
export const resolveContractFromSymbol = (
symbol: string,
chainId: string,
tokenMapping = TOKEN_SYMBOLS_MAP
): string | undefined => {
return Object.values(tokenMapping).find((details) => {
return details.symbol.toLowerCase() === symbol.toLowerCase();
})?.addresses[Number(chainId)];
};
export function getTokenInformationFromAddress(address: string, tokenMapping = TOKEN_SYMBOLS_MAP): L1Token | undefined {
const details = Object.values(tokenMapping).find((details) => {
return Object.values(details.addresses).some((t) => t.toLowerCase() === address.toLowerCase());
});
return isDefined(details)
? {
decimals: details.decimals,
symbol: details.symbol,
address,
}
: undefined;
}
export function getCoingeckoTokenIdByAddress(contractAddress: string): string {
const token = getTokenInformationFromAddress(contractAddress);
if (!token) {
throw new Error(`Token with address ${contractAddress} not found in token mapping`);
}
return TOKEN_SYMBOLS_MAP[token.symbol as keyof typeof TOKEN_SYMBOLS_MAP].coingeckoId;
}
/**
* Retrieves the ERC20 balance for a given address and token address.
* @param address The address to retrieve the balance for.
* @param tokenAddress The token address
* @param signerOrProvider A valid ethers.js Signer or Provider object.
* @param blockTag The block tag to retrieve the balance at.
* @returns The balance of the given address for the given token address.
*/
export function getTokenBalance(
address: string,
tokenAddress: string,
signerOrProvider: SignerOrProvider,
blockTag: BlockTag = "latest"
): Promise<BigNumber> {
const token = ERC20__factory.connect(tokenAddress, signerOrProvider);
return token.balanceOf(address, { blockTag });
}
export function isBridgedUsdc(tokenSymbol: string): boolean {
return !!constants.BRIDGED_USDC_SYMBOLS.find(
(bridgedUsdcSymbol) => bridgedUsdcSymbol.toLowerCase() === tokenSymbol.toLowerCase()
);
}
export function getTokenInfo(l2TokenAddress: string, chainId: number): L1Token {
// @dev This might give false positives if tokens on different networks have the same address. I'm not sure how
// to get around this...
const tokenObject = Object.values(TOKEN_SYMBOLS_MAP).find(({ addresses }) => addresses[chainId] === l2TokenAddress);
if (!tokenObject) {
throw new Error(
`TokenUtils#getTokenInfo: Unable to resolve token in TOKEN_SYMBOLS_MAP for ${l2TokenAddress} on chain ${chainId}`
);
}
return {
address: l2TokenAddress,
symbol: tokenObject.symbol,
decimals: tokenObject.decimals,
};
}
/**
* Get the USDC symbol for the given token address and chain ID.
* Note that this function is not especially safe because it relies on unique token addresses across different chains.
* @param l2Token A Web3 token address (not case sensitive)
* @param chainId A chain Id to reference
* @returns Either USDC (if native) or USDbC/USDzC/USDC.e (if bridged) or undefined if the token address is not recognized.
*/
export function getUsdcSymbol(l2Token: string, chainId: number): string | undefined {
const compareToken = (token?: string) => isDefined(token) && compareAddressesSimple(l2Token, token);
return ["USDC", "USDbC", "USDzC", "USDC.e"].find((token) =>
compareToken(
(TOKEN_SYMBOLS_MAP as Record<string, { addresses?: Record<number, string> }>)[token]?.addresses?.[chainId]
)
);
}
export function getL1TokenInfo(l2TokenAddress: string, chainId: number): L1Token {
const tokenObject = Object.values(TOKEN_SYMBOLS_MAP).find(({ addresses }) => addresses[chainId] === l2TokenAddress);
const l1TokenAddress = tokenObject?.addresses[CHAIN_IDs.MAINNET];
if (!l1TokenAddress) {
throw new Error(
`TokenUtils#getL1TokenInfo: Unable to resolve l1 token address in TOKEN_SYMBOLS_MAP for L2 token ${l2TokenAddress} on chain ${chainId}`
);
}
return {
address: l1TokenAddress,
symbol: tokenObject.symbol,
decimals: tokenObject.decimals,
};
}