1 | import * as _ from 'lodash';
|
2 | import { OfcTokenConfig } from './v2/coins/ofcToken';
|
3 | import { Erc20TokenConfig } from './v2/coins/erc20Token';
|
4 | import { StellarTokenConfig } from './v2/coins/stellarToken';
|
5 | import { CeloTokenConfig } from './v2/coins/celoToken';
|
6 | import { EosTokenConfig } from './v2/coins/eosToken';
|
7 | import { AlgoTokenConfig } from './v2/coins/algoToken';
|
8 | import { AvaxcTokenConfig } from './v2/coins/avaxcToken';
|
9 | import { FiatTokenConfig } from './v2/coins/fiatToken';
|
10 | import {
|
11 | coins,
|
12 | Erc20Coin,
|
13 | StellarCoin,
|
14 | OfcCoin,
|
15 | CeloCoin,
|
16 | CoinKind,
|
17 | NetworkType,
|
18 | EosCoin,
|
19 | Networks,
|
20 | AlgoCoin,
|
21 | AvaxERC20Token,
|
22 | FiatToken,
|
23 | } from '@bitgo/statics';
|
24 | import { EnvironmentName, Environments } from '@bitgo/sdk-core';
|
25 |
|
26 | export interface Tokens {
|
27 | bitcoin: {
|
28 | eth: {
|
29 | tokens: Erc20TokenConfig[];
|
30 | };
|
31 | xlm: {
|
32 | tokens: StellarTokenConfig[];
|
33 | };
|
34 | algo: {
|
35 | tokens: AlgoTokenConfig[];
|
36 | };
|
37 | ofc: {
|
38 | tokens: OfcTokenConfig[];
|
39 | };
|
40 | celo: {
|
41 | tokens: CeloTokenConfig[];
|
42 | };
|
43 | eos: {
|
44 | tokens: EosTokenConfig[];
|
45 | };
|
46 | avaxc: {
|
47 | tokens: AvaxcTokenConfig[];
|
48 | };
|
49 | fiat: {
|
50 | tokens: FiatTokenConfig[];
|
51 | };
|
52 | };
|
53 | testnet: {
|
54 | eth: {
|
55 | tokens: Erc20TokenConfig[];
|
56 | };
|
57 | xlm: {
|
58 | tokens: StellarTokenConfig[];
|
59 | };
|
60 | algo: {
|
61 | tokens: AlgoTokenConfig[];
|
62 | };
|
63 | ofc: {
|
64 | tokens: OfcTokenConfig[];
|
65 | };
|
66 | celo: {
|
67 | tokens: CeloTokenConfig[];
|
68 | };
|
69 | eos: {
|
70 | tokens: EosTokenConfig[];
|
71 | };
|
72 | avaxc: {
|
73 | tokens: AvaxcTokenConfig[];
|
74 | };
|
75 | fiat: {
|
76 | tokens: FiatTokenConfig[];
|
77 | };
|
78 | };
|
79 | }
|
80 |
|
81 |
|
82 | const formattedErc20Tokens = coins.reduce((acc: Erc20TokenConfig[], coin) => {
|
83 | if (coin instanceof Erc20Coin) {
|
84 | let baseCoin: string;
|
85 | switch (coin.network) {
|
86 | case Networks.main.ethereum:
|
87 | baseCoin = 'eth';
|
88 | break;
|
89 | case Networks.test.kovan:
|
90 | baseCoin = 'teth';
|
91 | break;
|
92 | case Networks.test.goerli:
|
93 | baseCoin = 'gteth';
|
94 | break;
|
95 | default:
|
96 | throw new Error(`Erc20 token ${coin.name} has an unsupported network`);
|
97 | }
|
98 |
|
99 | acc.push({
|
100 | type: coin.name,
|
101 | coin: baseCoin,
|
102 | network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
|
103 | name: coin.fullName,
|
104 | tokenContractAddress: coin.contractAddress.toString().toLowerCase(),
|
105 | decimalPlaces: coin.decimalPlaces,
|
106 | });
|
107 | }
|
108 | return acc;
|
109 | }, []);
|
110 |
|
111 | export const ethGasConfigs = {
|
112 | minimumGasPrice: 1000000000,
|
113 | defaultGasPrice: 20000000000,
|
114 | maximumGasPrice: 2500000000000,
|
115 | defaultGasLimit: 500000,
|
116 | defaultGasLimitTokenSend: 1000000,
|
117 | minimumGasLimit: 30000,
|
118 | maximumGasLimit: 20000000,
|
119 | };
|
120 |
|
121 | const formattedStellarTokens = coins.reduce((acc: StellarTokenConfig[], coin) => {
|
122 | if (coin instanceof StellarCoin) {
|
123 | acc.push({
|
124 | type: coin.name,
|
125 | coin: coin.network.type === NetworkType.MAINNET ? 'xlm' : 'txlm',
|
126 | network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
|
127 | name: coin.fullName,
|
128 | decimalPlaces: coin.decimalPlaces,
|
129 | });
|
130 | }
|
131 | return acc;
|
132 | }, []);
|
133 |
|
134 |
|
135 | const formattedAlgoTokens = coins.reduce((acc: AlgoTokenConfig[], coin) => {
|
136 | if (coin instanceof AlgoCoin) {
|
137 | acc.push({
|
138 | type: coin.name,
|
139 | coin: coin.network.type === NetworkType.MAINNET ? 'algo' : 'talgo',
|
140 | alias: coin.alias,
|
141 | network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
|
142 | name: coin.fullName,
|
143 | decimalPlaces: coin.decimalPlaces,
|
144 | });
|
145 | }
|
146 | return acc;
|
147 | }, []);
|
148 |
|
149 |
|
150 | const formattedOfcCoins = coins.reduce((acc: OfcTokenConfig[], coin) => {
|
151 | if (coin instanceof OfcCoin) {
|
152 | acc.push({
|
153 | type: coin.name,
|
154 | coin: 'ofc',
|
155 | backingCoin: coin.asset,
|
156 | name: coin.fullName,
|
157 | decimalPlaces: coin.decimalPlaces,
|
158 | isFiat: coin.kind === CoinKind.FIAT,
|
159 | });
|
160 | }
|
161 | return acc;
|
162 | }, []);
|
163 |
|
164 | const formattedCeloTokens = coins.reduce((acc: CeloTokenConfig[], coin) => {
|
165 | if (coin instanceof CeloCoin) {
|
166 | acc.push({
|
167 | type: coin.name,
|
168 | coin: coin.network.type === NetworkType.MAINNET ? 'celo' : 'tcelo',
|
169 | network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
|
170 | name: coin.fullName,
|
171 | tokenContractAddress: coin.contractAddress.toString().toLowerCase(),
|
172 | decimalPlaces: coin.decimalPlaces,
|
173 | });
|
174 | }
|
175 | return acc;
|
176 | }, []);
|
177 |
|
178 | const formattedEosTokens = coins.reduce((acc: EosTokenConfig[], coin) => {
|
179 | if (coin instanceof EosCoin) {
|
180 | acc.push({
|
181 | type: coin.name,
|
182 | coin: coin.network.type === NetworkType.MAINNET ? 'eos' : 'teos',
|
183 | network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
|
184 | name: coin.fullName,
|
185 | tokenContractAddress: coin.contractName.toString().toLowerCase(),
|
186 | decimalPlaces: coin.decimalPlaces,
|
187 | });
|
188 | }
|
189 | return acc;
|
190 | }, []);
|
191 |
|
192 | const formattedFiatTokens = coins.reduce((acc: FiatTokenConfig[], coin) => {
|
193 | if (coin instanceof FiatToken) {
|
194 | acc.push({
|
195 | name: coin.fullName,
|
196 | type: coin.name,
|
197 | coin: coin.network.type === NetworkType.MAINNET ? 'fiat' : 'tfiat',
|
198 | network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
|
199 | decimalPlaces: coin.decimalPlaces,
|
200 | });
|
201 | }
|
202 | return acc;
|
203 | }, []);
|
204 |
|
205 | const formattedAvaxCTokens = coins.reduce((acc: AvaxcTokenConfig[], coin) => {
|
206 | if (coin instanceof AvaxERC20Token) {
|
207 | acc.push({
|
208 | type: coin.name,
|
209 | coin: coin.network.type === NetworkType.MAINNET ? 'avaxc' : 'tavaxc',
|
210 | network: coin.network.type === NetworkType.MAINNET ? 'Mainnet' : 'Testnet',
|
211 | name: coin.fullName,
|
212 | tokenContractAddress: coin.contractAddress.toString().toLowerCase(),
|
213 | decimalPlaces: coin.decimalPlaces,
|
214 | });
|
215 | }
|
216 | return acc;
|
217 | }, []);
|
218 |
|
219 | export const tokens: Tokens = {
|
220 |
|
221 | bitcoin: {
|
222 | eth: {
|
223 | tokens: formattedErc20Tokens.filter(token => token.network === 'Mainnet'),
|
224 | },
|
225 | xlm: {
|
226 | tokens: formattedStellarTokens.filter(token => token.network === 'Mainnet'),
|
227 | },
|
228 | algo: {
|
229 | tokens: formattedAlgoTokens.filter(token => token.network === 'Mainnet'),
|
230 | },
|
231 | ofc: {
|
232 | tokens: formattedOfcCoins.filter(token => coins.get(token.type).network.type === NetworkType.MAINNET),
|
233 | },
|
234 | celo: {
|
235 | tokens: formattedCeloTokens.filter(token => token.network === 'Mainnet'),
|
236 | },
|
237 | eos: {
|
238 | tokens: formattedEosTokens.filter(token => token.network === 'Mainnet'),
|
239 | },
|
240 | avaxc: {
|
241 | tokens: formattedAvaxCTokens.filter(token => token.network === 'Mainnet'),
|
242 | },
|
243 | fiat: {
|
244 | tokens: formattedFiatTokens.filter(token => token.network === 'Mainnet'),
|
245 | },
|
246 | },
|
247 |
|
248 | testnet: {
|
249 | eth: {
|
250 | tokens: formattedErc20Tokens.filter(token => token.network === 'Testnet'),
|
251 | },
|
252 | xlm: {
|
253 | tokens: formattedStellarTokens.filter(token => token.network === 'Testnet'),
|
254 | },
|
255 | algo: {
|
256 | tokens: formattedAlgoTokens.filter(token => token.network === 'Testnet'),
|
257 | },
|
258 | ofc: {
|
259 | tokens: formattedOfcCoins.filter(token => coins.get(token.type).network.type === NetworkType.TESTNET),
|
260 | },
|
261 | celo: {
|
262 | tokens: formattedCeloTokens.filter(token => token.network === 'Testnet'),
|
263 | },
|
264 | eos: {
|
265 | tokens: formattedEosTokens.filter(token => token.network === 'Testnet'),
|
266 | },
|
267 | avaxc: {
|
268 | tokens: formattedAvaxCTokens.filter(token => token.network === 'Testnet'),
|
269 | },
|
270 | fiat: {
|
271 | tokens: formattedFiatTokens.filter(token => token.network === 'Testnet'),
|
272 | },
|
273 | },
|
274 | };
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 | const verifyTokens = function (tokens) {
|
281 | const verifiedTokens = {};
|
282 | _.forEach(tokens, function (token) {
|
283 | if (verifiedTokens[token.type]) {
|
284 | throw new Error('token : ' + token.type + ' duplicated.');
|
285 | }
|
286 | verifiedTokens[token.type] = true;
|
287 |
|
288 | if (token.tokenContractAddress && token.tokenContractAddress !== _.toLower(token.tokenContractAddress)) {
|
289 | throw new Error('token contract: ' + token.type + ' is not all lower case: ' + token.tokenContractAddress);
|
290 | }
|
291 | });
|
292 | return verifiedTokens;
|
293 | };
|
294 |
|
295 | const mainnetErc20Tokens = verifyTokens(tokens.bitcoin.eth.tokens);
|
296 | const mainnetStellarTokens = verifyTokens(tokens.bitcoin.xlm.tokens);
|
297 | export const mainnetTokens = _.assign({}, mainnetErc20Tokens, mainnetStellarTokens);
|
298 |
|
299 | const testnetErc20Tokens = verifyTokens(tokens.testnet.eth.tokens);
|
300 | const testnetStellarTokens = verifyTokens(tokens.testnet.xlm.tokens);
|
301 | export const testnetTokens = _.assign({}, testnetErc20Tokens, testnetStellarTokens);
|
302 |
|
303 |
|
304 | export const defaults = {
|
305 | maxFee: 0.1e8,
|
306 | maxFeeRate: 1000000,
|
307 | minFeeRate: 5000,
|
308 | fallbackFeeRate: 50000,
|
309 | minOutputSize: 2730,
|
310 | minInstantFeeRate: 10000,
|
311 | bitgoEthAddress: '0x0f47ea803926926f299b7f1afc8460888d850f47',
|
312 | };
|
313 |
|
314 |
|
315 |
|
316 | export const supportedCrossChainRecoveries = {
|
317 | btc: ['bch', 'ltc', 'bsv'],
|
318 | bch: ['btc', 'ltc', 'bsv'],
|
319 | ltc: ['btc', 'bch', 'bsv'],
|
320 | bsv: ['btc', 'ltc', 'bch'],
|
321 | };
|
322 |
|
323 | export type KrsProvider = {
|
324 | feeType: 'flatUsd';
|
325 | feeAmount: number;
|
326 | supportedCoins: string[];
|
327 | feeAddresses?: Record<string, string>
|
328 | }
|
329 |
|
330 |
|
331 | export const krsProviders: Record<string, KrsProvider> = {
|
332 | keyternal: {
|
333 | feeType: 'flatUsd',
|
334 | feeAmount: 99,
|
335 | supportedCoins: ['btc', 'eth'],
|
336 | feeAddresses: {
|
337 | btc: '',
|
338 | },
|
339 | },
|
340 | bitgoKRSv2: {
|
341 | feeType: 'flatUsd',
|
342 | feeAmount: 0,
|
343 | supportedCoins: ['btc', 'eth'],
|
344 | },
|
345 | dai: {
|
346 | feeType: 'flatUsd',
|
347 | feeAmount: 0,
|
348 | supportedCoins: ['btc', 'eth', 'xlm', 'xrp', 'dash', 'zec', 'ltc', 'bch', 'bsv', 'bcha'],
|
349 | },
|
350 | };
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | export const defaultConstants = (env: EnvironmentName) => {
|
361 | if (Environments[env] === undefined) {
|
362 | throw Error(`invalid environment ${env}`);
|
363 | }
|
364 |
|
365 | const network = Environments[env].network;
|
366 | return _.merge({}, defaults, tokens[network]);
|
367 | };
|
368 |
|
369 | export type Config = {
|
370 | krsProviders: Record<string, KrsProvider>
|
371 | }
|