diff --git a/.github/workflows/deploy-wallet-at-merge-to-main.yml b/.github/workflows/deploy-wallet-at-merge-to-main.yml index 49dba86968d..7730da950e7 100644 --- a/.github/workflows/deploy-wallet-at-merge-to-main.yml +++ b/.github/workflows/deploy-wallet-at-merge-to-main.yml @@ -28,10 +28,9 @@ jobs: run: yarn build env: GENERATE_SOURCEMAP: false - REACT_APP_ALIAS: "Namada Testnet" - REACT_APP_CHAIN_ID: "qc-testnet-5.1.025a61165acd05e" - REACT_APP_LEDGER_URL: "https://d3brk13lbhxfdb.cloudfront.net/qc-testnet-5.1.025a61165acd05e" - REACT_APP_FAUCET: "atest1v4ehgw36gc6yxvpjxccyzvphxycrxw2xxsuyydesxgcnjs3cg9znwv3cxgmnj32yxy6rssf5tcqjm3" + REACT_APP_NAMADA_ALIAS: "Namada Testnet" + REACT_APP_NAMADA_CHAIN_ID: "qc-testnet-5.1.025a61165acd05e" + REACT_APP_NAMADA_URL: "https://d3brk13lbhxfdb.cloudfront.net/qc-testnet-5.1.025a61165acd05e" - name: Deploy to Netlify uses: nwtgck/actions-netlify@v1.2.3 diff --git a/.github/workflows/deploy-wallet-at-pr.yml b/.github/workflows/deploy-wallet-at-pr.yml index b0b9efc97a9..84e0fee00a1 100644 --- a/.github/workflows/deploy-wallet-at-pr.yml +++ b/.github/workflows/deploy-wallet-at-pr.yml @@ -78,10 +78,9 @@ jobs: run: yarn build env: GENERATE_SOURCEMAP: false - REACT_APP_ALIAS: "Namada Testnet" - REACT_APP_CHAIN_ID: "qc-testnet-5.1.025a61165acd05e" - REACT_APP_LEDGER_URL: "https://d3brk13lbhxfdb.cloudfront.net/qc-testnet-5.1.025a61165acd05e" - REACT_APP_FAUCET: "atest1v4ehgw36gc6yxvpjxccyzvphxycrxw2xxsuyydesxgcnjs3cg9znwv3cxgmnj32yxy6rssf5tcqjm3" + REACT_APP_NAMADA_ALIAS: "Namada Testnet" + REACT_APP_NAMADA_CHAIN_ID: "qc-testnet-5.1.025a61165acd05e" + REACT_APP_NAMADA_URL: "https://d3brk13lbhxfdb.cloudfront.net/qc-testnet-5.1.025a61165acd05e" - name: Deploy to Netlify uses: nwtgck/actions-netlify@v1.2.3 diff --git a/apps/extension/package.json b/apps/extension/package.json index 66dc99681af..822c2913264 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -23,6 +23,7 @@ "wasm:build": "./scripts/build.sh" }, "dependencies": { + "@anoma/chains": "0.1.0", "@anoma/components": "0.1.0", "@anoma/crypto": "0.1.0", "@anoma/rpc": "0.1.0", @@ -53,6 +54,7 @@ "@types/uuid": "^8.3.4", "@types/webextension-polyfill": "^0.9.0", "copy-webpack-plugin": "^11.0.0", + "dotenv": "^16.0.3", "eslint": "^8.23.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.5.0", diff --git a/apps/extension/src/App/Accounts/AddAccount.tsx b/apps/extension/src/App/Accounts/AddAccount.tsx index a04fd7071af..227cf8d3f82 100644 --- a/apps/extension/src/App/Accounts/AddAccount.tsx +++ b/apps/extension/src/App/Accounts/AddAccount.tsx @@ -12,7 +12,7 @@ import { AccountType, DerivedAccount } from "@anoma/types"; import { ExtensionRequester } from "extension"; import { Ports } from "router"; import { DeriveAccountMsg } from "background/keyring"; -import { chains } from "config"; +import { chains, defaultChainId } from "@anoma/chains"; import { AddAccountContainer, AddAccountForm, @@ -109,7 +109,7 @@ const AddAccount: React.FC = ({ const bip44Prefix = "m/44"; const zip32Prefix = "m/32"; - const { coinType } = chains[0].bip44; + const { coinType } = chains[defaultChainId].bip44; useEffect(() => { const isValid = validateAccount( diff --git a/apps/extension/src/background/index.ts b/apps/extension/src/background/index.ts index 23b1bdd5d5e..a881223561a 100644 --- a/apps/extension/src/background/index.ts +++ b/apps/extension/src/background/index.ts @@ -10,7 +10,7 @@ import { ExtensionMessenger, } from "extension"; import { Ports, KVPrefix } from "router"; -import { chains } from "config"; +import { defaultChainId, chains } from "@anoma/chains"; import { ChainsService, init as initChains } from "./chains"; import { KeyRingService, init as initKeyRing } from "./keyring"; @@ -31,8 +31,8 @@ const router = new ExtensionRouter( router.addGuard(ExtensionGuards.checkOriginIsValid); router.addGuard(ExtensionGuards.checkMessageIsInternal); -const chainsService = new ChainsService(store, chains); -const keyRingService = new KeyRingService(store); +const chainsService = new ChainsService(store, [chains[defaultChainId]]); +const keyRingService = new KeyRingService(store, defaultChainId); // Initialize messages and handlers initChains(router, chainsService); diff --git a/apps/extension/src/background/keyring/keyring.ts b/apps/extension/src/background/keyring/keyring.ts index ae097964f03..3973cf377c4 100644 --- a/apps/extension/src/background/keyring/keyring.ts +++ b/apps/extension/src/background/keyring/keyring.ts @@ -18,7 +18,7 @@ import { } from "@anoma/shared"; import { IStore, Store } from "@anoma/storage"; import { AccountType, Bip44Path, DerivedAccount, SignedTx } from "@anoma/types"; -import { chains } from "config"; +import { chains } from "@anoma/chains"; import { Crypto } from "./crypto"; import { KeyRingStatus, KeyStore } from "./types"; @@ -59,7 +59,7 @@ export class KeyRing { constructor( protected readonly kvStore: KVStore, - protected readonly chainId: string = chains[0].chainId + protected readonly chainId: string ) { this._keyStore = new Store(KEYSTORE_KEY, kvStore); } @@ -126,7 +126,7 @@ export class KeyRing { try { const mnemonic = Mnemonic.from_phrase(phrase); const seed = mnemonic.to_seed(); - const { coinType } = chains[0].bip44; + const { coinType } = chains[this.chainId].bip44; const path = `m/44'/${coinType}'/0'/0`; const bip44 = new HDWallet(seed); const account = bip44.derive(path); @@ -159,11 +159,12 @@ export class KeyRing { public static deriveTransparentAccount( seed: Uint8Array, path: Bip44Path, - parentId: string + parentId: string, + chainId: string ): DerivedAccountInfo { const { account, change, index = 0 } = path; const root = "m/44'"; - const { coinType } = chains[0].bip44; + const { coinType } = chains[chainId].bip44; const derivationPath = [ root, `${coinType}'`, @@ -260,7 +261,8 @@ export class KeyRing { const transparentAccount = KeyRing.deriveTransparentAccount( seed, path, - parentId + parentId, + this.chainId ); id = transparentAccount.id; address = transparentAccount.address; diff --git a/apps/extension/src/background/keyring/service.ts b/apps/extension/src/background/keyring/service.ts index a7881b2957d..e0ee8ee7415 100644 --- a/apps/extension/src/background/keyring/service.ts +++ b/apps/extension/src/background/keyring/service.ts @@ -9,8 +9,11 @@ import { IbcTransfer, Transfer } from "@anoma/shared"; export class KeyRingService { private _keyRing: KeyRing; - constructor(protected readonly kvStore: KVStore) { - this._keyRing = new KeyRing(kvStore); + constructor( + protected readonly kvStore: KVStore, + protected readonly chainId: string + ) { + this._keyRing = new KeyRing(kvStore, chainId); } lock(): { status: KeyRingStatus } { diff --git a/apps/extension/src/config/chains.ts b/apps/extension/src/config/chains.ts deleted file mode 100644 index 92ba2a932c3..00000000000 --- a/apps/extension/src/config/chains.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Chain, IBCCurrency } from "@anoma/types"; - -const bech32Prefix = "namada"; -export const chains: Chain[] = []; -const DEFAULT_CHAIN_ID = "namada-75a7e12.69483d59a9fb174"; - -const ibcCurrency: IBCCurrency = { - coinDenom: "NAM", - coinMinimalDenom: "nam", - coinDecimals: 6, - originChainId: DEFAULT_CHAIN_ID, - originCurrency: undefined, - paths: [ - { - portId: "transfer", - channelId: "channel-0", - }, - ], -}; - -/** - * Define chains to embed within extension - */ -const defaultChain: Chain = { - rpc: "http://localhost:26657", - rest: "http://localhost:1317", - chainId: DEFAULT_CHAIN_ID, - chainName: "Namada Testnet", - stakeCurrency: ibcCurrency, - bip44: { - // coinType = testnet (all coins) - Slip-0044 - // See: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - coinType: 1, - }, - bech32Config: { - bech32PrefixAccAddr: bech32Prefix, - bech32PrefixAccPub: `${bech32Prefix}pub`, - bech32PrefixValAddr: `${bech32Prefix}valoper`, - bech32PrefixValPub: `${bech32Prefix}valoperpub`, - bech32PrefixConsAddr: `${bech32Prefix}valcons`, - bech32PrefixConsPub: `${bech32Prefix}valconspub`, - }, - currencies: [ibcCurrency], - feeCurrencies: [ibcCurrency], - gasPriceStep: { low: 0.01, average: 0.025, high: 0.03 }, // Optional - features: ["ibc-transfer"], - beta: false, -}; - -chains.push(defaultChain); diff --git a/apps/extension/src/config/index.ts b/apps/extension/src/config/index.ts index 50439db4216..d571f77bc5f 100644 --- a/apps/extension/src/config/index.ts +++ b/apps/extension/src/config/index.ts @@ -1,2 +1 @@ export * from "./argon2"; -export * from "./chains"; diff --git a/apps/extension/src/provider/Anoma.test.ts b/apps/extension/src/provider/Anoma.test.ts index d721fbc21ae..4c95eefdc9b 100644 --- a/apps/extension/src/provider/Anoma.test.ts +++ b/apps/extension/src/provider/Anoma.test.ts @@ -19,7 +19,7 @@ import { import { KVKeys } from "router"; import { init } from "test/init"; -import { chains as defaultChains } from "../config"; +import { chains as defaultChains } from "@anoma/chains"; import { chain, keyStore, NAM, password } from "./data.mock"; import { KeyRing, KEYSTORE_KEY } from "background/keyring"; @@ -53,7 +53,7 @@ describe("Anoma", () => { iDBStore.set(KVKeys.Chains, [chain]); const storedChains = await anoma.chains(); - expect(storedChains).toEqual([...defaultChains, chain]); + expect(storedChains).toEqual([...Object.values(defaultChains), chain]); }); it("should return all accounts", async () => { diff --git a/apps/extension/src/provider/data.mock.ts b/apps/extension/src/provider/data.mock.ts index c5407460f64..36fbbbeb55b 100644 --- a/apps/extension/src/provider/data.mock.ts +++ b/apps/extension/src/provider/data.mock.ts @@ -1,4 +1,4 @@ -import { AccountType, Chain } from "@anoma/types"; +import { AccountType, Chain, Extensions } from "@anoma/types"; import { KdfType, KeyStore } from "background/keyring"; export const NAM = { @@ -15,41 +15,21 @@ export const NAM = { }; export const chain: Chain = { - rpc: "http://localhost:26657", - rest: "http://localhost:1317", - chainId: "namada-test.XXXXXXXXXXXX", - chainName: "Namada Testnet", - stakeCurrency: { - coinDenom: "NAM", - coinMinimalDenom: "nam", - coinDecimals: 6, - }, + alias: "Namada Testnet", + bech32Prefix: "atest", bip44: { - coinType: 9999, + // coinType = testnet (all coins) - Slip-0044 + // See: https://github.com/satoshilabs/slips/blob/master/slip-0044.md + coinType: 1, }, - bech32Config: { - bech32PrefixAccAddr: "namada", - bech32PrefixAccPub: "namadapub", - bech32PrefixValAddr: "namadavaloper", - bech32PrefixValPub: "namadavaloperpub", - bech32PrefixConsAddr: "namadavalcons", - bech32PrefixConsPub: "namadavalconspub", - }, - currencies: [NAM], - feeCurrencies: [ - { - coinDenom: "NAM", - coinMinimalDenom: "nam", - coinDecimals: 6, - }, - ], - gasPriceStep: { - low: 0.01, - average: 0.025, - high: 0.03, + rpc: "http://localhost:26657", + chainId: "namada-75a7e12.69483d59a9fb174", + extension: Extensions["keplr"], + currency: { + token: "Namada", + symbol: "NAM", + gasPriceStep: { low: 0.01, average: 0.025, high: 0.03 }, // Optional }, - features: ["ibc-transfer"], - beta: false, }; export const keyStore: KeyStore = { diff --git a/apps/extension/src/test/init.ts b/apps/extension/src/test/init.ts index ea2d2ba3ec8..a268df45b74 100644 --- a/apps/extension/src/test/init.ts +++ b/apps/extension/src/test/init.ts @@ -6,7 +6,7 @@ import { ExtensionRequester, } from "../extension"; import { Ports, KVPrefix } from "../router"; -import { chains } from "../config"; +import { chains } from "@anoma/chains"; import { ChainsService, init as initChains } from "../background/chains"; import { KeyRingService, @@ -37,8 +37,8 @@ export const init = (): { anoma: Anoma; iDBStore: KVStoreMock; extStore: KVStoreMock; - chainsService: ChainsService, - keyRingService: KeyRingService + chainsService: ChainsService; + keyRingService: KeyRingService; } => { const messenger = new ExtensionMessengerMock(); @@ -58,8 +58,14 @@ export const init = (): { extStore ); - const chainsService = new ChainsService(iDBStore as KVStore, chains); - const keyRingService = new KeyRingService(iDBStore as KVStore); + const chainsService = new ChainsService( + iDBStore as KVStore, + Object.values(chains) + ); + const keyRingService = new KeyRingService( + iDBStore as KVStore, + "namada-test.XXXXXXXX" + ); // Initialize messages and handlers initChains(router, chainsService); @@ -75,6 +81,6 @@ export const init = (): { iDBStore, extStore, chainsService, - keyRingService + keyRingService, }; }; diff --git a/apps/extension/webpack.config.js b/apps/extension/webpack.config.js index 03b72555765..25bf22ed241 100644 --- a/apps/extension/webpack.config.js +++ b/apps/extension/webpack.config.js @@ -6,7 +6,25 @@ const ExtensionReloader = require("webpack-extension-reloader"); const createStyledComponentsTransformer = require("typescript-plugin-styled-components").default; -const { NODE_ENV, TARGET } = process.env; +// Load .env from namada-interface: +require("dotenv").config({ path: "../namada-interface/.env" }); + +const { + NODE_ENV, + TARGET, + // React .env variables from interface for shared chain configuration: + REACT_APP_NAMADA_ALIAS, + REACT_APP_NAMADA_CHAIN_ID, + REACT_APP_NAMADA_URL, + REACT_APP_NAMADA_BECH32_PREFIX, + REACT_APP_COSMOS_ALIAS, + REACT_APP_COSMOS_CHAIN_ID, + REACT_APP_COSMOS_URL, + REACT_APP_OSMOSIS_ALIAS, + REACT_APP_OSMOSIS_CHAIN_ID, + REACT_APP_OSMOSIS_URL, +} = process.env; + const MANIFEST_VERSION = TARGET === "firefox" ? "v2" : "v3"; const MANIFEST_BASE_PATH = `./src/manifest/_base.json`; const MANIFEST_BASE_VERSION_PATH = `./src/manifest/${MANIFEST_VERSION}/_base.json`; @@ -28,6 +46,26 @@ const copyPatterns = [ }, ]; +// Set up environment values +const env = {}; +const envVariables = { + REACT_APP_NAMADA_ALIAS, + REACT_APP_NAMADA_CHAIN_ID, + REACT_APP_NAMADA_URL, + REACT_APP_NAMADA_BECH32_PREFIX, + REACT_APP_COSMOS_ALIAS, + REACT_APP_COSMOS_CHAIN_ID, + REACT_APP_COSMOS_URL, + REACT_APP_OSMOSIS_ALIAS, + REACT_APP_OSMOSIS_CHAIN_ID, + REACT_APP_OSMOSIS_URL, +}; + +// Stringify to ensure values are wrapped in quotes +for (const key in envVariables) { + env[key] = JSON.stringify(envVariables[key]); +} + const plugins = [ new CopyPlugin({ patterns: copyPatterns, @@ -41,6 +79,12 @@ const plugins = [ new webpack.ProvidePlugin({ Buffer: ["buffer", "Buffer"], }), + // Provide environment variables to extension: + new webpack.DefinePlugin({ + process: { + env, + }, + }), ]; if (NODE_ENV === "development") { diff --git a/apps/namada-interface/.env.sample b/apps/namada-interface/.env.sample index f7e2e28f1d1..c830a53e8f5 100644 --- a/apps/namada-interface/.env.sample +++ b/apps/namada-interface/.env.sample @@ -1,23 +1,19 @@ GENERATE_SOURCEMAP=false -# Mainnet -REACT_APP_ALIAS=Namada Mainnet -REACT_APP_CHAIN_ID=namada-1.5.32ccad5356012a7 -REACT_APP_LEDGER_URL=http://127.0.0.1:26657 -REACT_APP_FAUCET=atest1v4ehgw36gc6yxvpjxccyzvphxycrxw2xxsuyydesxgcnjs3cg9znwv3cxgmnj32yxy6rssf5tcqjm3 +# Specify the following if you wish to override the defaults defined in @anoma/chains: -# THE BELOW IS OPTIONAL, ONLY NEEDED FOR TESTING IBC -# Chain A -# REACT_APP_CHAIN_A_ALIAS=Namada - Instance 1 -# REACT_APP_CHAIN_A_ID=anoma-test.91b126950d65405f034 -# REACT_APP_CHAIN_A_URL=http://127.0.0.1 -# REACT_APP_CHAIN_A_PORT=27657 -# REACT_APP_CHAIN_A_FAUCET=atest1v4ehgw36gcmnvsf5g5mryv2zxapns3psx5m5vwzxx9zrs3fjg4pnvs2zx56nvdjzxqe5xvf5frv6wu +# NAMADA +REACT_APP_NAMADA_ALIAS=Namada Testnet +REACT_APP_NAMADA_CHAIN_ID=namada-1.5.32ccad5356012a7 +REACT_APP_NAMADA_URL=http://127.0.0.1:26657 +REACT_APP_NAMADA_BECH32_PREFIX=atest -# Chain B -# REACT_APP_CHAIN_B_ALIAS=Namada - Instance 2 -# REACT_APP_CHAIN_B_ID=anoma-test.1f8181bd3cebc012a1f -# REACT_APP_CHAIN_B_URL=http://127.0.0.1 -# REACT_APP_CHAIN_B_PORT=28657 -# REACT_APP_CHAIN_B_FAUCET=atest1v4ehgw36x4pnyveexaqny3f5xqu5vwfh89zyvd3exymrydjrx5uyy3pnxvmnyvf4x56yzwfed7w3cw +# COSMOS +REACT_APP_COSMOS_ALIAS=Cosmos Testnet +REACT_APP_COSMOS_CHAIN_ID=cosmos-testnet.123412341234 +REACT_APP_COSMOS_URL=http://127.0.0.1:12345 +# OSMOSIS +REACT_APP_OSMOSIS_ALIAS=Osmosis Testnet +REACT_APP_OSMOSIS_CHAIN_ID=osmosis-testnet.123412341234 +REACT_APP_OSMOSIS_URL=http://127.0.0.1:54321 diff --git a/apps/namada-interface/src/App/AccountOverview/AccountOverview.tsx b/apps/namada-interface/src/App/AccountOverview/AccountOverview.tsx index ee2488f3c17..b89875bba04 100644 --- a/apps/namada-interface/src/App/AccountOverview/AccountOverview.tsx +++ b/apps/namada-interface/src/App/AccountOverview/AccountOverview.tsx @@ -1,7 +1,7 @@ -import { useState } from "react"; +import { useContext, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { Anoma } from "@anoma/integrations"; +import { chains } from "@anoma/chains"; import { useAppSelector, useAppDispatch } from "store"; import { AccountsState, addAccounts } from "slices/accounts"; import { SettingsState } from "slices/settings"; @@ -26,11 +26,19 @@ import { TotalHeading, } from "./AccountOverview.components"; import { formatCurrency } from "@anoma/utils"; +import { AppContext } from "App/App"; +import { Account, ExtensionKey, Extensions } from "@anoma/types"; export const AccountOverview = (): JSX.Element => { const navigate = useNavigate(); const dispatch = useAppDispatch(); - const [isExtensionConnected, setIsExtensionConnected] = useState(false); + const [isExtensionConnected, setIsExtensionConnected] = useState< + Record + >({ + anoma: false, + keplr: false, + metamask: false, + }); const [isConnectingToExtension, setIsConnectingToExtension] = useState(false); const { derived } = useAppSelector((state) => state.accounts); @@ -38,22 +46,34 @@ export const AccountOverview = (): JSX.Element => { const { chainId, fiatCurrency } = useAppSelector( (state) => state.settings ); + + const context = useContext(AppContext); + const integration = context?.integrations[chainId]; + const chain = chains[chainId]; + const extensionAlias = Extensions[chain.extension.id].alias; + const [total, setTotal] = useState(0); const handleConnectExtension = async (): Promise => { try { - const anoma = new Anoma(); - if (anoma.detect()) { + if (integration?.detect()) { setIsConnectingToExtension(true); - await anoma.connect(); - const accounts = await anoma.fetchAccounts(); + await integration?.connect(); + const accounts = await integration?.accounts(); if (accounts) { - dispatch(addAccounts(accounts)); + dispatch(addAccounts(accounts as Account[])); } - setIsExtensionConnected(true); + + setIsExtensionConnected({ + ...isExtensionConnected, + [chain.extension.id]: true, + }); } } catch (e) { - setIsExtensionConnected(false); + setIsExtensionConnected({ + ...isExtensionConnected, + [chain.extension.id]: false, + }); setIsConnectingToExtension(false); } }; @@ -106,13 +126,13 @@ export const AccountOverview = (): JSX.Element => { )} {!derived[chainId] && ( - {!isExtensionConnected && ( + {!isExtensionConnected[chain.extension.id] && ( )} diff --git a/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx b/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx index cb0b09ac335..0259aca3a3a 100644 --- a/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx +++ b/apps/namada-interface/src/App/AccountOverview/DerivedAccounts/DerivedAccounts.tsx @@ -3,6 +3,7 @@ import { ThemeContext } from "styled-components"; import { useNavigate } from "react-router-dom"; import Config from "config"; +import { chains } from "@anoma/chains"; import { useAppDispatch, useAppSelector } from "store"; import { AccountsState } from "slices/accounts"; import { BalancesState, fetchBalances } from "slices/balances"; @@ -81,7 +82,6 @@ const DerivedAccounts = ({ setTotal }: Props): JSX.Element => { ); const { api } = Config; - const chains = Config.chain; const { alias } = chains[chainId] || {}; const transparentBalances = balancesByChainId[chainId] || {}; diff --git a/apps/namada-interface/src/App/App.tsx b/apps/namada-interface/src/App/App.tsx index 069134f4a80..4b0c0c5da9b 100644 --- a/apps/namada-interface/src/App/App.tsx +++ b/apps/namada-interface/src/App/App.tsx @@ -1,11 +1,13 @@ /* eslint-disable max-len */ -import { useState, useEffect } from "react"; +import { useState, useEffect, createContext } from "react"; import { useLocation, Location } from "react-router-dom"; import { createBrowserHistory } from "history"; import { AnimatePresence } from "framer-motion"; import { ThemeProvider } from "styled-components/macro"; // internal +import { Anoma, Keplr } from "@anoma/integrations"; +import { chains } from "@anoma/chains"; import { getTheme, loadColorMode, @@ -13,7 +15,6 @@ import { ColorMode, } from "utils/theme"; import { TopLevelRoute, locationToTopLevelRoute } from "./types"; - import { TopNavigation } from "./TopNavigation"; import { AppContainer, @@ -54,6 +55,33 @@ const getShouldUsePlaceholderTheme = (location: Location): boolean => { return isStaking; }; +type Integrations = Record; +export const AppContext = createContext<{ integrations: Integrations } | null>( + null +); + +const IntegrationInstances = (() => { + const instances: Integrations = {}; + let chainId: keyof typeof chains; + for (chainId in chains) { + const chain = chains[chainId]; + const extensionId = chain.extension.id; + switch (extensionId) { + case "anoma": { + instances[chainId] = new Anoma(chain); + break; + } + case "keplr": { + instances[chainId] = new Keplr(chain); + break; + } + default: + break; + } + } + return instances; +})(); + function App(): JSX.Element { const initialColorMode = loadColorMode(); const [colorMode, setColorMode] = useState(initialColorMode); @@ -69,25 +97,27 @@ function App(): JSX.Element { return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ); } diff --git a/apps/namada-interface/src/App/Settings/SettingsWalletSettings/SettingsWalletSettings.tsx b/apps/namada-interface/src/App/Settings/SettingsWalletSettings/SettingsWalletSettings.tsx index 5380a9a537c..fb779e5a05a 100644 --- a/apps/namada-interface/src/App/Settings/SettingsWalletSettings/SettingsWalletSettings.tsx +++ b/apps/namada-interface/src/App/Settings/SettingsWalletSettings/SettingsWalletSettings.tsx @@ -1,5 +1,7 @@ import { useNavigate } from "react-router-dom"; -import { Chain } from "config/chain"; +import { chains } from "@anoma/chains"; +import { Chain } from "@anoma/types"; + import { setFiatCurrency, setChainId, SettingsState } from "slices/settings"; import { useAppDispatch, useAppSelector } from "store"; import { Currencies } from "currencies"; @@ -11,7 +13,6 @@ import { Tooltip } from "components/Tooltip"; import { Icon, IconName } from "components/Icon"; import { Select, Option } from "components/Select"; import { InputContainer } from "App/AccountOverview/AccountOverview.components"; -import Config from "config"; import { BackButton } from "App/Token/TokenSend/TokenSendForm.components"; import { ButtonsContainer, SettingsContent } from "../Settings.components"; @@ -19,12 +20,11 @@ export const SettingsWalletSettings = (): JSX.Element => { const dispatch = useAppDispatch(); const navigate = useNavigate(); - const chains = Object.values(Config.chain); const { chainId } = useAppSelector((state) => state.settings); - const networks = Object.values(chains).map(({ id, alias }: Chain) => ({ + const networks = Object.values(chains).map(({ chainId, alias }: Chain) => ({ label: alias, - value: id, + value: chainId, })); const currencies: Option[] = Currencies.map((currency) => ({ diff --git a/apps/namada-interface/src/App/Token/IBCTransfer/IBCTransfer.tsx b/apps/namada-interface/src/App/Token/IBCTransfer/IBCTransfer.tsx index 144a6213782..dc11e583bc8 100644 --- a/apps/namada-interface/src/App/Token/IBCTransfer/IBCTransfer.tsx +++ b/apps/namada-interface/src/App/Token/IBCTransfer/IBCTransfer.tsx @@ -1,5 +1,7 @@ import { useEffect, useState } from "react"; +import { chains } from "@anoma/chains"; +import { Chain, Symbols, Tokens, TokenType } from "@anoma/types"; import { useAppDispatch, useAppSelector } from "store"; import { AccountsState } from "slices/accounts"; import { addChannel, ChannelsState } from "slices/channels"; @@ -25,8 +27,6 @@ import { Option, Select } from "components/Select"; import { Icon, IconName } from "components/Icon"; import { Button, ButtonVariant } from "components/Button"; import { Address } from "../Transfers/TransferDetails.components"; -import Config from "config"; -import { Symbols, Tokens, TokenType } from "@anoma/types"; import { BalancesState, fetchBalances } from "slices/balances"; const IBCTransfer = (): JSX.Element => { @@ -43,13 +43,14 @@ const IBCTransfer = (): JSX.Element => { const { isIbcTransferSubmitting, transferError, events } = useAppSelector((state) => state.transfers); - const chains = Config.chain; - const chain = chains[chainId]; - const { ibc = [] } = chain || {}; + /* const chain = Object.values(chains).find((chain: Chain) => chain.chainId === chainId); */ + const ibc = Object.values(chains).filter( + (chain: Chain) => chain.chainId !== chainId + ); const defaultIbcChain = chains[ibc[0]?.chainId] || null; const [selectedChainId, setSelectedChainId] = useState( - defaultIbcChain ? defaultIbcChain.id : "" + defaultIbcChain ? defaultIbcChain.chainId : "" ); const selectDestinationChainData = ibc.map((ibcChain) => ({ @@ -131,7 +132,7 @@ const IBCTransfer = (): JSX.Element => { const handleFocus = (e: React.ChangeEvent): void => e.target.select(); - const { portId = "transfer" } = defaultIbcChain || {}; + const { portId = "transfer" } = defaultIbcChain.ibc || {}; useEffect(() => { return () => { @@ -141,7 +142,6 @@ const IBCTransfer = (): JSX.Element => { }, []); useEffect(() => { - const { ibc = [] } = chains[chainId] || {}; if (ibc.length > 0) { const selectedChain = ibc[0].chainId; setSelectedChainId(selectedChain); diff --git a/apps/namada-interface/src/App/Token/TokenDetails.tsx b/apps/namada-interface/src/App/Token/TokenDetails.tsx index aa907a841b7..d0bad598e6e 100644 --- a/apps/namada-interface/src/App/Token/TokenDetails.tsx +++ b/apps/namada-interface/src/App/Token/TokenDetails.tsx @@ -1,7 +1,7 @@ import { useParams, useNavigate } from "react-router-dom"; -import { Account } from "@anoma/types"; +import { chains } from "@anoma/chains"; +import { Account, Chain, Tokens } from "@anoma/types"; -import config from "config"; import { TopLevelRoute } from "App/types"; import { AccountsState } from "slices/accounts"; import { TransfersState } from "slices/transfers"; @@ -12,7 +12,6 @@ import { formatRoute, stringFromTimestamp } from "@anoma/utils"; import { Button, ButtonVariant } from "components/Button"; import { Heading, HeadingLevel } from "components/Heading"; import { NavigationContainer } from "components/NavigationContainer"; -import { Tokens } from "@anoma/types"; import { ButtonsContainer, TokenDetailContainer, @@ -30,7 +29,9 @@ const TokenDetails = (): JSX.Element => { const { id = "" } = useParams(); const { derived } = useAppSelector((state) => state.accounts); const { chainId } = useAppSelector((state) => state.settings); - const { ibc } = config.chain[chainId]; + const { ibc } = + Object.values(chains).find((chain: Chain) => chain.chainId === chainId) ?? + {}; const { transactions: accountTransactions } = useAppSelector( (state) => state.transfers ); diff --git a/apps/namada-interface/src/App/Token/TokenReceive/TokenReceive.tsx b/apps/namada-interface/src/App/Token/TokenReceive/TokenReceive.tsx index 8b427c32e50..49e30f47770 100644 --- a/apps/namada-interface/src/App/Token/TokenReceive/TokenReceive.tsx +++ b/apps/namada-interface/src/App/Token/TokenReceive/TokenReceive.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQRCode } from "next-qrcode"; -import Config from "config"; import { AccountsState } from "slices/accounts"; import { SettingsState } from "slices/settings"; import { useAppSelector } from "store"; @@ -32,7 +31,6 @@ const TokenReceive = (): JSX.Element => { string | undefined >(); const accounts = derived[chainId] || {}; - const { accountIndex } = Config.chain[chainId] || {}; const accountsArray = Object.values(accounts); @@ -62,7 +60,6 @@ const TokenReceive = (): JSX.Element => { const text = `${protocol}//${host}${formatRoute( TopLevelRoute.TokenSendTarget, { - accountIndex, // TODO: Fix this: target: selectedAccountAddress || "", } diff --git a/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx b/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx index 86765497c93..94cde7ed48a 100644 --- a/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx +++ b/apps/namada-interface/src/App/Token/TokenSend/TokenSend.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; import { AccountsState } from "slices/accounts"; -import { setChainId, SettingsState } from "slices/settings"; +import { SettingsState } from "slices/settings"; import { TransferType } from "slices/transfers"; -import { useAppDispatch, useAppSelector } from "store"; +import { useAppSelector } from "store"; import { PAYMENT_ADDRESS_LENGTH, @@ -25,7 +25,6 @@ import { } from "./TokenSend.components"; import { BalancesState } from "slices/balances"; import { useParams } from "react-router-dom"; -import Config from "config"; export const parseTarget = (target: string): TransferType | undefined => { if ( @@ -45,15 +44,13 @@ export const parseTarget = (target: string): TransferType | undefined => { }; type Params = { - accountIndex: string; target: string; }; const TokenSend = (): JSX.Element => { const { derived } = useAppSelector((state) => state.accounts); const { chainId } = useAppSelector((state) => state.settings); - const { accountIndex, target } = useParams(); - const dispatch = useAppDispatch(); + const { target } = useParams(); const balancesByChain = useAppSelector( (state) => state.balances @@ -138,20 +135,6 @@ const TokenSend = (): JSX.Element => { const defaultTokenType = "NAM"; const [token, setToken] = useState(defaultTokenType); - useEffect(() => { - if (accountIndex) { - const chains = Object.values(Config.chain); - const targetChain = chains.find( - (chain) => chain.accountIndex === parseInt(accountIndex) - ); - // Token Send should match the target's network, otherwise - // transactions will not succeed: - if (targetChain && targetChain.id !== chainId) { - dispatch(setChainId(targetChain.id)); - } - } - }, [accountIndex]); - useEffect(() => { // Set selectedTransparentAccountAddress to first account if one // hasn't been set: diff --git a/apps/namada-interface/src/App/Token/Transfers/TransferDetails.tsx b/apps/namada-interface/src/App/Token/Transfers/TransferDetails.tsx index 4e67d7e0d7e..b28e41cae8d 100644 --- a/apps/namada-interface/src/App/Token/Transfers/TransferDetails.tsx +++ b/apps/namada-interface/src/App/Token/Transfers/TransferDetails.tsx @@ -1,6 +1,7 @@ import { useNavigate, useParams } from "react-router-dom"; +import { chains } from "@anoma/chains"; +import { Chain } from "@anoma/types"; -import Config, { Chain } from "config"; import { TransfersState } from "slices/transfers"; import { useAppSelector } from "store"; import { stringFromTimestamp } from "@anoma/utils"; @@ -11,7 +12,6 @@ import { Address, TransferDetailContainer } from "./TransferDetails.components"; import { BackButton } from "../TokenSend/TokenSendForm.components"; import { Icon, IconName } from "components/Icon"; import { ButtonsContainer, TransfersContent } from "./Transfers.components"; -import { IBCConfigItem } from "config/ibc"; type TransferDetailsParams = { id: string; @@ -41,8 +41,9 @@ const TransferDetail = (): JSX.Element => { (transaction) => transaction.appliedHash === appliedHash ) || {}; - const sourceChain: Chain | undefined = - (sourceChainId && Config.chain[sourceChainId]) || undefined; + const sourceChain: Chain | undefined = Object.values(chains).find( + (chain: Chain) => chain.chainId === sourceChainId + ); const { chainId, @@ -52,19 +53,7 @@ const TransferDetail = (): JSX.Element => { destinationPort, } = ibcTransfer || {}; - const chains = Config.chain; - const chain = chainId && chains[chainId]; - const { ibc = [] } = chain || {}; - const ibcChainConfig: IBCConfigItem | undefined = ibc.find( - (ibcConfig) => ibcConfig.chainId === chainId - ); - - const destinationChain = ibcChainConfig - ? chains[ibcChainConfig.chainId] - : undefined; - - const chainAlias = destinationChain?.alias; - + const chainAlias = sourceChain?.alias; const dateTimeFormatted = stringFromTimestamp(timestamp); return ( diff --git a/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx b/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx index bccf9a7a0e3..8c0fedb1913 100644 --- a/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx +++ b/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx @@ -48,7 +48,8 @@ import { AppStore } from "store/store"; import { setChainId, SettingsState } from "slices/settings"; import TopNavigationLoggedIn from "./topNavigationLoggedIn"; import { Icon, IconName } from "components/Icon"; -import Config, { Chain } from "config"; +import { chains } from "@anoma/chains"; +import { Chain } from "@anoma/types"; /** * this is rendered in one of 2 places depending of the screen size @@ -137,10 +138,9 @@ const SecondMenuRow = (props: SecondMenuRowProps): React.ReactElement => { }; // transform for select component - const chains = Object.values(Config.chain); - const networks = Object.values(chains).map(({ id, alias }: Chain) => ({ + const networks = Object.values(chains).map(({ chainId, alias }: Chain) => ({ label: alias, - value: id, + value: chainId, })); return ( diff --git a/apps/namada-interface/src/config/chain.ts b/apps/namada-interface/src/config/chain.ts deleted file mode 100644 index 875bbb1847b..00000000000 --- a/apps/namada-interface/src/config/chain.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { - getUrl, - getUrlProtocol, - sanitize, - stripInvalidCharacters, -} from "@anoma/utils"; -import IBCConfig, { IBCConfigItem } from "./ibc"; -import { Network } from "@anoma/rpc"; - -export type Protocol = "http" | "https" | "ws" | "wss"; - -const { - // Default local ledger config - REACT_APP_LEDGER_URL, - REACT_APP_LEDGER_PORT, - REACT_APP_CHAIN_ID, - REACT_APP_FAUCET, - REACT_APP_ALIAS, - - // Alternatively, specify a local IBC chain: - // IBC - CHAIN A - REACT_APP_CHAIN_A_ALIAS, - REACT_APP_CHAIN_A_ID, - REACT_APP_CHAIN_A_URL, - REACT_APP_CHAIN_A_PORT, - REACT_APP_CHAIN_A_FAUCET, - REACT_APP_CHAIN_A_PORT_ID, - - // IBC - CHAIN B - REACT_APP_CHAIN_B_ALIAS, - REACT_APP_CHAIN_B_ID, - REACT_APP_CHAIN_B_URL, - REACT_APP_CHAIN_B_PORT, - REACT_APP_CHAIN_B_FAUCET, - REACT_APP_CHAIN_B_PORT_ID, -} = process.env; - -export type NetworkConfig = Network & { - wsProtocol: Protocol; -}; - -export type Chain = { - id: string; - alias: string; - accountIndex: number; - network: NetworkConfig; - faucet?: string; - portId?: string; - ibc?: IBCConfigItem[]; -}; - -const DEFAULT_URL = "127.0.0.1"; -const DEFAULT_CHAIN_ALIAS = "Namada"; -const DEFAULT_IBC_PORT = "transfer"; - -/** - * The .env can currently provide configurations for up to 3 chains: - * - A default chain (No-IBC), and/or: - * - IBC-enabled chain 1 - * - This chain will only provide IBC functionality if Chain 2 is defined, - * otherwise will behave as a non-IBC chain - * - IBC-enabled chain 2 - * - This will only be loaded if Chain 1 is also defined. - * - * NOTE: IBC functionality will only be displayed where Chain 1 and Chain 2 exist, - * and will automatically include a reference to the other capable chain in it's config, - * so the user will see a destination chain. - */ - -// Default chain (No IBC Configuration) -const chainId = sanitize(REACT_APP_CHAIN_ID); -const alias = stripInvalidCharacters(REACT_APP_ALIAS) || DEFAULT_CHAIN_ALIAS; -const url = getUrl(sanitize(REACT_APP_LEDGER_URL)); -const protocol = getUrlProtocol(REACT_APP_LEDGER_URL); -const wsProtocol: Protocol = protocol === "https" ? "wss" : "ws"; -const faucet = sanitize(REACT_APP_FAUCET); -const port = parseInt(sanitize(REACT_APP_LEDGER_PORT)) || undefined; - -// IBC Chain A -const chainAId = sanitize(REACT_APP_CHAIN_A_ID); -const chainAAlias = - stripInvalidCharacters(REACT_APP_CHAIN_A_ALIAS) || "IBC - 1"; -const chainAUrl = getUrl(REACT_APP_CHAIN_A_URL) || DEFAULT_URL; -const chainAPort = parseInt(sanitize(REACT_APP_CHAIN_A_PORT)) || undefined; -const chainAProtocol = getUrlProtocol(REACT_APP_CHAIN_A_URL); -const chainAWsProtocol: Protocol = chainAProtocol === "https" ? "wss" : "ws"; -const chainAPortId = sanitize(REACT_APP_CHAIN_A_PORT_ID) || DEFAULT_IBC_PORT; -const chainAFaucet = sanitize(REACT_APP_CHAIN_A_FAUCET) || undefined; -const chainAIbcChains: IBCConfigItem[] = - IBCConfig.development.filter((config) => config.chainId !== chainAId) || []; - -// IBC Chain B -const chainBId = sanitize(REACT_APP_CHAIN_B_ID); -const chainBAlias = - stripInvalidCharacters(REACT_APP_CHAIN_B_ALIAS) || "IBC - 1"; -const chainBUrl = getUrl(REACT_APP_CHAIN_B_URL) || DEFAULT_URL; -const chainBPort = parseInt(sanitize(REACT_APP_CHAIN_B_PORT)) || undefined; -const chainBProtocol = getUrlProtocol(REACT_APP_CHAIN_B_URL); -const chainBWsProtocol: Protocol = chainBProtocol === "https" ? "wss" : "ws"; -const chainBPortId = sanitize(REACT_APP_CHAIN_B_PORT_ID) || DEFAULT_IBC_PORT; -const chainBFaucet = sanitize(REACT_APP_CHAIN_B_FAUCET) || undefined; -const chainBIbcChains: IBCConfigItem[] = - IBCConfig.development.filter((config) => config.chainId !== chainBId) || []; - -export const defaultChainId = - chainId || chainAId || "anoma-test.fd58c789bc11e6c6392"; - -const chains: Chain[] = []; - -if (chainId) { - // If no IBC chains specified in .env, you MUST have a default chain config specified for Namada! - chains.push({ - id: chainId || defaultChainId, - alias, - accountIndex: 0, - faucet, - network: { - url: url || DEFAULT_URL, - port, - protocol, - wsProtocol, - }, - }); -} - -if (chainAId) { - chains.push({ - id: chainAId, - alias: chainAAlias, - accountIndex: 998, - faucet: chainAFaucet, - portId: chainAPortId, - network: { - url: chainAUrl, - port: chainAPort, - protocol: chainAProtocol, - wsProtocol: chainAWsProtocol, - }, - ibc: chainAIbcChains, - }); -} - -if (chainAId && chainBId) { - chains.push({ - id: chainBId, - alias: chainBAlias, - accountIndex: 999, - faucet: chainBFaucet, - portId: chainBPortId, - network: { - url: chainBUrl, - port: chainBPort, - protocol: chainBProtocol, - wsProtocol: chainBWsProtocol, - }, - ibc: chainBIbcChains, - }); -} - -/** - * TODO: Load ChainConfig from JSON. - * This set-up only supports up to 3 chains (one default, two with IBC enabled), - * loaded from .env - */ -const ChainConfig = chains.reduce( - (config: Record, chain: Chain) => { - const chainId = chain.id; - config[chainId] = chain; - return config; - }, - {} -); - -export default ChainConfig; diff --git a/apps/namada-interface/src/config/ibc.ts b/apps/namada-interface/src/config/ibc.ts deleted file mode 100644 index 59c0b0514c0..00000000000 --- a/apps/namada-interface/src/config/ibc.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { sanitize, stripInvalidCharacters } from "@anoma/utils"; - -export type IBCConfigItem = { - chainId: string; - alias: string; -}; - -type IBCConfigType = { - development: IBCConfigItem[]; - production: IBCConfigItem[]; -}; - -const { - REACT_APP_CHAIN_A_ID: chainAId, - REACT_APP_CHAIN_A_ALIAS: chainAAlias, - REACT_APP_CHAIN_B_ID: chainBId, - REACT_APP_CHAIN_B_ALIAS: chainBAlias, -} = process.env; - -/** - * Specify any IBC-enabled chains below per environment. Match the following - * definitions to settings in .env: - */ -const IBCConfig: IBCConfigType = { - development: [ - { - chainId: chainAId ? sanitize(chainAId) : "anoma-test.569195096d4a940e5ee", - alias: chainAAlias - ? stripInvalidCharacters(chainAAlias) - : "Namada - Instance 1", - }, - { - chainId: chainBId ? sanitize(chainBId) : "anoma-test.aa4a0f246ca97b6a903", - alias: chainBAlias - ? stripInvalidCharacters(chainBAlias) - : "Namada - Instance 2", - }, - { - chainId: "gaia", - alias: "Cosmos (Gaia)", - }, - ], - production: [], -}; - -export default IBCConfig; diff --git a/apps/namada-interface/src/config/index.ts b/apps/namada-interface/src/config/index.ts index 06c2d343356..f987ba4147e 100644 --- a/apps/namada-interface/src/config/index.ts +++ b/apps/namada-interface/src/config/index.ts @@ -1,13 +1,7 @@ import ApiConfig from "./api"; -import ChainConfig from "./chain"; -import IBCConfig from "./ibc"; - -export { defaultChainId, type Protocol, type Chain } from "./chain"; const Config = { api: ApiConfig, - chain: ChainConfig, - ibc: IBCConfig, }; export default Config; diff --git a/apps/namada-interface/src/slices/accounts.ts b/apps/namada-interface/src/slices/accounts.ts index 350786d6ca9..4655a9a31d1 100644 --- a/apps/namada-interface/src/slices/accounts.ts +++ b/apps/namada-interface/src/slices/accounts.ts @@ -1,28 +1,15 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { Account } from "@anoma/types"; +import { defaultChainId as chainId } from "@anoma/chains"; -// TODO: Remove the following once configuration updates are in place: -// See notes below. -import { defaultChainId as chainId } from "config"; - -// TODO: REMOVE THIS TYPE! USE SHARED TYPE INSTEAD! -export type DerivedAccount = { - alias: string; - address: string; - balance?: number; - chainId: string; - publicKey: string; - isShielded?: boolean; -}; - -type DerivedAccounts = { +type Accounts = { [address: string]: Account; }; type ChainId = string; export type AccountsState = { - derived: Record; + derived: Record; }; const ACCOUNTS_ACTIONS_BASE = "accounts"; @@ -35,14 +22,9 @@ const accountsSlice = createSlice({ name: ACCOUNTS_ACTIONS_BASE, initialState, reducers: { - addAccounts: (state, action: PayloadAction) => { + addAccounts: (state, action: PayloadAction) => { const accounts = action.payload; accounts.forEach((account) => { - /** - * For now, use the default chainId. - * TODO - Use chainId from accounts below, as we - * need to support multiple chains: - */ const { address, alias, isShielded } = account; if (!state.derived[chainId]) { state.derived[chainId] = {}; diff --git a/apps/namada-interface/src/slices/balances.ts b/apps/namada-interface/src/slices/balances.ts index 78b42e2794e..db2b7eba490 100644 --- a/apps/namada-interface/src/slices/balances.ts +++ b/apps/namada-interface/src/slices/balances.ts @@ -1,10 +1,9 @@ import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { chains } from "@anoma/chains"; import { Account, Symbols, Tokens, TokenType } from "@anoma/types"; import { RpcClient } from "@anoma/rpc"; -import Config from "config"; - type Balance = { chainId: string; address: string; @@ -22,14 +21,15 @@ enum BalancesThunkActions { FetchBalanceByToken = "fetchBalanceByToken", } +// TODO: We need to update this to also support balance queries from Cosmos, Osmosis, etc. export const fetchBalanceByToken = createAsyncThunk( `${BALANCES_ACTIONS_BASE}/${BalancesThunkActions.FetchBalanceByToken}`, async (args: { token: TokenType; account: Account }) => { const { token, account } = args; const { chainId, address } = account; - const chainConfig = Config.chain[chainId]; - const rpcClient = new RpcClient(chainConfig.network); + const { rpc } = chains[chainId]; + const rpcClient = new RpcClient(rpc); const { address: tokenAddress = "" } = Tokens[token]; const balance = await rpcClient.queryBalance(tokenAddress, address); @@ -43,14 +43,15 @@ export const fetchBalanceByToken = createAsyncThunk( } ); +// TODO: We need to update this to also support balance queries from Cosmos, Osmosis, etc. export const fetchBalances = createAsyncThunk( `${BALANCES_ACTIONS_BASE}/${BalancesThunkActions.FetchBalanceByAccounts}`, async (accounts: Account[]) => { const balances = await Promise.all( accounts.map(async (account) => { const { chainId, address } = account; - const chainConfig = Config.chain[chainId]; - const rpcClient = new RpcClient(chainConfig.network); + const { rpc } = chains[chainId]; + const rpcClient = new RpcClient(rpc); const results: Balance[] = await Promise.all( Symbols.map(async (token) => { diff --git a/apps/namada-interface/src/slices/settings.ts b/apps/namada-interface/src/slices/settings.ts index ae9fd639e6e..3731be9fc44 100644 --- a/apps/namada-interface/src/slices/settings.ts +++ b/apps/namada-interface/src/slices/settings.ts @@ -1,6 +1,5 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; - -import { defaultChainId } from "config/chain"; +import { defaultChainId } from "@anoma/chains"; const SETTINGS_ACTIONS_BASE = "settings"; diff --git a/apps/namada-interface/src/slices/shieldedTransfer/utils.ts b/apps/namada-interface/src/slices/shieldedTransfer/utils.ts index a70aae01921..f7926fae723 100644 --- a/apps/namada-interface/src/slices/shieldedTransfer/utils.ts +++ b/apps/namada-interface/src/slices/shieldedTransfer/utils.ts @@ -1,4 +1,4 @@ -import Config from "config"; +import { chains } from "@anoma/chains"; import { RpcClient } from "@anoma/rpc"; import { NodeWithNextId, getMaspWeb } from "@anoma/masp-web"; import { TRANSFER_CONFIGURATION } from "./types"; @@ -51,9 +51,9 @@ const fetchShieldedTransferById = async ( transactionId?: string ): Promise => { const { maspAddress } = TRANSFER_CONFIGURATION; - const { network } = Config.chain[chainId]; - const rpcClient = new RpcClient(network); + const { rpc } = chains[chainId]; + const rpcClient = new RpcClient(rpc); const shieldedTransactionId = await rpcClient.queryShieldedTransaction( maspAddress, transactionId diff --git a/apps/namada-interface/src/slices/transfers.ts b/apps/namada-interface/src/slices/transfers.ts index f59af4af0ce..87a49d38a27 100644 --- a/apps/namada-interface/src/slices/transfers.ts +++ b/apps/namada-interface/src/slices/transfers.ts @@ -1,9 +1,9 @@ import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { chains } from "@anoma/chains"; import { Account, TxWasm, Tokens, TokenType } from "@anoma/types"; import { Anoma } from "@anoma/integrations"; import { - RpcConfig, RpcClient, SocketClient, TxResponse, @@ -17,7 +17,6 @@ import { amountToMicro, } from "@anoma/utils"; -import Config from "config"; /* import { */ /* createShieldedTransfer, */ /* } from "./shieldedTransfer"; */ @@ -199,10 +198,10 @@ const createTransfer = async ( const txCode = await fetchWasmCode(TxWasm.Transfer); // Invoke extension integration - const anoma = new Anoma(); - const signer = anoma.signer(chainId); + const anoma = new Anoma(chains[chainId]); + const signer = anoma.signer(); const encodedTx = - (await signer.encodeTransfer({ + (await signer?.encodeTransfer({ source: address, target, token, @@ -218,7 +217,7 @@ const createTransfer = async ( }; const { hash, bytes } = - (await signer.signTx(address, txProps, encodedTx)) || {}; + (await signer?.signTx(address, txProps, encodedTx)) || {}; if (hash && bytes) { return { @@ -330,14 +329,10 @@ export const submitTransferTransaction = createAsyncThunk( const { address } = account; const source = faucet || address; - const chainConfig = Config.chain[chainId]; - const { network } = chainConfig; + const { rpc } = chains[chainId]; - const rpcClient = new RpcClient(network); - const socketClient = new SocketClient({ - ...network, - protocol: network.wsProtocol, - }); + const rpcClient = new RpcClient(rpc); + const socketClient = new SocketClient(rpc); notify && dispatch( @@ -468,11 +463,10 @@ export const submitIbcTransferTransaction = createAsyncThunk( { rejectWithValue } ) => { const { address: source = "" } = account; - const chainConfig = Config.chain[chainId] || {}; - const { url, port, protocol, wsProtocol } = chainConfig.network; - const rpcConfig = new RpcConfig(url, port, protocol, wsProtocol); - const rpcClient = new RpcClient(rpcConfig.network); - const socketClient = new SocketClient(rpcConfig.wsNetwork); + const { rpc } = chains[chainId]; + + const rpcClient = new RpcClient(rpc); + const socketClient = new SocketClient(rpc); let epoch: number; try { @@ -486,10 +480,10 @@ export const submitIbcTransferTransaction = createAsyncThunk( const txCode = await fetchWasmCode(TxWasm.IBC); // Invoke extension integration - const anoma = new Anoma(); - const signer = anoma.signer(chainId); + const anoma = new Anoma(chains[chainId]); + const signer = anoma.signer(); const encodedTx = - (await signer.encodeIbcTransfer({ + (await signer?.encodeIbcTransfer({ sourcePort: portId, sourceChannel: channelId, sender: source, @@ -507,7 +501,7 @@ export const submitIbcTransferTransaction = createAsyncThunk( }; const { hash, bytes } = - (await signer.signTx(source, txProps, encodedTx)) || {}; + (await signer?.signTx(source, txProps, encodedTx)) || {}; if (!hash || !bytes) { throw new Error("Invalid transaction!"); diff --git a/packages/chains/package.json b/packages/chains/package.json new file mode 100644 index 00000000000..d3dfc42940a --- /dev/null +++ b/packages/chains/package.json @@ -0,0 +1,21 @@ +{ + "name": "@anoma/chains", + "version": "0.1.0", + "description": "Chain configurations for Namada", + "main": "src/index.ts", + "repository": "https://github.com/anoma/namada-interface", + "author": "Heliax AG ", + "license": "MIT", + "private": false, + "dependencies": { + "typescript": "^4.9.3", + "@anoma/types": "0.1.0" + }, + "devDependencies": { + "eslint": "^8.27.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.26.0" + } +} diff --git a/packages/chains/src/chains/cosmos.ts b/packages/chains/src/chains/cosmos.ts new file mode 100644 index 00000000000..21b2bafb9d0 --- /dev/null +++ b/packages/chains/src/chains/cosmos.ts @@ -0,0 +1,32 @@ +import { Chain, Extensions } from "@anoma/types"; + +const DEFAULT_ALIAS = "Cosmos Testnet"; +const DEFAULT_CHAIN_ID = "cosmos-75a7e12.69483d59a9fb174"; +const DEFAULT_RPC = "https://api.cosmos.network/"; + +const { + REACT_APP_COSMOS_ALIAS: alias = DEFAULT_ALIAS, + REACT_APP_COSMOS_CHAIN_ID: chainId = DEFAULT_CHAIN_ID, + REACT_APP_COSMOS_CHAIN_URL: rpc = DEFAULT_RPC, +} = process.env; + +const cosmos: Chain = { + alias, + bech32Prefix: "cosmos", + bip44: { + coinType: 118, + }, + rpc, + chainId, + currency: { + token: "Atom", + symbol: "ATOM", + gasPriceStep: { low: 0.01, average: 0.025, high: 0.03 }, + }, + extension: Extensions["keplr"], + ibc: { + portId: "transfer", + }, +}; + +export default cosmos; diff --git a/packages/chains/src/chains/namada.ts b/packages/chains/src/chains/namada.ts new file mode 100644 index 00000000000..52385dbeae7 --- /dev/null +++ b/packages/chains/src/chains/namada.ts @@ -0,0 +1,35 @@ +import { Chain, Extensions } from "@anoma/types"; + +const DEFAULT_ALIAS = "Namada Testnet"; +const DEFAULT_CHAIN_ID = "qc-testnet-5.1.025a61165acd05e"; +const DEFAULT_RPC = + "https://d3brk13lbhxfdb.cloudfront.net/qc-testnet-5.1.025a61165acd05e"; +const DEFAULT_BECH32_PREFIX = "atest"; + +const { + REACT_APP_NAMADA_ALIAS: alias = DEFAULT_ALIAS, + REACT_APP_NAMADA_CHAIN_ID: chainId = DEFAULT_CHAIN_ID, + REACT_APP_NAMADA_URL: rpc = DEFAULT_RPC, + REACT_APP_NAMADA_BECH32_PREFIX: bech32Prefix = DEFAULT_BECH32_PREFIX, +} = process.env; + +const namada: Chain = { + alias, + bech32Prefix, + bip44: { + coinType: 118, + }, + rpc, + chainId, + currency: { + token: "Nam", + symbol: "NAM", + gasPriceStep: { low: 0.01, average: 0.025, high: 0.03 }, + }, + extension: Extensions["anoma"], + ibc: { + portId: "transfer", + }, +}; + +export default namada; diff --git a/packages/chains/src/chains/osmosis.ts b/packages/chains/src/chains/osmosis.ts new file mode 100644 index 00000000000..fad23439173 --- /dev/null +++ b/packages/chains/src/chains/osmosis.ts @@ -0,0 +1,32 @@ +import { Chain, Extensions } from "@anoma/types"; + +const DEFAULT_ALIAS = "Osmosis Testnet"; +const DEFAULT_CHAIN_ID = "osmosis-75a7e12.69483d59a9fb174"; +const DEFAULT_RPC = "https://rpc.osmosis.zone/"; + +const { + REACT_APP_OSMOSIS_ALIAS: alias = DEFAULT_ALIAS, + REACT_APP_OSMOSIS_CHAIN_ID: chainId = DEFAULT_CHAIN_ID, + REACT_APP_OSMOSIS_CHAIN_URL: rpc = DEFAULT_RPC, +} = process.env; + +const osmosis: Chain = { + alias, + bech32Prefix: "osmo", + bip44: { + coinType: 1, + }, + rpc, + chainId, + currency: { + token: "Osmosis", + symbol: "OSMO", + gasPriceStep: { low: 0.01, average: 0.025, high: 0.03 }, + }, + extension: Extensions["keplr"], + ibc: { + portId: "transfer", + }, +}; + +export default osmosis; diff --git a/packages/chains/src/index.ts b/packages/chains/src/index.ts new file mode 100644 index 00000000000..5b7427f2ff4 --- /dev/null +++ b/packages/chains/src/index.ts @@ -0,0 +1,11 @@ +import cosmos from "./chains/cosmos"; +import namada from "./chains/namada"; +import osmosis from "./chains/osmosis"; + +export const defaultChainId = namada.chainId; + +export const chains = { + [cosmos.chainId]: cosmos, + [namada.chainId]: namada, + [osmosis.chainId]: osmosis, +}; diff --git a/packages/integrations/src/Anoma.ts b/packages/integrations/src/Anoma.ts index 62edffa3742..a4c485dba96 100644 --- a/packages/integrations/src/Anoma.ts +++ b/packages/integrations/src/Anoma.ts @@ -1,34 +1,44 @@ import { Account, Anoma as IAnoma, + Chain, Signer, WindowWithAnoma, } from "@anoma/types"; -export default class Anoma { - constructor( - /* public readonly chain: Chain, */ - private readonly _anoma = (window)?.anoma - ) {} +import { Integration } from "types/Integration"; + +export default class Anoma implements Integration { + private _anoma: WindowWithAnoma["anoma"] | undefined; + + constructor(public readonly chain: Chain) {} public get instance(): IAnoma | undefined { return this._anoma; } + private _init(): void { + if (!this._anoma) { + this._anoma = (window)?.anoma; + } + } + public detect(): boolean { + this._init(); return !!this._anoma; } public async connect(): Promise { - await this._anoma.connect(""); + this._init(); + await this._anoma?.connect(this.chain.chainId); } - public async fetchAccounts(): Promise { - const signer = this._anoma.getSigner(""); - return await signer.accounts(); + public async accounts(): Promise { + const signer = this._anoma?.getSigner(this.chain.chainId); + return await signer?.accounts(); } - public signer(chainId: string): Signer { - return this._anoma.getSigner(chainId); + public signer(): Signer | undefined { + return this._anoma?.getSigner(this.chain.chainId); } } diff --git a/packages/integrations/src/Keplr.ts b/packages/integrations/src/Keplr.ts index c6c94e6c1bf..0ca12890ad0 100644 --- a/packages/integrations/src/Keplr.ts +++ b/packages/integrations/src/Keplr.ts @@ -1,25 +1,19 @@ import { Keplr as IKeplr, - ChainInfo, Key, Window as KeplrWindow, } from "@keplr-wallet/types"; import { AccountData } from "@cosmjs/proto-signing"; -import { Chain } from "./types"; -import { Tokens, TokenType } from "@anoma/types"; +import { Account, Chain } from "@anoma/types"; +import { Integration } from "types/Integration"; -const { REACT_APP_LOCAL, NODE_ENV } = process.env; -const DEV_ENV = !!(REACT_APP_LOCAL || NODE_ENV === "development"); - -const BECH32_PREFIX = "namada"; -const BECH32_PREFIX_TESTNET = "cosmos"; const KEPLR_NOT_FOUND = "Keplr extension not found!"; const DEFAULT_FEATURES: string[] = []; const IBC_FEATURE = "ibc-transfer"; type OfflineSigner = ReturnType; -class Keplr { +class Keplr implements Integration { private _offlineSigner: OfflineSigner | undefined; private _features: string[] = []; /** @@ -33,10 +27,10 @@ class Keplr { private readonly _keplr = (window)?.keplr ) { // If chain is ibc-enabled, add relevant feature: - const { ibc = [] } = chain; + const { ibc } = chain; this._features.push(...DEFAULT_FEATURES); - if (ibc.length > 0) { + if (ibc) { this._features.push(IBC_FEATURE); } } @@ -53,14 +47,14 @@ class Keplr { * Get offline signer for current chain * @returns {OfflineSigner} */ - public get offlineSigner(): OfflineSigner { + public signer(): OfflineSigner { if (this._offlineSigner) { return this._offlineSigner; } if (this._keplr) { - const { id } = this.chain; - this._offlineSigner = this._keplr.getOfflineSigner(id); + const { chainId } = this.chain; + this._offlineSigner = this._keplr.getOfflineSigner(chainId); return this._offlineSigner; } throw new Error(KEPLR_NOT_FOUND); @@ -74,33 +68,15 @@ class Keplr { return !!this._keplr; } - /** - * Determine if chain has already been added to extension. Keplr - * will throw an error if chainId is not found - * @returns {Promise} - */ - public async detectChain(): Promise { - if (this._keplr) { - try { - await this._keplr.getOfflineSignerAuto(this.chain.id); - return true; - } catch (e) { - return false; - } - } - return Promise.reject(KEPLR_NOT_FOUND); - } - /** * Enable connection to Keplr for current chain * @returns {Promise} */ - public async enable(): Promise { + public async connect(): Promise { if (this._keplr) { - const { id } = this.chain; + const { chainId } = this.chain; - await this._keplr.enable(id); - return true; + await this._keplr.enable(chainId); } return Promise.reject(KEPLR_NOT_FOUND); } @@ -111,8 +87,8 @@ class Keplr { */ public async getKey(): Promise { if (this._keplr) { - const { id } = this.chain; - return await this._keplr.getKey(id); + const { chainId } = this.chain; + return await this._keplr.getKey(chainId); } return Promise.reject(KEPLR_NOT_FOUND); } @@ -121,75 +97,19 @@ class Keplr { * Get accounts from offline signer * @returns {Promise} */ - public async getAccounts(): Promise { - if (this._keplr) { - return await this.offlineSigner.getAccounts(); - } - return Promise.reject(KEPLR_NOT_FOUND); - } - - /** - * Suggest a chain to Keplr extension - * @returns {Promise} - */ - public async suggestChain(): Promise { + public async accounts(): Promise { if (this._keplr) { - const { id: chainId, alias: chainName, network } = this.chain; - const { protocol, url, port } = network; - const rpcUrl = `${protocol}://${url}${port ? ":" + port : ""}`; - // The following is the Light Client Daemon (LCD) REST address. - // TODO: Add LCD restUrl to Chain type and config - const restUrl = `${protocol}://${url}:1317`; - const bech32Prefix = DEV_ENV ? BECH32_PREFIX_TESTNET : BECH32_PREFIX; - - const tokenType: TokenType = "ATOM"; - const token = Tokens[tokenType]; - const { symbol, coinGeckoId } = token; - - const currency = { - coinDenom: symbol, - coinMinimalDenom: "uatom", // Add this to Token config? - coinDecimals: 6, - coinGeckoId, - }; - - const chainInfo: ChainInfo = { - rpc: rpcUrl, - /* - NOTE: Optionally specify rpcConfig for more granular configuration: - rpcConfig: AxiosRequestConfig, - */ - rest: restUrl, - /* - NOTE: Optionally specify restConfig for more granular configuration: - rpcConfig: AxiosRequestConfig, - */ - chainId, - chainName, - stakeCurrency: currency, - bip44: { - coinType: token.type, - }, - bech32Config: { - bech32PrefixAccAddr: bech32Prefix, - bech32PrefixAccPub: `${bech32Prefix}pub`, - bech32PrefixValAddr: `${bech32Prefix}valoper`, - bech32PrefixValPub: `${bech32Prefix}valoperpub`, - bech32PrefixConsAddr: `${bech32Prefix}valcons`, - bech32PrefixConsPub: `${bech32Prefix}valconspub`, - }, - currencies: [currency], - feeCurrencies: [currency], - // TODO: This should be configured for production: - gasPriceStep: { low: 0.01, average: 0.025, high: 0.03 }, // Optional - features: this._features, - beta: DEV_ENV, - }; - - await this._keplr.experimentalSuggestChain(chainInfo); - return true; + const accounts = await this._offlineSigner?.getAccounts(); + + return accounts?.map( + (account: AccountData): Account => ({ + alias: "keplr", + chainId: this.chain.chainId, + address: account.address, + isShielded: false, + }) + ); } - return Promise.reject(KEPLR_NOT_FOUND); } } diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 8d4a3ddd755..e089b14040d 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -1,2 +1,4 @@ -export { default as Anoma } from "./Anoma"; -export { default as Keplr } from "./Keplr"; +import Anoma from "./Anoma"; +import Keplr from "./Keplr"; + +export { Anoma, Keplr }; diff --git a/packages/integrations/src/types/Integration.ts b/packages/integrations/src/types/Integration.ts new file mode 100644 index 00000000000..dfb52330bfa --- /dev/null +++ b/packages/integrations/src/types/Integration.ts @@ -0,0 +1,6 @@ +export interface Integration { + detect: () => boolean; + connect: () => Promise; + accounts: () => Promise; + signer: () => S | undefined; +} diff --git a/packages/integrations/src/types/index.ts b/packages/integrations/src/types/index.ts index 7ace7cfcce4..e690b0de94f 100644 --- a/packages/integrations/src/types/index.ts +++ b/packages/integrations/src/types/index.ts @@ -1,20 +1 @@ -import { Network, Protocol } from "@anoma/rpc"; - -export type IBCConfigItem = { - chainId: string; - alias: string; -}; - -export type NetworkConfig = Network & { - wsProtocol: Protocol; -}; - -export type Chain = { - id: string; - alias: string; - accountIndex: number; - network: NetworkConfig; - faucet?: string; - portId?: string; - ibc?: IBCConfigItem[]; -}; +export * from "./Integration"; diff --git a/packages/rpc/src/RpcClient.ts b/packages/rpc/src/RpcClient.ts index 619b5346558..79d7c946b37 100644 --- a/packages/rpc/src/RpcClient.ts +++ b/packages/rpc/src/RpcClient.ts @@ -7,7 +7,7 @@ import { Buffer } from "buffer"; import { amountFromMicro, createJsonRpcRequest } from "@anoma/utils"; import { decodeTransactionWithNextTxId } from "./utils"; import { NodeWithNextId } from "@anoma/masp-web"; -import RpcClientBase, { RpcClientInitArgs } from "./RpcClientBase"; +import RpcClientBase from "./RpcClientBase"; import { schemaAmount, TokenAmount } from "./schema"; import { AbciResponse, PathType } from "./types"; @@ -20,8 +20,8 @@ const ABCI_QUERY_PATH_PREFIX = "/shell/"; class RpcClient extends RpcClientBase { private _client: HttpClient; - constructor(args: RpcClientInitArgs) { - super(args); + constructor(url: string) { + super(url); this._client = new HttpClient(this.endpoint); } diff --git a/packages/rpc/src/RpcClientBase.ts b/packages/rpc/src/RpcClientBase.ts index a51dd2a287a..b8a651bd5ff 100644 --- a/packages/rpc/src/RpcClientBase.ts +++ b/packages/rpc/src/RpcClientBase.ts @@ -1,34 +1,14 @@ -import { Network, Protocol } from "./types"; - -export type RpcClientInitArgs = Network; - abstract class RpcClientBase { private _url: string; - private _port?: number; - private _protocol: Protocol; - - constructor({ url, port, protocol = "http" }: RpcClientInitArgs) { - this._url = url; - this._port = port; - this._protocol = protocol; - } - public set url(url: string) { + constructor(url: string) { this._url = url; } - public set port(port: number) { - this._port = port; - } - - public set protocol(protocol: Protocol) { - this._protocol = protocol; - } - + // TODO: We can possibly just remove this whole class, as we will no longer have a SocketClient, + // so a base class makes little sense, and everything can be simply defined in RpcClient. protected get endpoint(): string { - return `${this._protocol}://${this._url}${ - this._port ? `:${this._port}` : "" - }`; + return this._url; } } diff --git a/packages/rpc/src/SocketClient.ts b/packages/rpc/src/SocketClient.ts index fa81045d9f5..7f41839e57d 100644 --- a/packages/rpc/src/SocketClient.ts +++ b/packages/rpc/src/SocketClient.ts @@ -25,7 +25,11 @@ class SocketClient extends RpcClientBase { private _client: WebsocketClient | null = null; public connect(): void { - this._client = new WebsocketClient(this.endpoint, (e: Error) => { + const urlParts = this.endpoint.split("://"); + const protocol = urlParts[0] === "https" ? "wss" : "ws"; + const endpoint = `${protocol}://${urlParts[1]}`; + + this._client = new WebsocketClient(endpoint, (e: Error) => { throw new Error(e.message); }); } diff --git a/packages/rpc/src/config/index.ts b/packages/rpc/src/config/index.ts deleted file mode 100644 index b09f147731e..00000000000 --- a/packages/rpc/src/config/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Network, Protocol } from "../types"; - -export class RpcConfig { - constructor( - private _url: string, - private _port: number | undefined, - private _protocol: Protocol, - private _wsProtocol: Protocol - ) {} - - public get network(): Network { - return { - url: this._url, - port: this._port, - protocol: this._protocol, - }; - } - - public get wsNetwork(): Network { - return { - url: this._url, - port: this._port, - protocol: this._wsProtocol, - }; - } -} diff --git a/packages/rpc/src/index.ts b/packages/rpc/src/index.ts index 0faa959d264..e11d98898d9 100644 --- a/packages/rpc/src/index.ts +++ b/packages/rpc/src/index.ts @@ -1,4 +1,3 @@ -export { RpcConfig } from "./config"; export { TxResponse, IbcTxResponse } from "./enums"; export * from "./types"; export { default as RpcClient } from "./RpcClient"; diff --git a/packages/rpc/src/types/index.ts b/packages/rpc/src/types/index.ts index 2d875efaf96..7d3661e572c 100644 --- a/packages/rpc/src/types/index.ts +++ b/packages/rpc/src/types/index.ts @@ -1,15 +1,6 @@ import { BroadcastTxSyncResponse } from "@cosmjs/tendermint-rpc"; import { JsonRpcSuccessResponse } from "@cosmjs/json-rpc"; import { SubscriptionEvent } from "@cosmjs/tendermint-rpc/build/rpcclients"; -import { TxResponse, IbcTxResponse } from "../enums"; - -export type Protocol = "http" | "https" | "ws" | "wss"; - -export type Network = { - url: string; - port?: number; - protocol: Protocol; -}; export type AbciResponse = { code?: number; diff --git a/packages/types/src/chain.ts b/packages/types/src/chain.ts index dc23f9bb4f5..1651e7a0948 100644 --- a/packages/types/src/chain.ts +++ b/packages/types/src/chain.ts @@ -1,10 +1,52 @@ -import { - ChainInfo, - Currency as KeplrCurrency, - IBCCurrency as KeplrIBCCurrency, -} from "@keplr-wallet/types"; - -// Alias types from keplr-wallet so we can update as needed from a single point -export type Chain = ChainInfo; -export type Currency = KeplrCurrency; -export type IBCCurrency = KeplrIBCCurrency; +export type Currency = { + token: string; + symbol: string; + gasPriceStep?: { + low: number; + average: number; + high: number; + }; +}; + +// Define keys for supported extensions +export type ExtensionKey = "anoma" | "keplr" | "metamask"; + +export type ExtensionInfo = { + alias: string; + id: ExtensionKey; + url: string; +}; + +// Define constant with extension properties +export const Extensions: Record = { + anoma: { + alias: "Anoma", + id: "anoma", + url: "https://namada.me/", + }, + keplr: { + alias: "Keplr", + id: "keplr", + url: "https://www.keplr.app/", + }, + metamask: { + alias: "Metamask", + id: "metamask", + url: "https://metamask.io/", + }, +}; + +export type Chain = { + alias: string; + bech32Prefix: string; + bip44: { + coinType: number; + }; + chainId: string; + currency: Currency; + extension: ExtensionInfo; + rpc: string; + ibc?: { + portId: string; + }; +}; diff --git a/packages/utils/src/helpers/index.ts b/packages/utils/src/helpers/index.ts index dcca85f45d0..c1da0ce4cc1 100644 --- a/packages/utils/src/helpers/index.ts +++ b/packages/utils/src/helpers/index.ts @@ -1,10 +1,6 @@ import { JsonRpcRequest } from "@cosmjs/json-rpc"; import { DateTime } from "luxon"; -import { - JsonCompatibleArray, - JsonCompatibleDictionary, - Protocol, -} from "@anoma/rpc"; +import { JsonCompatibleArray, JsonCompatibleDictionary } from "@anoma/rpc"; const MICRO_FACTOR = 1000000; // 1,000,000 @@ -122,21 +118,6 @@ export const getUrl = (url = ""): string => { return sanitize(url).replace(/^https?\:\/\//, ""); }; -/** - * Get the protocol from a URL or return default - * @param url - * @returns {Protocol} - */ -export const getUrlProtocol = (url?: string): Protocol => { - const prefix = sanitize(url).split(":")[0]; - - if (prefix === "https") { - return "https"; - } - - return "http"; -}; - /* * Shorten a Bech32 address */ diff --git a/yarn.lock b/yarn.lock index abc19447cbc..de2e2ddc62d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2464,6 +2464,15 @@ debug "^4.1.1" minimatch "^3.0.4" +"@humanwhocodes/config-array@^0.11.6": + version "0.11.7" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" + integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + "@humanwhocodes/config-array@^0.9.2": version "0.9.5" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" @@ -3570,7 +3579,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -3590,6 +3599,18 @@ tiny-glob "^0.2.9" tslib "^2.4.0" +"@pkgr/utils@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" + integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw== + dependencies: + cross-spawn "^7.0.3" + is-glob "^4.0.3" + open "^8.4.0" + picocolors "^1.0.0" + tiny-glob "^0.2.9" + tslib "^2.4.0" + "@playwright/test@^1.24.1": version "1.24.1" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.24.1.tgz#b70f5cd0909530e17c7610918b45f4af156f16c4" @@ -7057,6 +7078,11 @@ dotenv@^10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + duplexer@^0.1.1, duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -7360,6 +7386,19 @@ eslint-import-resolver-typescript@^3.5.1: is-glob "^4.0.3" synckit "^0.8.3" +eslint-import-resolver-typescript@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.2.tgz#9431acded7d898fd94591a08ea9eec3514c7de91" + integrity sha512-zX4ebnnyXiykjhcBvKIf5TNvt8K7yX6bllTRZ14MiurKPjDpCAZujlszTdB8pcNXhZcOf+god4s9SjQa5GnytQ== + dependencies: + debug "^4.3.4" + enhanced-resolve "^5.10.0" + get-tsconfig "^4.2.0" + globby "^13.1.2" + is-core-module "^2.10.0" + is-glob "^4.0.3" + synckit "^0.8.4" + eslint-module-utils@^2.7.2: version "2.7.3" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" @@ -7701,6 +7740,51 @@ eslint@^8.25.0: strip-json-comments "^3.1.0" text-table "^0.2.0" +eslint@^8.27.0: + version "8.28.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.28.0.tgz#81a680732634677cc890134bcdd9fdfea8e63d6e" + integrity sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ== + dependencies: + "@eslint/eslintrc" "^1.3.3" + "@humanwhocodes/config-array" "^0.11.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.4.0" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.15.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + eslint@^8.3.0, eslint@^8.9.0: version "8.11.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37" @@ -9050,7 +9134,7 @@ is-path-cwd@^2.2.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-inside@^3.0.2: +is-path-inside@^3.0.2, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -11710,7 +11794,7 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.2: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -14564,6 +14648,14 @@ synckit@^0.8.3: "@pkgr/utils" "^2.3.0" tslib "^2.4.0" +synckit@^0.8.4: + version "0.8.4" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.4.tgz#0e6b392b73fafdafcde56692e3352500261d64ec" + integrity sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw== + dependencies: + "@pkgr/utils" "^2.3.1" + tslib "^2.4.0" + tailwindcss@^3.0.2: version "3.0.23" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.23.tgz#c620521d53a289650872a66adfcb4129d2200d10" @@ -15016,6 +15108,11 @@ typescript@^4.8.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== +typescript@^4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" + integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"