Skip to content

Commit

Permalink
Add Mezo testnet as a builtin network (#3780)
Browse files Browse the repository at this point in the history
Adds Mezo testnet as a built-in network, there's no support yet for
reverse name resolution. The price for the network base asset is
resolved as the price for tBTC.


## Testing env
```
SUPPORT_MEZO_NETWORK=true
```

Latest build:
[extension-builds-3780](https://github.com/tahowallet/extension/suites/34891830413/artifacts/2653025518)
(as of Wed, 26 Feb 2025 02:09:25 GMT).
  • Loading branch information
Shadowfiend authored Feb 28, 2025
2 parents 8ed24a2 + a510874 commit 81c917f
Show file tree
Hide file tree
Showing 23 changed files with 195 additions and 16 deletions.
1 change: 1 addition & 0 deletions .env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ SUPPORT_NFT_SEND=false
SHOW_ISLAND_UI=false
SUPPORT_THE_ISLAND=false
SUPPORT_THE_ISLAND_ON_TENDERLY=false
SUPPORT_MEZO_NETWORK=false
USE_MAINNET_FORK=false
ARBITRUM_FORK_RPC=https://rpc.tenderly.co/fork/2fc2cf12-5c58-439f-9b5e-967bfd02191a
TESTNET_TAHO_DEPLOYER_ADDRESS=0x55B180c3470dA8E31761d45468e4E61DbE13Eb9B
Expand Down
13 changes: 13 additions & 0 deletions background/constants/base-assets.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CoinGeckoAsset } from "../assets"
import { NetworkBaseAsset } from "../networks"

const ETH: NetworkBaseAsset = {
Expand Down Expand Up @@ -108,9 +109,21 @@ const ZK_SYNC_ETH: NetworkBaseAsset = {
chainID: "324",
}

const MEZO_BTC: NetworkBaseAsset & Required<CoinGeckoAsset> = {
chainID: "31611",
name: "Bitcoin",
symbol: "BTC",
decimals: 18,
metadata: {
coinGeckoID: "bitcoin",
tokenLists: [],
},
}

export const BASE_ASSETS_BY_CUSTOM_NAME = {
ETH,
MATIC,
MEZO_BTC,
RBTC,
AVAX,
BNB,
Expand Down
1 change: 1 addition & 0 deletions background/constants/coin-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Limited extension-specific list of coin types by asset symbol.
*/
export const coinTypesByAssetSymbol = {
BTC: 0,
ETH: 60,
RBTC: 137,
MATIC: 966,
Expand Down
6 changes: 6 additions & 0 deletions background/constants/currencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@ export const BNB: NetworkBaseAsset & Required<CoinGeckoAsset> = {
},
}

export const MEZO_BTC: NetworkBaseAsset & Required<CoinGeckoAsset> = {
...BASE_ASSETS_BY_CUSTOM_NAME.MEZO_BTC,
coinType: 0,
}

export const BUILT_IN_NETWORK_BASE_ASSETS = [
MEZO_BTC,
ETH,
MATIC,
RBTC,
Expand Down
15 changes: 15 additions & 0 deletions background/constants/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
OPTIMISTIC_ETH,
RBTC,
ZK_SYNC_ETH,
MEZO_BTC,
} from "./currencies"

export const ETHEREUM: EVMNetwork = {
Expand Down Expand Up @@ -102,8 +103,16 @@ export const ZK_SYNC: EVMNetwork = {
family: "EVM",
}

export const MEZO_TESTNET: EVMNetwork = {
name: "Matsnet",
baseAsset: MEZO_BTC,
chainID: "31611",
family: "EVM",
}

export const DEFAULT_NETWORKS = [
ETHEREUM,
...wrapIfEnabled(FeatureFlags.SUPPORT_MEZO_NETWORK, MEZO_TESTNET),
POLYGON,
OPTIMISM,
SEPOLIA,
Expand Down Expand Up @@ -156,6 +165,7 @@ export const NETWORK_BY_CHAIN_ID = {
[ARBITRUM_SEPOLIA.chainID]: ARBITRUM_SEPOLIA,
[FORK.chainID]: FORK,
[ZK_SYNC.chainID]: ZK_SYNC,
[MEZO_TESTNET.chainID]: MEZO_TESTNET,
}

export const TEST_NETWORK_BY_CHAIN_ID = new Set(
Expand Down Expand Up @@ -195,6 +205,11 @@ export const CHAIN_ID_TO_RPC_URLS: {
[chainId: string]: string[]
} = {
[ROOTSTOCK.chainID]: ["https://public-node.rsk.co"],
[MEZO_TESTNET.chainID]: [
"https://rpc.test.mezo.org",
"https://mezo-testnet.drpc.org",
"wss://mezo-testnet.drpc.org",
],
[POLYGON.chainID]: [
// This one sometimes returns 0 for eth_getBalance
"https://polygon-rpc.com",
Expand Down
1 change: 1 addition & 0 deletions background/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const RuntimeFlag = {
SUPPORT_THE_ISLAND: process.env.SUPPORT_THE_ISLAND === "true",
SUPPORT_THE_ISLAND_ON_TENDERLY:
process.env.SUPPORT_THE_ISLAND_ON_TENDERLY === "true",
SUPPORT_MEZO_NETWORK: process.env.SUPPORT_MEZO_NETWORK === "true",
} as const

type BuildTimeFlagType = keyof typeof BuildTimeFlag
Expand Down
1 change: 1 addition & 0 deletions background/lib/multicall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const MULTICALL_CONTRACT_ADDRESS =

export const CHAIN_SPECIFIC_MULTICALL_CONTRACT_ADDRESSES = {
"324": "0x47898B2C52C957663aE9AB46922dCec150a2272c", // zksync era
"31611": "0xBB4A6fb567ac8aA20503D3a6964d655439aa1F12", // Mezo testnet
} as { [chainId: string]: string }

export const MULTICALL_ABI = [
Expand Down
10 changes: 1 addition & 9 deletions background/services/chain/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
EVMNetwork,
Network,
NetworkBaseAsset,
sameNetwork,
} from "../../networks"
import { FungibleAsset } from "../../assets"
import {
Expand Down Expand Up @@ -369,17 +368,10 @@ export class ChainDatabase extends Dexie {
}

private async initializeEVMNetworks(): Promise<void> {
const existingNetworks = await this.getAllEVMNetworks()
await Promise.all(
ChainDatabase.defaultSettings.DEFAULT_NETWORKS.map(
async (defaultNetwork) => {
if (
!existingNetworks.some((network) =>
sameNetwork(network, defaultNetwork),
)
) {
await this.networks.put(defaultNetwork)
}
await this.networks.put(defaultNetwork)
},
),
)
Expand Down
30 changes: 30 additions & 0 deletions background/services/indexing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ETHEREUM,
FIAT_CURRENCIES,
HOUR,
MEZO_TESTNET,
MINUTE,
NETWORK_BY_CHAIN_ID,
OPTIMISM,
Expand Down Expand Up @@ -55,6 +56,7 @@ import {
isTrustedAsset,
isSameAsset,
} from "../../redux-slices/utils/asset-utils"
import { wrapIfEnabled } from "../../features"

// Transactions seen within this many blocks of the chain tip will schedule a
// token refresh sooner than the standard rate.
Expand Down Expand Up @@ -791,6 +793,7 @@ export default class IndexingService extends BaseService<Events> {
basicPrices = await Promise.all(
[
ETHEREUM,
...wrapIfEnabled("SUPPORT_MEZO_NETWORK", MEZO_TESTNET),
ARBITRUM_ONE,
OPTIMISM,
BINANCE_SMART_CHAIN,
Expand All @@ -799,6 +802,33 @@ export default class IndexingService extends BaseService<Events> {
].map(async (network: EVMNetwork) => {
const provider =
this.chainService.providerForNetworkOrThrow(network)

const TBTC: SmartContractFungibleAsset = {
name: "tBTC v2",
symbol: "tBTC",
decimals: 18,
homeNetwork: ETHEREUM,
contractAddress: "0x18084fba666a33d37592fa2633fd49a74dd93a88",
}

if (network === MEZO_TESTNET) {
const ethProvider =
this.chainService.providerForNetworkOrThrow(ETHEREUM)

const pricePoint = await getUSDPriceForTokens(
[TBTC],
ETHEREUM,
ethProvider,
).then((pricePoints) =>
getPricePoint(
MEZO_TESTNET.baseAsset,
pricePoints[TBTC.contractAddress],
),
)

return pricePoint
}

return getUSDPriceForBaseAsset(network, provider)
}),
)
Expand Down
4 changes: 3 additions & 1 deletion background/services/indexing/tests/index.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,9 @@ describe("IndexingService", () => {
fetchJsonStub
.getCalls()
.toString()
.match(/ethereum,matic-network,rootstock,avalanche-2,binancecoin/i),
.match(
/ethereum,matic-network,rootstock,bitcoin,avalanche-2,binancecoin/i,
),
).toBeTruthy()
})
})
Expand Down
3 changes: 3 additions & 0 deletions background/services/name/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import {
ensResolverFor,
unsResolver,
rnsResolver,
mezoResolver,
} from "./resolvers"
import PreferenceService from "../preferences"
import { isFulfilledPromise } from "../../lib/utils/type-guards"
import { wrapIfEnabled } from "../../features"

export { NameResolverSystem }

Expand Down Expand Up @@ -123,6 +125,7 @@ export default class NameService extends BaseService<Events> {
ensResolverFor(chainService),
unsResolver(),
rnsResolver(),
...wrapIfEnabled("SUPPORT_MEZO_NETWORK", mezoResolver()),
]

preferenceService.emitter.on(
Expand Down
3 changes: 3 additions & 0 deletions background/services/name/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import addressBookResolverFor from "./address-book"
import knownContractResolverFor from "./known-contracts"
import unsResolver from "./uns"
import rnsResolver from "./rns"
import mezoResolver from "./mezo"

const resolvers = {
ensResolverFor,
addressBookResolverFor,
knownContractResolverFor,
unsResolver,
rnsResolver,
mezoResolver,
}

type ResolverConstructors = ReturnType<
Expand All @@ -24,4 +26,5 @@ export {
knownContractResolverFor,
unsResolver,
rnsResolver,
mezoResolver,
}
57 changes: 57 additions & 0 deletions background/services/name/resolvers/mezo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { fetchJson } from "@ethersproject/web"
import { AddressOnNetwork, NameOnNetwork } from "../../../accounts"
import { MEZO_TESTNET } from "../../../constants"
import { sameNetwork } from "../../../networks"
import { NameResolver } from "../name-resolver"
import { normalizeEVMAddress } from "../../../lib/utils"

const MEZO_SUPPORTED_NETWORKS = [MEZO_TESTNET]

export default function mezoResolver(): NameResolver<"MEZO"> {
return {
type: "MEZO",
canAttemptNameResolution(): boolean {
return true
},
canAttemptAvatarResolution(): boolean {
return false
},
canAttemptAddressResolution({ name, network }: NameOnNetwork): boolean {
return (
name.endsWith(".mezo") &&
MEZO_SUPPORTED_NETWORKS.some((supportedNetwork) =>
sameNetwork(network, supportedNetwork),
)
)
},

async lookUpAddressForName({
name,
network,
}: NameOnNetwork): Promise<AddressOnNetwork | undefined> {
type ResolveNameData = {
mezoId: string
evmAddress: string
btcAddress: null | string
}

const data: ResolveNameData = await fetchJson(
`https://portal.api.mezo.org/api/v2/external/accounts?mezoId=${name}`,
)

return {
address: normalizeEVMAddress(data.evmAddress),
network,
}
},
async lookUpAvatar() {
return undefined
},
async lookUpNameForAddress(
_: AddressOnNetwork,
): Promise<NameOnNetwork | undefined> {
// FIXME: Missing endpoint
return undefined
},
}
}
2 changes: 1 addition & 1 deletion background/services/preferences/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export default class PreferenceService extends BaseService<Events> {
}

async getSelectedAccount(): Promise<AddressOnNetwork> {
return (await this.db.getPreferences())?.selectedAccount
return (await this.db.getPreferences()).selectedAccount
}

async setSelectedAccount(addressNetwork: AddressOnNetwork): Promise<void> {
Expand Down
18 changes: 16 additions & 2 deletions background/services/redux/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
} from ".."

import { HexString, NormalizedEVMAddress } from "../../types"
import { SignedTransaction } from "../../networks"
import { SignedTransaction, sameNetwork } from "../../networks"
import { AccountBalance, AddressOnNetwork, NameOnNetwork } from "../../accounts"
import { Eligible, ReferrerStats } from "../island/types"

Expand Down Expand Up @@ -1344,11 +1344,25 @@ export default class ReduxService extends BaseService<never> {
"initializeSelectedAccount",
async (dbAddressNetwork: AddressOnNetwork) => {
if (dbAddressNetwork) {
// Wait until chain service starts and populates supported networks
await this.chainService.started()
// TBD: naming the normal reducer and async thunks
// Initialize redux from the db
// !!! Important: this action belongs to a regular reducer.
// NOT to be confused with the setNewCurrentAddress asyncThunk
this.store.dispatch(setSelectedAccount(dbAddressNetwork))
const { address, network } = dbAddressNetwork
let supportedNetwork = this.chainService.supportedNetworks.find(
(net) => sameNetwork(network, net),
)

if (!supportedNetwork) {
// eslint-disable-next-line prefer-destructuring
supportedNetwork = this.chainService.supportedNetworks[0]
}

this.store.dispatch(
setSelectedAccount({ address, network: supportedNetwork }),
)
} else {
// Update currentAddress in db if it's not set but it is in the store
// should run only one time
Expand Down
1 change: 1 addition & 0 deletions ui/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@
"popupEdition": "Community Edition",
"protocol": {
"mainnet": "Mainnet",
"mezoTestnet": "Mezo (Testnet)",
"beta": "Mainnet (beta)",
"testnet": "Test Network",
"l2": "L2 scaling solution",
Expand Down
10 changes: 9 additions & 1 deletion ui/components/Shared/SharedAssetIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ type Props = {
symbol: string
}

const hardcodedIcons = new Set(["ETH", "MATIC", "DOGGO", "RBTC", "AVAX", "BNB"])
const hardcodedIcons = new Set([
"ETH",
"MATIC",
"DOGGO",
"RBTC",
"AVAX",
"BNB",
"BTC",
])

// Passes IPFS and Arweave through HTTP gateway
function getAsHttpURL(anyURL: string) {
Expand Down
Loading

0 comments on commit 81c917f

Please sign in to comment.