Skip to content

Commit fe34bd9

Browse files
committed
Add service keys
1 parent e823f1d commit fe34bd9

File tree

11 files changed

+197
-38
lines changed

11 files changed

+197
-38
lines changed

src/common/serviceKeys.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ServiceKeys } from './types'
2+
import { pickRandom } from './utils'
3+
4+
export function getRandomServiceKey(
5+
serviceKeys: ServiceKeys,
6+
index: string
7+
): string | undefined {
8+
const keys = serviceKeys[index]
9+
if (keys == null || keys.length === 0) return undefined
10+
return pickRandom(keys, 1)[0]
11+
}
12+
13+
export function getServiceKeyIndex(urlString: string): string {
14+
const url = new URL(urlString, `https://${urlString}`)
15+
return url.host
16+
}

src/common/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,8 @@ export const asEdgeToken = asObject({
187187
export const asInfoServerTokens = asObject({
188188
infoServerTokens: asMaybe(asArray(asUnknown))
189189
})
190+
191+
export interface ServiceKeys {
192+
[host: string]: string[]
193+
}
194+
export const asServiceKeys: Cleaner<ServiceKeys> = asObject(asArray(asString))

src/ethereum/EthereumTools.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from '../common/utils'
3030
import { ethereumPlugins } from './ethereumInfos'
3131
import {
32+
asEthereumInitOptions,
3233
asEthereumPrivateKeys,
3334
asSafeEthWalletInfo,
3435
EthereumInfoPayload,
@@ -50,7 +51,7 @@ export class EthereumTools implements EdgeCurrencyTools {
5051
this.currencyInfo = currencyInfo
5152
this.io = io
5253
this.networkInfo = networkInfo
53-
this.initOptions = initOptions
54+
this.initOptions = asEthereumInitOptions(initOptions)
5455
}
5556

5657
async getDisplayPrivateKey(

src/ethereum/ethereumTypes.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,50 @@ import { EdgeSpendInfo } from 'edge-core-js/types'
1818
import {
1919
asIntegerString,
2020
asSafeCommonWalletInfo,
21+
asServiceKeys,
22+
ServiceKeys,
2123
WalletConnectPayload
2224
} from '../common/types'
2325
import { FeeAlgorithmConfig } from './feeAlgorithms/feeAlgorithmTypes'
2426
import type { NetworkAdapterConfig } from './networkAdapters/networkAdapterTypes'
2527

2628
export interface EthereumInitOptions {
29+
serviceKeys: ServiceKeys
30+
infuraProjectId?: string
31+
32+
/** @deprecated Use serviceKeys instead */
2733
alchemyApiKey?: string
34+
/** @deprecated Use serviceKeys instead */
2835
amberdataApiKey?: string
36+
/** @deprecated Use serviceKeys instead */
2937
blockchairApiKey?: string
38+
/** @deprecated Use serviceKeys instead */
3039
blockcypherApiKey?: string
40+
/** @deprecated Use serviceKeys instead */
3141
drpcApiKey?: string
32-
/** For Etherscan v2 API */
42+
/** For Etherscan v2 API
43+
* @deprecated Use serviceKeys instead
44+
*/
3345
etherscanApiKey?: string | string[]
34-
/** For bespoke scan APIs unsupported by Etherscan v2 API (e.g. fantomscan) */
46+
/** For bespoke scan APIs unsupported by Etherscan v2 API (e.g. fantomscan)
47+
* @deprecated Use serviceKeys instead
48+
*/
3549
evmScanApiKey?: string | string[]
50+
/** @deprecated Use serviceKeys instead */
3651
gasStationApiKey?: string
37-
infuraProjectId?: string
52+
/** @deprecated Use serviceKeys instead */
3853
nowNodesApiKey?: string
54+
/** @deprecated Use serviceKeys instead */
3955
poktPortalApiKey?: string
56+
/** @deprecated Use serviceKeys instead */
4057
quiknodeApiKey?: string
4158
}
4259

4360
export const asEthereumInitOptions = asObject<EthereumInitOptions>({
61+
serviceKeys: asOptional(asServiceKeys, () => ({})),
62+
infuraProjectId: asOptional(asString),
63+
64+
// Deprecated:
4465
alchemyApiKey: asOptional(asString),
4566
amberdataApiKey: asOptional(asString),
4667
blockchairApiKey: asOptional(asString),
@@ -49,7 +70,6 @@ export const asEthereumInitOptions = asObject<EthereumInitOptions>({
4970
etherscanApiKey: asOptional(asEither(asString, asArray(asString))),
5071
evmScanApiKey: asOptional(asEither(asString, asArray(asString))),
5172
gasStationApiKey: asOptional(asString),
52-
infuraProjectId: asOptional(asString),
5373
nowNodesApiKey: asOptional(asString),
5474
poktPortalApiKey: asOptional(asString),
5575
quiknodeApiKey: asOptional(asString)

src/ethereum/fees/feeProviders.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import {
88
} from 'edge-core-js/types'
99

1010
import { fetchInfo } from '../../common/network'
11+
import {
12+
getRandomServiceKey,
13+
getServiceKeyIndex
14+
} from '../../common/serviceKeys'
1115
import { hexToDecimal, pickRandom } from '../../common/utils'
1216
import {
1317
GAS_PRICE_SANITY_CHECK,
@@ -338,36 +342,55 @@ export const getEvmScanApiKey = (
338342
log: EdgeLog,
339343
serverUrl: string
340344
): string | string[] | undefined => {
341-
const { evmScanApiKey, etherscanApiKey, bscscanApiKey, polygonscanApiKey } =
342-
initOptions
345+
const {
346+
evmScanApiKey,
347+
etherscanApiKey,
348+
bscscanApiKey,
349+
polygonscanApiKey,
350+
serviceKeys
351+
} = initOptions
343352

344353
const { currencyCode } = info
354+
const serviceKey = getRandomServiceKey(
355+
serviceKeys,
356+
getServiceKeyIndex(serverUrl)
357+
)
358+
359+
if (serviceKey != null) return serviceKey
345360

346361
// If we have a server URL and it's etherscan.io, use the Ethereum API key
347362
if (serverUrl.includes('etherscan.io')) {
348363
if (etherscanApiKey == null)
349364
throw new Error(`Missing etherscanApiKey for etherscan.io`)
365+
log.warn(
366+
"INIT OPTION 'etherscanApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
367+
)
350368
return etherscanApiKey
351369
}
352370

353-
if (evmScanApiKey != null) return evmScanApiKey
371+
if (evmScanApiKey != null) {
372+
log.warn(
373+
"INIT OPTION 'evmScanApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
374+
)
375+
return evmScanApiKey
376+
}
354377

355378
// For networks that don't support Etherscan v2, fall back to network-specific keys
356379
if (currencyCode === 'ETH' && etherscanApiKey != null) {
357380
log.warn(
358-
"INIT OPTION 'etherscanApiKey' IS DEPRECATED. USE 'evmScanApiKey' INSTEAD"
381+
"INIT OPTION 'etherscanApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
359382
)
360383
return etherscanApiKey
361384
}
362385
if (currencyCode === 'BNB' && bscscanApiKey != null) {
363386
log.warn(
364-
"INIT OPTION 'bscscanApiKey' IS DEPRECATED. USE 'evmScanApiKey' INSTEAD"
387+
"INIT OPTION 'bscscanApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
365388
)
366389
return bscscanApiKey
367390
}
368391
if (currencyCode === 'POL' && polygonscanApiKey != null) {
369392
log.warn(
370-
"INIT OPTION 'polygonscanApiKey' IS DEPRECATED. USE 'evmScanApiKey' INSTEAD"
393+
"INIT OPTION 'polygonscanApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
371394
)
372395
return polygonscanApiKey
373396
}

src/ethereum/networkAdapters/AmberdataAdapter.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { base58ToHexAddress } from '../../tron/tronUtils'
1+
import {
2+
getRandomServiceKey,
3+
getServiceKeyIndex
4+
} from '../../common/serviceKeys'
25
import { EthereumNetworkUpdate } from '../EthereumNetwork'
36
import { asRpcResultString } from '../ethereumTypes'
47
import { NetworkAdapter } from './networkAdapterTypes'
@@ -51,10 +54,22 @@ export class AmberdataAdapter extends NetworkAdapter<AmberdataAdapterConfig> {
5154
method: string,
5255
params: string[] = []
5356
): Promise<any> {
54-
const { amberdataApiKey = '' } = this.ethEngine.initOptions
57+
const { amberdataApiKey, serviceKeys } = this.ethEngine.initOptions
58+
59+
return await this.serialServers(async url => {
60+
let apiKey = getRandomServiceKey(serviceKeys, getServiceKeyIndex(url))
61+
62+
if (apiKey === null && amberdataApiKey != null) {
63+
this.ethEngine.log.warn(
64+
"INIT OPTION 'amberdataApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
65+
)
66+
apiKey = amberdataApiKey
67+
}
68+
69+
if (apiKey == null) {
70+
throw new Error('Missing API key')
71+
}
5572

56-
return await this.serialServers(async baseUrl => {
57-
const url = `${this.config.servers[0]}`
5873
const body = {
5974
jsonrpc: '2.0',
6075
method: method,
@@ -64,7 +79,7 @@ export class AmberdataAdapter extends NetworkAdapter<AmberdataAdapterConfig> {
6479
const response = await this.ethEngine.fetchCors(url, {
6580
headers: {
6681
'x-amberdata-blockchain-id': this.config.amberdataBlockchainId,
67-
'x-api-key': amberdataApiKey,
82+
'x-api-key': apiKey,
6883
'Content-Type': 'application/json'
6984
},
7085
method: 'POST',
@@ -81,14 +96,27 @@ export class AmberdataAdapter extends NetworkAdapter<AmberdataAdapterConfig> {
8196

8297
// TODO: Clean return type
8398
private async fetchGetAmberdataApi(path: string): Promise<any> {
84-
const { amberdataApiKey = '' } = this.ethEngine.initOptions
99+
const { amberdataApiKey, serviceKeys } = this.ethEngine.initOptions
85100

86101
return await this.serialServers(async baseUrl => {
87-
const url = `${base58ToHexAddress}${path}`
102+
let apiKey = getRandomServiceKey(serviceKeys, getServiceKeyIndex(baseUrl))
103+
104+
if (apiKey == null && amberdataApiKey != null) {
105+
this.ethEngine.log.warn(
106+
"INIT OPTION 'amberdataApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
107+
)
108+
apiKey = amberdataApiKey
109+
}
110+
111+
if (apiKey == null) {
112+
throw new Error('Missing API key')
113+
}
114+
115+
const url = `${baseUrl}${path}`
88116
const response = await this.ethEngine.fetchCors(url, {
89117
headers: {
90118
'x-amberdata-blockchain-id': this.config.amberdataBlockchainId,
91-
'x-api-key': amberdataApiKey
119+
'x-api-key': apiKey
92120
}
93121
})
94122
if (!response.ok) {

src/ethereum/networkAdapters/BlockbookWsAdapter.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { asBoolean, asJSON, asMaybe, asObject, asString } from 'cleaners'
22
import WebSocket from 'isomorphic-ws'
33

44
import { makePeriodicTask, PeriodicTask } from '../../common/periodicTask'
5+
import {
6+
getRandomServiceKey,
7+
getServiceKeyIndex
8+
} from '../../common/serviceKeys'
59
import { pickRandomOne } from '../../common/utils'
610
import { EthereumEngine } from '../EthereumEngine'
711
import { EthereumInitOptions } from '../ethereumTypes'
@@ -207,9 +211,19 @@ export class BlockbookWsAdapter extends NetworkAdapter<BlockbookWsAdapterConfig>
207211

208212
while (servers.length > 0) {
209213
const server = pickRandomOne(this.servers)
214+
let apiKey = getRandomServiceKey(
215+
this.ethEngine.initOptions.serviceKeys,
216+
getServiceKeyIndex(server.url)
217+
)
210218

211219
if (server.keyType != null) {
212-
const apiKey = this.ethEngine.initOptions[server.keyType]
220+
if (apiKey == null) {
221+
apiKey = this.ethEngine.initOptions[server.keyType]
222+
if (apiKey !== null)
223+
this.ethEngine.log.warn(
224+
`INIT OPTION '${server.keyType}' IS DEPRECATED. USE 'serviceKeys' INSTEAD`
225+
)
226+
}
213227

214228
// Check for missing API key
215229
if (apiKey == null) {

src/ethereum/networkAdapters/BlockchairAdapter.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
getRandomServiceKey,
3+
getServiceKeyIndex
4+
} from '../../common/serviceKeys'
15
import { safeErrorMessage } from '../../common/utils'
26
import { EthereumNetworkUpdate } from '../EthereumNetwork'
37
import {
@@ -87,13 +91,24 @@ export class BlockchairAdapter extends NetworkAdapter<BlockchairAdapterConfig> {
8791
path: string,
8892
includeKey: boolean = false
8993
): Promise<any> {
90-
const { blockchairApiKey } = this.ethEngine.initOptions
94+
const { blockchairApiKey, serviceKeys } = this.ethEngine.initOptions
9195

9296
return await this.serialServers(async baseUrl => {
93-
const keyParam =
94-
includeKey && blockchairApiKey != null ? `&key=${blockchairApiKey}` : ''
95-
const url = `${baseUrl}${path}`
96-
const response = await this.ethEngine.fetchCors(`${url}${keyParam}`)
97+
let apiKey: string | undefined
98+
99+
if (includeKey) {
100+
apiKey = getRandomServiceKey(serviceKeys, getServiceKeyIndex(baseUrl))
101+
102+
if (apiKey == null && blockchairApiKey != null) {
103+
this.ethEngine.log.warn(
104+
"INIT OPTION 'blockchairApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
105+
)
106+
apiKey = blockchairApiKey
107+
}
108+
}
109+
110+
const url = `${baseUrl}${path}${apiKey != null ? `&key=${apiKey}` : ''}`
111+
const response = await this.ethEngine.fetchCors(url)
97112
if (!response.ok) {
98113
const resBody = await response.text()
99114
this.throwError(response, 'fetchGetBlockchair', url, resBody)

src/ethereum/networkAdapters/BlockcypherAdapter.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { EdgeTransaction } from 'edge-core-js/types'
22
import parse from 'url-parse'
33

4+
import {
5+
getRandomServiceKey,
6+
getServiceKeyIndex
7+
} from '../../common/serviceKeys'
48
import { BroadcastResults } from '../EthereumNetwork'
59
import { NetworkAdapter } from './networkAdapterTypes'
610

@@ -49,13 +53,21 @@ export class BlockcypherAdapter extends NetworkAdapter<BlockcypherAdapterConfig>
4953
body: any,
5054
baseUrl: string
5155
): Promise<any> {
52-
const { blockcypherApiKey } = this.ethEngine.initOptions
53-
let apiKey = ''
54-
if (blockcypherApiKey != null && blockcypherApiKey.length > 5) {
55-
apiKey = '&token=' + blockcypherApiKey
56+
const { blockcypherApiKey, serviceKeys } = this.ethEngine.initOptions
57+
let apiKey = getRandomServiceKey(serviceKeys, getServiceKeyIndex(baseUrl))
58+
59+
if (
60+
apiKey == null &&
61+
blockcypherApiKey != null &&
62+
blockcypherApiKey.length > 5
63+
) {
64+
apiKey = blockcypherApiKey
65+
this.ethEngine.log.warn(
66+
"INIT OPTION 'blockcypherApiKey' IS DEPRECATED. USE 'serviceKeys' INSTEAD"
67+
)
5668
}
5769

58-
const url = `${baseUrl}/${cmd}${apiKey}`
70+
const url = `${baseUrl}/${cmd}${apiKey != null ? `&token=${apiKey}` : ''}`
5971
const response = await this.ethEngine.fetchCors(url, {
6072
headers: {
6173
Accept: 'application/json',

0 commit comments

Comments
 (0)