Skip to content

Commit 1c427db

Browse files
authored
Merge pull request #2880 from tallyhowallet/add_ethereum_chain
2 parents 733ff47 + d9d0fcc commit 1c427db

File tree

15 files changed

+330
-63
lines changed

15 files changed

+330
-63
lines changed

background/accounts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { HexString } from "./types"
44

55
/**
66
* An account balance at a particular time and block height, on a particular
7-
* network. Flexible enough to represent base assets like ETH and BTC as well
7+
* network. Flexible enough to represent base assets like ETH as well
88
* application-layer tokens like ERC-20s.
99
*/
1010
export type AccountBalance = {

background/constants/base-assets.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,8 @@ const BNB: NetworkBaseAsset = {
5555
decimals: 18,
5656
}
5757

58-
const BTC: NetworkBaseAsset = {
59-
/**
60-
* To persist base asset to indexDB chainID must be declared.
61-
*/
62-
chainID: "",
63-
name: "Bitcoin",
64-
symbol: "BTC",
65-
decimals: 8,
66-
}
67-
6858
export const BASE_ASSETS_BY_CUSTOM_NAME = {
6959
ETH,
70-
BTC,
7160
MATIC,
7261
RBTC,
7362
AVAX,

background/constants/coin-types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
* Limited extension-specific list of coin types by asset symbol.
66
*/
77
export const coinTypesByAssetSymbol = {
8-
BTC: 0,
9-
"Testnet BTC": 1,
108
ETH: 60,
119
RBTC: 137,
1210
MATIC: 966,

background/constants/currencies.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,8 @@ export const BNB: NetworkBaseAsset & Required<CoinGeckoAsset> = {
9090
},
9191
}
9292

93-
export const BTC: NetworkBaseAsset & Required<CoinGeckoAsset> = {
94-
...BASE_ASSETS_BY_CUSTOM_NAME.BTC,
95-
coinType: coinTypesByAssetSymbol.BTC,
96-
metadata: {
97-
coinGeckoID: "bitcoin",
98-
tokenLists: [],
99-
websiteURL: "https://bitcoin.org",
100-
},
101-
}
102-
10393
export const BUILT_IN_NETWORK_BASE_ASSETS = [
10494
ETH,
105-
BTC,
10695
MATIC,
10796
RBTC,
10897
OPTIMISTIC_ETH,

background/constants/networks.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { FeatureFlags, isEnabled } from "../features"
2-
import { EVMNetwork, Network } from "../networks"
2+
import { EVMNetwork } from "../networks"
33
import {
44
ARBITRUM_NOVA_ETH,
55
ARBITRUM_ONE_ETH,
66
AVAX,
77
BNB,
8-
BTC,
98
ETH,
109
GOERLI_ETH,
1110
MATIC,
@@ -85,13 +84,6 @@ export const GOERLI: EVMNetwork = {
8584
coingeckoPlatformID: "ethereum",
8685
}
8786

88-
export const BITCOIN: Network = {
89-
name: "Bitcoin",
90-
baseAsset: BTC,
91-
family: "BTC",
92-
coingeckoPlatformID: "bitcoin",
93-
}
94-
9587
export const DEFAULT_NETWORKS = [
9688
ETHEREUM,
9789
POLYGON,

background/networks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
* Each supported network family is generally incompatible with others from a
1515
* transaction, consensus, and/or wire format perspective.
1616
*/
17-
export type NetworkFamily = "EVM" | "BTC"
17+
export type NetworkFamily = "EVM"
1818

1919
// Should be structurally compatible with FungibleAsset or much code will
2020
// likely explode.
@@ -34,7 +34,7 @@ export type Network = {
3434
baseAsset: NetworkBaseAsset & CoinGeckoAsset
3535
family: NetworkFamily
3636
chainID?: string
37-
coingeckoPlatformID: string
37+
coingeckoPlatformID?: string
3838
}
3939

4040
/**

background/services/chain/db.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ import {
1010
NetworkBaseAsset,
1111
} from "../../networks"
1212
import { FungibleAsset } from "../../assets"
13-
import { BASE_ASSETS, DEFAULT_NETWORKS, GOERLI, POLYGON } from "../../constants"
13+
import {
14+
BASE_ASSETS,
15+
CHAIN_ID_TO_RPC_URLS,
16+
DEFAULT_NETWORKS,
17+
GOERLI,
18+
POLYGON,
19+
} from "../../constants"
1420

1521
export type Transaction = AnyEVMTransaction & {
1622
dataSource: "alchemy" | "local"
@@ -73,6 +79,8 @@ export class ChainDatabase extends Dexie {
7379

7480
private baseAssets!: Dexie.Table<NetworkBaseAsset, number>
7581

82+
private rpcUrls!: Dexie.Table<{ chainID: string; rpcUrls: string[] }, string>
83+
7684
constructor(options?: DexieOptions) {
7785
super("tally/chain", options)
7886
this.version(1).stores({
@@ -153,6 +161,16 @@ export class ChainDatabase extends Dexie {
153161
this.version(6).stores({
154162
baseAssets: "&chainID,symbol,name",
155163
})
164+
165+
this.version(7).stores({
166+
rpcUrls: "&chainID, rpcUrls",
167+
})
168+
}
169+
170+
async initialize(): Promise<void> {
171+
await this.initializeBaseAssets()
172+
await this.initializeRPCs()
173+
await this.initializeEVMNetworks()
156174
}
157175

158176
async getLatestBlock(network: Network): Promise<AnyEVMBlock | null> {
@@ -183,14 +201,70 @@ export class ChainDatabase extends Dexie {
183201
)
184202
}
185203

204+
async addEVMNetwork({
205+
chainName,
206+
chainID,
207+
decimals,
208+
symbol,
209+
assetName,
210+
rpcUrls,
211+
}: {
212+
chainName: string
213+
chainID: string
214+
decimals: number
215+
symbol: string
216+
assetName: string
217+
rpcUrls: string[]
218+
}): Promise<void> {
219+
await this.networks.put({
220+
name: chainName,
221+
chainID,
222+
family: "EVM",
223+
baseAsset: {
224+
decimals,
225+
symbol,
226+
name: assetName,
227+
chainID,
228+
},
229+
})
230+
// A bit awkward that we are adding the base asset to the network as well
231+
// as to its own separate table - but lets forge on for now.
232+
await this.addBaseAsset(assetName, symbol, chainID, decimals)
233+
await this.addRpcUrls(chainID, rpcUrls)
234+
}
235+
186236
async getAllEVMNetworks(): Promise<EVMNetwork[]> {
187237
return this.networks.where("family").equals("EVM").toArray()
188238
}
189239

240+
private async addBaseAsset(
241+
name: string,
242+
symbol: string,
243+
chainID: string,
244+
decimals: number
245+
) {
246+
await this.baseAssets.put({
247+
decimals,
248+
name,
249+
symbol,
250+
chainID,
251+
})
252+
}
253+
190254
async getAllBaseAssets(): Promise<NetworkBaseAsset[]> {
191255
return this.baseAssets.toArray()
192256
}
193257

258+
async initializeRPCs(): Promise<void> {
259+
await Promise.all(
260+
Object.entries(CHAIN_ID_TO_RPC_URLS).map(async ([chainId, rpcUrls]) => {
261+
if (rpcUrls) {
262+
await this.addRpcUrls(chainId, rpcUrls)
263+
}
264+
})
265+
)
266+
}
267+
194268
async initializeBaseAssets(): Promise<void> {
195269
await this.updateBaseAssets(BASE_ASSETS)
196270
}
@@ -210,6 +284,31 @@ export class ChainDatabase extends Dexie {
210284
)
211285
}
212286

287+
async getRpcUrlsByChainId(chainId: string): Promise<string[]> {
288+
const rpcUrls = await this.rpcUrls.where({ chainId }).first()
289+
if (rpcUrls) {
290+
return rpcUrls.rpcUrls
291+
}
292+
throw new Error(`No RPC Found for ${chainId}`)
293+
}
294+
295+
private async addRpcUrls(chainID: string, rpcUrls: string[]): Promise<void> {
296+
const existingRpcUrlsForChain = await this.rpcUrls.get(chainID)
297+
if (existingRpcUrlsForChain) {
298+
existingRpcUrlsForChain.rpcUrls.push(...rpcUrls)
299+
existingRpcUrlsForChain.rpcUrls = [
300+
...new Set(existingRpcUrlsForChain.rpcUrls),
301+
]
302+
await this.rpcUrls.put(existingRpcUrlsForChain)
303+
} else {
304+
await this.rpcUrls.put({ chainID, rpcUrls })
305+
}
306+
}
307+
308+
async getAllRpcUrls(): Promise<{ chainID: string; rpcUrls: string[] }[]> {
309+
return this.rpcUrls.toArray()
310+
}
311+
213312
async getAllSavedTransactionHashes(): Promise<IndexableTypeArray> {
214313
return this.chainTransactions.orderBy("hash").keys()
215314
}

background/services/chain/index.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
OPTIMISM_GAS_ORACLE_ADDRESS,
6363
} from "./utils/optimismGasPriceOracle"
6464
import KeyringService from "../keyring"
65+
import type { ValidatedAddEthereumChainParameter } from "../internal-ethereum-provider"
6566

6667
// The number of blocks to query at a time for historic asset transfers.
6768
// Unfortunately there's no "right" answer here that works well across different
@@ -299,10 +300,11 @@ export default class ChainService extends BaseService<Events> {
299300
override async internalStartService(): Promise<void> {
300301
await super.internalStartService()
301302

302-
await this.initializeBaseAssets()
303+
await this.db.initialize()
303304
await this.initializeNetworks()
304305
const accounts = await this.getAccountsToTrack()
305306
const trackedNetworks = await this.getTrackedNetworks()
307+
306308
const transactions = await this.db.getAllTransactions()
307309

308310
this.emitter.emit("initializeActivities", { transactions, accounts })
@@ -344,12 +346,8 @@ export default class ChainService extends BaseService<Events> {
344346
)
345347
}
346348

347-
async initializeBaseAssets(): Promise<void> {
348-
await this.db.initializeBaseAssets()
349-
}
350-
351349
async initializeNetworks(): Promise<void> {
352-
await this.db.initializeEVMNetworks()
350+
const rpcUrls = await this.db.getAllRpcUrls()
353351
if (!this.supportedNetworks.length) {
354352
this.supportedNetworks = await this.db.getAllEVMNetworks()
355353
}
@@ -362,7 +360,10 @@ export default class ChainService extends BaseService<Events> {
362360
evm: Object.fromEntries(
363361
this.supportedNetworks.map((network) => [
364362
network.chainID,
365-
makeSerialFallbackProvider(network),
363+
makeSerialFallbackProvider(
364+
network,
365+
rpcUrls.find((v) => v.chainID === network.chainID)?.rpcUrls || []
366+
),
366367
])
367368
),
368369
}
@@ -1863,4 +1864,19 @@ export default class ChainService extends BaseService<Events> {
18631864
)
18641865
}
18651866
}
1867+
1868+
// Used to add non-default chains via wallet_addEthereumChain
1869+
async addCustomChain(
1870+
chainInfo: ValidatedAddEthereumChainParameter
1871+
): Promise<void> {
1872+
await this.db.addEVMNetwork({
1873+
chainName: chainInfo.chainName,
1874+
chainID: chainInfo.chainId,
1875+
decimals: chainInfo.nativeCurrency.decimals,
1876+
symbol: chainInfo.nativeCurrency.symbol,
1877+
assetName: chainInfo.nativeCurrency.name,
1878+
rpcUrls: chainInfo.rpcUrls,
1879+
})
1880+
this.supportedNetworks = await this.db.getAllEVMNetworks()
1881+
}
18661882
}

background/services/chain/serial-fallback-provider.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { utils } from "ethers"
1010
import { getNetwork } from "@ethersproject/networks"
1111
import {
1212
SECOND,
13-
CHAIN_ID_TO_RPC_URLS,
1413
ALCHEMY_SUPPORTED_CHAIN_IDS,
1514
RPC_METHOD_PROVIDER_ROUTING,
1615
} from "../../constants"
@@ -931,7 +930,8 @@ export default class SerialFallbackProvider extends JsonRpcProvider {
931930
}
932931

933932
export function makeSerialFallbackProvider(
934-
network: EVMNetwork
933+
network: EVMNetwork,
934+
rpcUrls: string[]
935935
): SerialFallbackProvider {
936936
const alchemyProviderCreators = ALCHEMY_SUPPORTED_CHAIN_IDS.has(
937937
network.chainID
@@ -956,12 +956,10 @@ export function makeSerialFallbackProvider(
956956
]
957957
: []
958958

959-
const genericProviders = (CHAIN_ID_TO_RPC_URLS[network.chainID] || []).map(
960-
(rpcUrl) => ({
961-
type: "generic" as const,
962-
creator: () => new JsonRpcProvider(rpcUrl),
963-
})
964-
)
959+
const genericProviders = rpcUrls.map((rpcUrl) => ({
960+
type: "generic" as const,
961+
creator: () => new JsonRpcProvider(rpcUrl),
962+
}))
965963

966964
return new SerialFallbackProvider(network, [
967965
// Prefer alchemy as the primary provider when available

0 commit comments

Comments
 (0)