From d4900223282d802d396b73aa1dbeb5134d85207d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 27 Jul 2023 17:35:15 +0100 Subject: [PATCH 001/110] Add regression network --- skale-network | 2 +- src/components/TokenList/helper.ts | 3 ++- src/components/WalletConnector/MetamaskConnector.ts | 1 + src/core/constants.ts | 6 ++++-- src/core/core.ts | 4 ++++ src/core/helper.ts | 4 +++- src/metadata/addresses/regression.json | 10 ++++++++++ src/metadata/faucet.json | 3 ++- src/metadata/proxy.json | 1 + 9 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 src/metadata/addresses/regression.json diff --git a/skale-network b/skale-network index 84e54b4..5b00cae 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 84e54b423f920c42efaca79274e16789fc8cf0a7 +Subproject commit 5b00cae6736322f302c3a75331b19ee02b0a3596 diff --git a/src/components/TokenList/helper.ts b/src/components/TokenList/helper.ts index 8904bfa..96db6ef 100644 --- a/src/components/TokenList/helper.ts +++ b/src/components/TokenList/helper.ts @@ -47,7 +47,8 @@ const icons = importAll(require.context('../../icons', false, /\.(png|jpe?g|svg) const CHAIN_ICONS = { 'mainnet': importAll(require.context('../../meta/mainnet/icons', false, /\.(png|jpe?g|svg)$/)), 'staging3': importAll(require.context('../../meta/staging/icons', false, /\.(png|jpe?g|svg)$/)), - 'legacy': importAll(require.context('../../meta/legacy/icons', false, /\.(png|jpe?g|svg)$/)) + 'legacy': importAll(require.context('../../meta/legacy/icons', false, /\.(png|jpe?g|svg)$/)), + 'regression': importAll(require.context('../../meta/regression/icons', false, /\.(png|jpe?g|svg)$/)) } diff --git a/src/components/WalletConnector/MetamaskConnector.ts b/src/components/WalletConnector/MetamaskConnector.ts index 67eba54..50287da 100644 --- a/src/components/WalletConnector/MetamaskConnector.ts +++ b/src/components/WalletConnector/MetamaskConnector.ts @@ -4,6 +4,7 @@ export const CHAIN_IDS = { 'staging': '0x4', 'staging3': '0x5', 'legacy': '0x5', + 'regression': '0x5', 'qatestnet': '0x4', 'mainnet': '0x1' } diff --git a/src/core/constants.ts b/src/core/constants.ts index 89d3d89..bc438e1 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -48,13 +48,15 @@ export const HTTPS_PREFIX = 'https://'; export const MAINNET_EXPLORER_URLS: { [skaleNetwork: string]: string } = { mainnet: 'https://etherscan.io', staging3: 'https://goerli.etherscan.io/', - legacy: 'https://goerli.etherscan.io/' + legacy: 'https://goerli.etherscan.io/', + regression: 'https://goerli.etherscan.io/' }; export const BASE_EXPLORER_URLS = { mainnet: "explorer.mainnet.skalenodes.com", staging3: "explorer.staging-v3.skalenodes.com", - legacy: "explorer.staging-v3.skalenodes.com" + legacy: "explorer.staging-v3.skalenodes.com", + regression: "regression-explorer.skalenodes.com" }; // ETA constants diff --git a/src/core/core.ts b/src/core/core.ts index a8e72b7..d924986 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -27,6 +27,7 @@ import mainnetAddresses from '../metadata/addresses/mainnet.json'; import stagingAddresses from '../metadata/addresses/staging.json'; import staging3Addresses from '../metadata/addresses/staging3.json'; import legacyAddresses from '../metadata/addresses/legacy.json'; +import regressionAddresses from '../metadata/addresses/regression.json'; import { getChainName } from './helper'; import { MAINNET_CHAIN_NAME } from './constants'; @@ -149,6 +150,9 @@ function getMainnetAbi(network: string) { if (network === 'legacy') { return { ...mainnetAbi, ...legacyAddresses } } + if (network === 'regression') { + return { ...mainnetAbi, ...regressionAddresses } + } return { ...mainnetAbi, ...mainnetAddresses } } diff --git a/src/core/helper.ts b/src/core/helper.ts index 602ea6f..8855b52 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -29,12 +29,14 @@ import { TransferRequestStatus } from './dataclasses'; import mainnetMeta from '../meta/mainnet/chains.json'; import stagingMeta from '../meta/staging/chains.json'; import legacyMeta from '../meta/legacy/chains.json'; +import regressionMeta from '../meta/regression/chains.json'; export const CHAINS_META = { 'mainnet': mainnetMeta, 'staging3': stagingMeta, - 'legacy': legacyMeta + 'legacy': legacyMeta, + 'regression': regressionMeta } diff --git a/src/metadata/addresses/regression.json b/src/metadata/addresses/regression.json new file mode 100644 index 0000000..ac5d5fd --- /dev/null +++ b/src/metadata/addresses/regression.json @@ -0,0 +1,10 @@ +{ + "message_proxy_mainnet_address": "0xd2CF0381DB8966a2f9a0F414a3eF87caa9Bcf038", + "linker_address": "0x605f3aC69916bd744346DFD03aaE79556Cf83Dc2", + "community_pool_address": "0x504A1116a860950D6db8246e4763fc3c76Da5096", + "deposit_box_eth_address": "0x4EBD0E4da411540e1EdDFe6D978457D1f0B1d181", + "deposit_box_erc20_address": "0xe98528D168151aB0dEF9dD419C5e9a65a0FAE138", + "deposit_box_erc721_address": "0x1354EC4138E9f39B02f1e1026D2eddB2edcD5ec6", + "deposit_box_erc1155_address": "0x1B46E1Aac3dfDf578880fb0332522D0Dea52F392", + "deposit_box_erc721_with_metadata_address": "0x915ab36c3D680498e5A9F520e90C8312afB1CfAe" +} \ No newline at end of file diff --git a/src/metadata/faucet.json b/src/metadata/faucet.json index 8248998..dbb5d94 100644 --- a/src/metadata/faucet.json +++ b/src/metadata/faucet.json @@ -36,5 +36,6 @@ } }, "staging": null, - "legacy": null + "legacy": null, + "regression": null } \ No newline at end of file diff --git a/src/metadata/proxy.json b/src/metadata/proxy.json index 3f749d1..ff242df 100644 --- a/src/metadata/proxy.json +++ b/src/metadata/proxy.json @@ -3,5 +3,6 @@ "staging": "https://staging-v2.skalenodes.com", "staging3": "https://staging-v3.skalenodes.com", "legacy": "https://legacy-proxy.skalenodes.com/", + "regression": "https://regression-proxy.skalenodes.com/", "qatestnet": "https://new-testnet-proxy.skalenodes.com" } \ No newline at end of file From 896315cf86a04190ac963fe577c333ca6c78ba96 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 27 Jul 2023 18:18:53 +0100 Subject: [PATCH 002/110] Fix linter --- src/components/TokenList/helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/TokenList/helper.ts b/src/components/TokenList/helper.ts index 96db6ef..4e70b5b 100644 --- a/src/components/TokenList/helper.ts +++ b/src/components/TokenList/helper.ts @@ -48,7 +48,8 @@ const CHAIN_ICONS = { 'mainnet': importAll(require.context('../../meta/mainnet/icons', false, /\.(png|jpe?g|svg)$/)), 'staging3': importAll(require.context('../../meta/staging/icons', false, /\.(png|jpe?g|svg)$/)), 'legacy': importAll(require.context('../../meta/legacy/icons', false, /\.(png|jpe?g|svg)$/)), - 'regression': importAll(require.context('../../meta/regression/icons', false, /\.(png|jpe?g|svg)$/)) + 'regression': importAll( + require.context('../../meta/regression/icons', false, /\.(png|jpe?g|svg)$/)) } From 2a9202b8cf8081d6b798d1f74d1cf035d02918b5 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 7 Aug 2023 19:39:39 +0100 Subject: [PATCH 003/110] New metaport architecture --- .gitignore | 1 + .storybook/main.ts | 92 +- package.json | 41 +- src/Metaport.tsx | 4 +- .../AmountErrorMessage/AmountErrorMessage.tsx | 31 +- src/components/AmountInput/AmountInput.scss | 15 +- src/components/AmountInput/AmountInput.tsx | 87 +- src/components/BalanceBlock/BalanceBlock.tsx | 76 -- src/components/BalanceBlock/index.ts | 1 - src/components/ChainApps/ChainApps.tsx | 57 +- src/components/ChainIcon/ChainIcon.tsx | 30 + src/components/ChainIcon/index.ts | 1 + src/components/ChainsList/ChainsList.tsx | 146 ++- src/components/ChainsList/helper.tsx | 25 - .../CommunityPool/CommunityPool.tsx | 193 ---- src/components/CommunityPool/index.ts | 1 - src/components/CurrentChain/CurrentChain.tsx | 49 - src/components/CurrentChain/index.ts | 1 - .../CustomStationUrl/CustomStationUrl.tsx | 52 - src/components/CustomStationUrl/index.ts | 1 - src/components/Debug/Debug.tsx | 75 -- src/components/Debug/index.ts | 1 - src/components/ErrorMessage/ErrorMessage.tsx | 68 +- src/components/ErrorMessage/ErrorMessages.tsx | 80 -- src/components/ErrorMessage/index.ts | 3 +- src/components/Route/Route.tsx | 77 -- src/components/Route/index.ts | 1 - src/components/SFuelBadge/SFuelBadge.scss | 28 - src/components/SFuelBadge/SFuelBadge.tsx | 48 - src/components/SFuelBadge/index.ts | 1 - src/components/SFuelWarning/SFuelWarning.tsx | 252 ---- src/components/SFuelWarning/index.ts | 1 - src/components/SkConnect/SkConnect.tsx | 214 ++++ src/components/SkConnect/index.ts | 1 + src/components/SkPaper/SkPaper.tsx | 55 + src/components/SkPaper/index.ts | 1 + .../SkeletonLoader/SkeletonLoader.tsx | 5 +- .../StepperV2.scss => Stepper/SkStepper.scss} | 0 src/components/Stepper/SkStepper.tsx | 149 +++ src/components/Stepper/Stepper.scss | 37 - src/components/Stepper/Stepper.tsx | 78 -- src/components/Stepper/index.ts | 2 +- src/components/StepperV2/StepperV2.tsx | 69 -- src/components/StepperV2/index.ts | 1 - .../SwitchDirection/SwitchDirection.tsx | 73 ++ src/components/SwitchDirection/index.ts | 1 + src/components/TokenIcon/TokenIcon.tsx | 42 + src/components/TokenIcon/index.ts | 1 + src/components/TokenIdInput/TokenIdInput.scss | 43 - src/components/TokenIdInput/TokenIdInput.tsx | 39 - src/components/TokenIdInput/index.ts | 1 - src/components/TokenList/TokenBalance.tsx | 46 +- src/components/TokenList/TokenList.tsx | 164 +-- .../TokenListSection/TokenListSection.tsx | 95 +- .../TransactionData/TransactionData.scss | 26 - .../TransactionData/TransactionData.tsx | 112 -- src/components/TransactionData/index.ts | 1 - .../TransactionsHistory.tsx | 119 -- src/components/TransactionsHistory/index.ts | 1 - src/components/TransferETA/TransferETA.tsx | 50 - src/components/TransferETA/index.ts | 1 - src/components/TransferETF/TransferETF.tsx | 53 - src/components/TransferETF/index.ts | 1 - .../TransferRequest/TransferRequest.tsx | 217 ---- src/components/TransferRequest/index.ts | 1 - .../TransferSummary/TransferSummary.scss | 71 -- .../TransferSummary/TransferSummary.tsx | 59 - src/components/TransferSummary/index.ts | 1 - src/components/TransferUI/TransferUI.tsx | 197 ---- src/components/TransferUI/index.ts | 1 - src/components/UnwrapUI/UnwrapUI.tsx | 102 -- src/components/UnwrapUI/index.ts | 1 - src/components/WalletConnector/Connector.tsx | 55 - .../WalletConnector/MetamaskConnector.ts | 95 -- src/components/WalletConnector/index.ts | 2 - src/components/WalletConnector/metamask.svg | 61 - .../WalletConnector/walletconnect.svg | 1 - src/components/Widget/Widget.mdx | 4 - src/components/Widget/Widget.stories.tsx | 9 +- src/components/Widget/Widget.tsx | 1024 +++-------------- src/components/Widget/index.ts | 2 +- src/components/WidgetBody/WidgetBody.tsx | 239 ++-- src/components/WidgetUI/Common.stories.tsx | 100 -- src/components/WidgetUI/ERC1155.stories.tsx | 13 - src/components/WidgetUI/ERC20.stories.tsx | 253 ---- src/components/WidgetUI/ERC721.stories.tsx | 14 - src/components/WidgetUI/Errors.stories.tsx | 63 - .../WidgetUI/Guidelines.stories.mdx | 50 - .../WidgetUI/Multichain.stories.tsx | 149 --- src/components/WidgetUI/StoriesHelper.ts | 330 ------ src/components/WidgetUI/StorybookHelper.tsx | 245 ---- src/components/WidgetUI/Themes.stories.tsx | 99 -- src/components/WidgetUI/WidgetUI.scss | 725 ------------ src/components/WidgetUI/WidgetUI.tsx | 128 ++- src/components/WidgetUI/_variables.scss | 7 - .../WrappedTokensWarning.tsx | 56 - src/components/WrappedTokensWarning/index.ts | 1 - src/configs/metaportConfigStaging.json | 192 ---- src/core/actions/action.ts | 209 +++- src/core/actions/checks.ts | 34 +- src/core/actions/erc1155.ts | 222 ---- src/core/actions/erc20.ts | 366 +++--- src/core/actions/erc721.ts | 236 ---- src/core/actions/eth.ts | 121 -- src/core/actions/index.ts | 96 +- src/core/chain_id.ts | 48 + src/core/community_pool.ts | 138 +-- src/core/constants.ts | 8 +- src/core/contracts.ts | 59 + src/core/convertation.ts | 14 +- src/core/core.ts | 225 ---- src/core/dataclasses/ErrorMessage.ts | 74 ++ src/core/dataclasses/EthTokenData.ts | 38 +- src/core/dataclasses/StepMetadata.ts | 107 ++ src/core/dataclasses/TokenData.ts | 82 +- src/core/dataclasses/TokenType.ts | 6 + src/core/dataclasses/index.ts | 3 + src/core/ethers.ts | 47 + src/core/events.ts | 2 +- src/core/explorer.ts | 8 +- src/core/faucet.ts | 58 +- src/core/helper.ts | 42 +- src/core/index.ts | 1 - src/core/interfaces/Config.ts | 11 +- src/core/interfaces/TokenDataMap.ts | 6 +- src/core/interfaces/TokenMetadata.ts | 31 + src/core/interfaces/Tokens.ts | 28 +- src/core/interfaces/index.ts | 1 + .../TokenList/helper.ts => core/metadata.ts} | 64 +- src/core/metaport.ts | 237 ++++ src/core/network.ts | 104 ++ src/core/sfuel.ts | 228 ++-- .../WidgetUI/Themes.ts => core/themes.ts} | 16 +- src/core/tokens/erc20.ts | 173 --- src/core/tokens/eth.ts | 91 -- src/core/tokens/helper.ts | 4 +- src/core/tokens/index.ts | 121 -- src/core/tokens/m2s.ts | 210 ---- src/core/tokens/s2s.ts | 187 --- src/core/transfer_steps.ts | 251 ++-- src/core/views.ts | 2 +- src/core/wagmi_network.ts | 65 ++ src/metadata/addresses/staging.json | 16 +- src/metadata/addresses/staging3.json | 10 - src/metadata/faucet.json | 2 +- src/metadata/metaportConfigStaging.json | 314 +++++ src/metadata/proxy.json | 9 +- src/metadata/schainAbi.json | 2 +- src/store/MetaportState.ts | 326 ++++++ src/store/Store.ts | 75 ++ src/styles/_variables.scss | 14 + src/styles/common.scss | 233 ++++ src/styles/styles.scss | 262 +++++ src/types/custom.d.ts | 23 +- test/TestTest.ts | 105 ++ test/core/tokensTest.ts | 52 - test/test_utils.ts | 14 - tsconfig.json | 2 +- webpack.config.js | 68 +- 159 files changed, 4396 insertions(+), 8792 deletions(-) delete mode 100644 src/components/BalanceBlock/BalanceBlock.tsx delete mode 100644 src/components/BalanceBlock/index.ts create mode 100644 src/components/ChainIcon/ChainIcon.tsx create mode 100644 src/components/ChainIcon/index.ts delete mode 100644 src/components/ChainsList/helper.tsx delete mode 100644 src/components/CommunityPool/CommunityPool.tsx delete mode 100644 src/components/CommunityPool/index.ts delete mode 100644 src/components/CurrentChain/CurrentChain.tsx delete mode 100644 src/components/CurrentChain/index.ts delete mode 100644 src/components/CustomStationUrl/CustomStationUrl.tsx delete mode 100644 src/components/CustomStationUrl/index.ts delete mode 100644 src/components/Debug/Debug.tsx delete mode 100644 src/components/Debug/index.ts delete mode 100644 src/components/ErrorMessage/ErrorMessages.tsx delete mode 100644 src/components/Route/Route.tsx delete mode 100644 src/components/Route/index.ts delete mode 100644 src/components/SFuelBadge/SFuelBadge.scss delete mode 100644 src/components/SFuelBadge/SFuelBadge.tsx delete mode 100644 src/components/SFuelBadge/index.ts delete mode 100644 src/components/SFuelWarning/SFuelWarning.tsx delete mode 100644 src/components/SFuelWarning/index.ts create mode 100644 src/components/SkConnect/SkConnect.tsx create mode 100644 src/components/SkConnect/index.ts create mode 100644 src/components/SkPaper/SkPaper.tsx create mode 100644 src/components/SkPaper/index.ts rename src/components/{StepperV2/StepperV2.scss => Stepper/SkStepper.scss} (100%) create mode 100644 src/components/Stepper/SkStepper.tsx delete mode 100644 src/components/Stepper/Stepper.scss delete mode 100644 src/components/Stepper/Stepper.tsx delete mode 100644 src/components/StepperV2/StepperV2.tsx delete mode 100644 src/components/StepperV2/index.ts create mode 100644 src/components/SwitchDirection/SwitchDirection.tsx create mode 100644 src/components/SwitchDirection/index.ts create mode 100644 src/components/TokenIcon/TokenIcon.tsx create mode 100644 src/components/TokenIcon/index.ts delete mode 100644 src/components/TokenIdInput/TokenIdInput.scss delete mode 100644 src/components/TokenIdInput/TokenIdInput.tsx delete mode 100644 src/components/TokenIdInput/index.ts delete mode 100644 src/components/TransactionData/TransactionData.scss delete mode 100644 src/components/TransactionData/TransactionData.tsx delete mode 100644 src/components/TransactionData/index.ts delete mode 100644 src/components/TransactionsHistory/TransactionsHistory.tsx delete mode 100644 src/components/TransactionsHistory/index.ts delete mode 100644 src/components/TransferETA/TransferETA.tsx delete mode 100644 src/components/TransferETA/index.ts delete mode 100644 src/components/TransferETF/TransferETF.tsx delete mode 100644 src/components/TransferETF/index.ts delete mode 100644 src/components/TransferRequest/TransferRequest.tsx delete mode 100644 src/components/TransferRequest/index.ts delete mode 100644 src/components/TransferSummary/TransferSummary.scss delete mode 100644 src/components/TransferSummary/TransferSummary.tsx delete mode 100644 src/components/TransferSummary/index.ts delete mode 100644 src/components/TransferUI/TransferUI.tsx delete mode 100644 src/components/TransferUI/index.ts delete mode 100644 src/components/UnwrapUI/UnwrapUI.tsx delete mode 100644 src/components/UnwrapUI/index.ts delete mode 100644 src/components/WalletConnector/Connector.tsx delete mode 100644 src/components/WalletConnector/MetamaskConnector.ts delete mode 100644 src/components/WalletConnector/index.ts delete mode 100644 src/components/WalletConnector/metamask.svg delete mode 100644 src/components/WalletConnector/walletconnect.svg delete mode 100644 src/components/WidgetUI/Common.stories.tsx delete mode 100644 src/components/WidgetUI/ERC1155.stories.tsx delete mode 100644 src/components/WidgetUI/ERC20.stories.tsx delete mode 100644 src/components/WidgetUI/ERC721.stories.tsx delete mode 100644 src/components/WidgetUI/Errors.stories.tsx delete mode 100644 src/components/WidgetUI/Guidelines.stories.mdx delete mode 100644 src/components/WidgetUI/Multichain.stories.tsx delete mode 100644 src/components/WidgetUI/StoriesHelper.ts delete mode 100644 src/components/WidgetUI/StorybookHelper.tsx delete mode 100644 src/components/WidgetUI/Themes.stories.tsx delete mode 100644 src/components/WidgetUI/WidgetUI.scss delete mode 100644 src/components/WidgetUI/_variables.scss delete mode 100644 src/components/WrappedTokensWarning/WrappedTokensWarning.tsx delete mode 100644 src/components/WrappedTokensWarning/index.ts delete mode 100644 src/configs/metaportConfigStaging.json delete mode 100644 src/core/actions/erc1155.ts delete mode 100644 src/core/actions/erc721.ts delete mode 100644 src/core/actions/eth.ts create mode 100644 src/core/chain_id.ts create mode 100644 src/core/contracts.ts delete mode 100644 src/core/core.ts create mode 100644 src/core/dataclasses/ErrorMessage.ts create mode 100644 src/core/dataclasses/StepMetadata.ts create mode 100644 src/core/ethers.ts delete mode 100644 src/core/index.ts create mode 100644 src/core/interfaces/TokenMetadata.ts rename src/{components/TokenList/helper.ts => core/metadata.ts} (55%) create mode 100644 src/core/metaport.ts create mode 100644 src/core/network.ts rename src/{components/WidgetUI/Themes.ts => core/themes.ts} (85%) delete mode 100644 src/core/tokens/erc20.ts delete mode 100644 src/core/tokens/eth.ts delete mode 100644 src/core/tokens/index.ts delete mode 100644 src/core/tokens/m2s.ts delete mode 100644 src/core/tokens/s2s.ts create mode 100644 src/core/wagmi_network.ts delete mode 100644 src/metadata/addresses/staging3.json create mode 100644 src/metadata/metaportConfigStaging.json create mode 100644 src/store/MetaportState.ts create mode 100644 src/store/Store.ts create mode 100644 src/styles/_variables.scss create mode 100644 src/styles/common.scss create mode 100644 src/styles/styles.scss create mode 100644 test/TestTest.ts delete mode 100644 test/core/tokensTest.ts diff --git a/.gitignore b/.gitignore index bd5dd30..3c31b63 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ test.mjs src/meta/ metaportConfig.json +metaportConfigMainnet.json storybook-static/ .vercel diff --git a/.storybook/main.ts b/.storybook/main.ts index f53eaea..79487d7 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,32 +1,56 @@ + const path = require("path"); -const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); -import type { StorybookConfig } from "@storybook/react-webpack5"; -const config: StorybookConfig = { +/** @type { import('@storybook/react-webpack5').StorybookConfig } */ +const config = { stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], - core: {}, - addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/preset-create-react-app", "@storybook/addon-interactions"], + staticDirs: ["../build"], + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + // "storybook-css-modules", + // { + // name: '@storybook/addon-styling', + // options: { + // sass: { + // // Require your Sass preprocessor here + // implementation: require('sass'), + // }, + // }, + // } + ], framework: { name: "@storybook/react-webpack5", - options: { - builder: { - lazyCompilation: true - } - } + options: {}, }, docs: { - autodocs: "tag" + autodocs: "tag", }, - staticDirs: ["../build"], webpackFinal: async config => { if (config.resolve && config.resolve.alias) { const { global, ...alias } = config.resolve.alias; - // config.resolve.alias['global'] = undefined; + config.resolve.alias['browser'] = false; // const { ...alias } = config.resolve.alias config.resolve.alias = alias; } + if (config.resolve && config.resolve.fallback) { + config.resolve.fallback = { + path: require.resolve('path-browserify'), + os: "os-browserify/browser", + "fs": false, + "browser": false, + "https": require.resolve("https-browserify"), + "http": require.resolve("stream-http"), + "crypto": require.resolve("crypto-browserify"), + "stream": require.resolve("stream-browserify"), + "buffer": require.resolve("buffer"), + "constants": require.resolve("constants-browserify") + //...config.resolve.fallback, + }; + } if (config.module && config.module.rules) { config.module.rules.push({ test: /\.scss$/, @@ -41,48 +65,10 @@ const config: StorybookConfig = { }, "sass-loader"], include: path.resolve(__dirname, "../") }); - config.module.rules.push({ - test: /\.(ts|tsx)$/, - loader: require.resolve("babel-loader"), - options: { - presets: [["react-app", { - flow: false, - typescript: true, - runtime: 'automatic' - }]] - } - }); - config.module.rules.push({ - test: /\.svg$/, - use: [{ - loader: 'svg-url-loader', - options: { - limit: 10000 - } - }] - }); - } - if (config.plugins) { - config.plugins.push(new NodePolyfillPlugin()); - } - if (config.resolve && config.resolve.extensions) { - config.resolve.extensions.push(".ts", ".tsx"); - } - if (config.resolve && config.resolve.fallback) { - config.resolve.fallback = { - path: require.resolve('path-browserify'), - os: "os-browserify/browser", - "fs": false, - "https": require.resolve("https-browserify"), - "http": require.resolve("stream-http"), - "crypto": require.resolve("crypto-browserify"), - "stream": require.resolve("stream-browserify"), - "buffer": require.resolve("buffer") - //...config.resolve.fallback, - }; } return config; } }; -export default config; \ No newline at end of file +export default config; + diff --git a/package.json b/package.json index 1bf67ba..01786cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@skalenetwork/metaport", - "version": "1.2.0", + "version": "2.0.0", "description": "SKALE Metaport Widget", "keywords": [ "skale", @@ -21,7 +21,7 @@ "prepublish": "npm run build", "build": "NODE_ENV=production webpack --mode=production", "build-stats": " webpack --json > stats.json", - "test": "TS_NODE_PROJECT=\"tsconfig.test.json\" mocha -r ts-node/register test/**/*Test.ts", + "test": "TS_NODE_PROJECT=\"tsconfig.test.json\" mocha -t 300000 -r ts-node/register test/**/*Test.ts", "test-ts": "ts-mocha -n loader=ts-node/esm -p tsconfig.json test/**/*Test.ts", "lint": "tslint -c tslint.json 'src/**/*.ts'", "version": "node -e \"console.log(require('./package.json').version);\"" @@ -40,20 +40,21 @@ "@mui/icons-material": "^5.8.0", "@mui/lab": "^5.0.0-alpha.88", "@mui/material": "^5.8.1", + "@rainbow-me/rainbowkit": "^1.0.6", "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-image": "^2.1.1", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^11.2.1", - "@skalenetwork/ima-js": "1.1.3-custom", + "@skalenetwork/ima-js": "2.0.0-custom.5", "@skaleproject/pow-ethers": "0.3.2", - "@storybook/addon-essentials": "^7.0.6", - "@storybook/addon-interactions": "^7.0.6", - "@storybook/addon-links": "^7.0.6", - "@storybook/addon-mdx-gfm": "^7.0.6", - "@storybook/blocks": "^7.0.6", - "@storybook/preset-create-react-app": "^7.0.6", - "@storybook/react": "^7.0.6", - "@storybook/react-webpack5": "^7.0.6", + "@storybook/addon-essentials": "^7.1.0", + "@storybook/addon-interactions": "^7.1.0", + "@storybook/addon-links": "^7.1.0", + "@storybook/addon-mdx-gfm": "^7.1.0", + "@storybook/addon-styling": "^1.3.4", + "@storybook/blocks": "^7.1.0", + "@storybook/react": "^7.1.0", + "@storybook/react-webpack5": "^7.1.0", "@svgr/webpack": "^7.0.0", "@types/babel__core": "^7.1.19", "@types/mocha": "^9.1.1", @@ -64,6 +65,7 @@ "babel-loader": "^8.2.2", "babel-preset-react-app": "^10.0.0", "coingecko-api-v3": "0.0.13", + "constants-browserify": "^1.0.0", "crypto-browserify": "^3.12.0", "css-loader": "^6.7.1", "debug": "^4.3.4", @@ -76,8 +78,10 @@ "mocha": "^9.2.2", "node-polyfill-webpack-plugin": "^1.1.4", "postcss": "^8.4.14", + "postcss-loader": "^7.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-jazzicon": "^1.0.4", "react-script": "^2.0.5", "react-svg-loader": "^3.0.3", "rollup": "^2.56.3", @@ -88,7 +92,8 @@ "rollup-plugin-typescript2": "^0.29.0", "sass": "^1.54.0", "sass-loader": "^13.0.0", - "storybook": "^7.0.6", + "storybook": "^7.1.0", + "storybook-css-modules": "^1.0.8", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "style-loader": "^3.3.1", @@ -97,9 +102,13 @@ "ts-loader": "^9.3.0", "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", - "typescript": "^4.4.2", + "typescript": "^5.1.6", + "viem": "^1.3.0", + "wagmi": "^1.3.9", "webpack": "^5.73.0", - "webpack-cli": "^4.10.0" + "webpack-cli": "^4.10.0", + "zustand": "^4.3.9" }, - "peerDependencies": {} -} \ No newline at end of file + "peerDependencies": {}, + "dependencies": {} +} diff --git a/src/Metaport.tsx b/src/Metaport.tsx index f851282..5fc667e 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -25,14 +25,14 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; -import { Widget } from './components/Widget'; +import Widget from './components/Widget'; import { internalEvents } from './core/events'; import * as interfaces from './core/interfaces/index'; export * as dataclasses from './core/dataclasses/index'; export * as interfaces from './core/interfaces/index'; -export * as sfuel from './core/sfuel'; +// export * as sfuel from './core/sfuel'; export class Metaport { diff --git a/src/components/AmountErrorMessage/AmountErrorMessage.tsx b/src/components/AmountErrorMessage/AmountErrorMessage.tsx index 9ebd9c9..bc2a838 100644 --- a/src/components/AmountErrorMessage/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage/AmountErrorMessage.tsx @@ -1,22 +1,27 @@ import Collapse from '@mui/material/Collapse'; -import { clsNames } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; +import { cls } from '../../core/helper'; +import common from '../../styles/common.scss'; +import { useMetaportStore } from '../../store/MetaportState' -export default function AmountErrorMessage(props) { + +export default function AmountErrorMessage() { + const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage); return ( - -

+

- 🔴 {props.amountErrorMessage} + 🔴 {amountErrorMessage}

) } diff --git a/src/components/AmountInput/AmountInput.scss b/src/components/AmountInput/AmountInput.scss index 1c09027..bd3b898 100644 --- a/src/components/AmountInput/AmountInput.scss +++ b/src/components/AmountInput/AmountInput.scss @@ -1,7 +1,17 @@ -@import '../WidgetUI/variables'; +@import '../../styles/variables'; .mp__inputAmount { + .tokenSymbol { + font-weight: bold !important; + font-size: 1.3rem !important; + } + + .tokenSymbolPlaceholder { + color: #979797; + } + + :global .MuiInput-root::after { border-bottom: none !important; } @@ -14,6 +24,7 @@ border-radius: 4px 0 0 4px; padding: 9pt 15pt; font-weight: bold !important; + font-size: 1.3rem !important; } :global(.MuiFormControl-root) { @@ -21,7 +32,7 @@ // border-radius: 4px; } - background-color: $sk-paper-color !important; + background-color: transparent !important; border-radius: $sk-border-radius !important; diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index f591c99..c59e4df 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -1,65 +1,86 @@ import React from "react"; +import { useAccount } from 'wagmi' + import TextField from '@mui/material/TextField'; -import Button from '@mui/material/Button'; -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; +import { cls } from '../../core/helper'; +import common from '../../styles/common.scss'; import localStyles from './AmountInput.scss'; -import { TokenType } from '../../core/dataclasses/TokenType'; -import { SFUEL_RESERVE_AMOUNT } from "../../core/constants"; +import { useMetaportStore } from '../../store/MetaportState' + +export default function AmountInput() { -export default function AmountInput(props) { + const { address } = useAccount() + + const token = useMetaportStore((state) => state.token); + const transferInProgress = useMetaportStore((state) => state.transferInProgress); + const setAmount = useMetaportStore((state) => state.setAmount); + const amount = useMetaportStore((state) => state.amount); const handleChange = (event: React.ChangeEvent) => { if (parseFloat(event.target.value) < 0) { - props.setAmount(''); + setAmount('', address); return; } - props.setAmount(event.target.value); + setAmount(event.target.value, address); }; - const setMaxAmount = () => { - if (props.token && !props.token.clone && - (props.token.wrapsSFuel || props.token.type === TokenType.eth)) { - const adjustedAmount = Number(props.token.balance) - SFUEL_RESERVE_AMOUNT; - if (adjustedAmount > 0) { - props.setAmount(adjustedAmount.toString()); - } - } else { - if (props.token && !props.token.clone && props.token.unwrappedBalance) { - props.setAmount(props.token.unwrappedBalance); - } else { - props.setAmount(props.token.balance); - } - } - } + // const setMaxAmount = () => { + // if (token && !token.clone && + // (token.wrapsSFuel || token.type === TokenType.eth)) { + // const adjustedAmount = Number(token.balance) - SFUEL_RESERVE_AMOUNT; + // if (adjustedAmount > 0) { + // props.setAmount(adjustedAmount.toString()); + // } + // } else { + // if (token && !token.clone && token.unwrappedBalance) { + // props.setAmount(token.unwrappedBalance); + // } else { + // props.setAmount(token.balance); + // } + // } + // } - if (!props.token) return; + if (!token) return; return ( -
-
+
+
- {props.maxBtn ?
+ +
+ {token.meta.symbol} +
+ + {/* {props.maxBtn ?
-
: null} +
: null} */}
) } diff --git a/src/components/BalanceBlock/BalanceBlock.tsx b/src/components/BalanceBlock/BalanceBlock.tsx deleted file mode 100644 index 2a5a150..0000000 --- a/src/components/BalanceBlock/BalanceBlock.tsx +++ /dev/null @@ -1,76 +0,0 @@ - -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file BalanceBlock.ts - * @copyright SKALE Labs 2023-Present - */ - -import { ReactElement } from "react"; -import Skeleton from '@mui/material/Skeleton'; -import styles from "../WidgetUI/WidgetUI.scss"; -import { clsNames } from "../../core/helper"; -import { fromWei } from '../../core/convertation'; -import { DEFAULT_ERC20_DECIMALS } from '../../core/constants'; - - -export default function BalanceBlock(props: { - icon: ReactElement, - disabled?: boolean, - balance: string | undefined, - token: string | undefined, - chainName: string, - margTop?: boolean -}) { - const displayedBalance = props.balance ? fromWei( - props.balance as string, - DEFAULT_ERC20_DECIMALS - ).substring(0, 5) : null; - const displayedToken = props.token ? props.token.toUpperCase() : null; - - return (
-
-
- {props.icon} -
-

- Balance on {props.chainName} -

-
-
- {props.balance ? (

- {displayedBalance} {displayedToken} -

) : } -
-
) -} \ No newline at end of file diff --git a/src/components/BalanceBlock/index.ts b/src/components/BalanceBlock/index.ts deleted file mode 100644 index 84ad926..0000000 --- a/src/components/BalanceBlock/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./BalanceBlock"; diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index a8f556a..94c228e 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -1,41 +1,48 @@ -import Tooltip from '@mui/material/Tooltip'; +import { cls, getChainAppsMeta, getChainAlias } from '../../core/helper'; -import { getChainIcon } from '../ChainsList/helper'; +import styles from "../../styles/styles.scss"; +import common from "../../styles/common.scss"; +import { SkaleNetwork } from '../../core/interfaces'; -import { clsNames, getChainAppsMeta } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; -import { MetaportConfig } from '../../core/interfaces'; +import ChainIcon from '../ChainIcon'; export default function ChainApps(props: { - config: MetaportConfig, - chain: string, - dark: boolean + skaleNetwork: SkaleNetwork, + chain: string }) { - const apps = getChainAppsMeta(props.chain, props.config.skaleNetwork); + const apps = getChainAppsMeta(props.chain, props.skaleNetwork); if (!apps || !Object.keys(apps) || Object.keys(apps).length === 0) return
; return ( -
-
+
{Object.keys(apps).map((key, _) => ( - -
- {getChainIcon(props.config.skaleNetwork, props.chain, props.dark, key)} -
-
+
+ +

+ {getChainAlias(props.skaleNetwork, props.chain, key)} +

+
))}
diff --git a/src/components/ChainIcon/ChainIcon.tsx b/src/components/ChainIcon/ChainIcon.tsx new file mode 100644 index 0000000..2488da8 --- /dev/null +++ b/src/components/ChainIcon/ChainIcon.tsx @@ -0,0 +1,30 @@ +import OfflineBoltRoundedIcon from '@mui/icons-material/OfflineBoltRounded'; +import { SkaleNetwork } from '../../core/interfaces'; +import { chainIconPath } from '../../core/metadata'; + +import { cls } from '../../core/helper'; +import styles from "../../styles/styles.scss"; + + +export default function ChainIcon(props: { + skaleNetwork: SkaleNetwork, + chainName: string, + className?: string, + app?: string, + size?: 'xs' | 'sm' | 'md' | 'lg' +}) { + const iconPath = chainIconPath( + props.skaleNetwork, + props.chainName, + props.app + ) + const size = props.size ?? 'sm'; + const className = styles[`chainIcon${size}`] + ' ' + props.className; + if (iconPath !== undefined) { + if (iconPath.default) { + return ; + } + return ; + } + return (); +} \ No newline at end of file diff --git a/src/components/ChainIcon/index.ts b/src/components/ChainIcon/index.ts new file mode 100644 index 0000000..78b7638 --- /dev/null +++ b/src/components/ChainIcon/index.ts @@ -0,0 +1 @@ +export { default } from "./ChainIcon"; diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index 4d59655..b36e98f 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -7,26 +7,27 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import Tooltip from '@mui/material/Tooltip'; import Button from '@mui/material/Button'; -import OfflineBoltIcon from '@mui/icons-material/OfflineBolt'; import ChainApps from '../ChainApps'; -import { getChainIcon } from '../ChainsList/helper'; +import ChainIcon from '../ChainIcon'; -import { clsNames, getChainName } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; +import { MetaportConfig } from '../../core/interfaces'; - -function stringToColor(_, dark) { - if (dark) { - // return `hsl(${hashCode(str) % 360}, 100%, 80%)`; - return 'hsl(120deg 2% 88%)'; - } - return 'hsl(0deg 0% 15%)'; - // return `hsl(${hashCode(str) % 360}, 55%, 40%)`; -} +import { cls, getChainAlias } from '../../core/helper'; +import common from "../../styles/common.scss"; +import styles from "../../styles/styles.scss"; -export default function ChainsList(props) { +export default function ChainsList(props: { + config: MetaportConfig, + expanded: string | false, + setExpanded: (expanded: string | false) => void, + setChain: (chain: string) => void, + chain: string, + disabledChain: string, + from?: boolean, + disabled?: boolean +}) { const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { props.setExpanded(isExpanded ? panel : false); @@ -57,68 +58,125 @@ export default function ChainsList(props) { expandIcon={} aria-controls="panel1bh-content" id="panel1bh-header" + className={styles.accordionSummary} > {props.chain ? ( -
-
- {getChainIcon(props.config.skaleNetwork, props.chain, props.dark)} +
+
+
-

- {getChainName(props.config.chainsMetadata, props.chain, props.config.skaleNetwork)} + {getChainAlias(props.config.skaleNetwork, props.chain)}

-
-
+
+ {/*
-
+
*/}
) : ( -
-
- +
+
+
-

- Select chain +

+ Transfer {props.from ? 'from' : 'to'}...

) } -
+
+
+ +
{schainNames.map((name) => ( +
+ +
+
))}
diff --git a/src/components/ChainsList/helper.tsx b/src/components/ChainsList/helper.tsx deleted file mode 100644 index 527da31..0000000 --- a/src/components/ChainsList/helper.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import OfflineBoltIcon from '@mui/icons-material/OfflineBolt'; - -import { chainIconPath } from '../TokenList/helper'; - - -function stringToColor(_, dark) { - if (dark) { - // return `hsl(${hashCode(str) % 360}, 100%, 80%)`; - return 'hsl(120deg 2% 88%)'; - } - return 'hsl(0deg 0% 15%)'; - // return `hsl(${hashCode(str) % 360}, 55%, 40%)`; -} - - -export function getChainIcon(skaleNetwork: string, chainName: string, dark: boolean, app?: string) { - const iconPath = chainIconPath(skaleNetwork, chainName, app); - if (iconPath !== undefined) { - if (iconPath.default) { - return ; - } - return ; - } - return (); -} diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx deleted file mode 100644 index efed9e2..0000000 --- a/src/components/CommunityPool/CommunityPool.tsx +++ /dev/null @@ -1,193 +0,0 @@ - -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file CommunityPool.ts - * @copyright SKALE Labs 2023-Present - */ - -import React from 'react'; - -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import Grid from '@mui/material/Grid'; - -import Button from '@mui/material/Button'; - -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; - -import CheckCircleIcon from '@mui/icons-material/CheckCircle'; -import ErrorIcon from '@mui/icons-material/Error'; -import AccountBalanceWalletRoundedIcon from '@mui/icons-material/AccountBalanceWalletRounded'; - -import AmountInput from '../AmountInput'; -import BalanceBlock from "../BalanceBlock"; - -import { fromWei } from '../../core/convertation'; -import { DEFAULT_ERC20_DECIMALS } from '../../core/constants'; - -import { clsNames } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; -import { CommunityPoolData } from '../../core/interfaces'; -import { getChainIcon } from '../ChainsList/helper'; - - -export default function CommunityPool(props: { - communityPoolData: CommunityPoolData, - loading: string | false, - rechargeAmount: string, - setRechargeAmount: (amount: string) => {}, - expanded: string | false, - setExpanded: (expanded: string | false) => {}, - recharge: () => {}, - withdraw: () => {}, - marg: boolean -}) { - - const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { - props.setExpanded(isExpanded ? panel : false); - }; - - const text = props.communityPoolData.exitGasOk ? 'Exit gas wallet OK' : 'Recharge exit gas wallet'; - const icon = props.communityPoolData.exitGasOk ? : - const accountBalanceEther = props.communityPoolData.accountBalance ? fromWei( - props.communityPoolData.accountBalance as string, - DEFAULT_ERC20_DECIMALS - ) : null; - - function getRechargeBtnText() { - if (props.loading === 'recharge') return 'Recharging...'; - if (props.loading === 'activate') return 'Activating account...'; - if (Number(props.rechargeAmount) > Number(accountBalanceEther)) return 'Insufficient ETH balance'; - if (props.rechargeAmount === '' || props.rechargeAmount === '0' || !props.rechargeAmount) return 'Enter an amount'; - return 'Recharge exit gas wallet'; - } - - function getWithdrawBtnText() { - if (props.loading === 'withdraw') return 'Withdrawing...'; - return 'Withdraw all'; - } - - return (
- - } - aria-controls="panel1a-content" - id="panel1a-header" - > -
-
- {icon} -
-

- {text} -

-
-
- -
-

- Exit gas wallet support is in BETA.

- This wallet is used to pay for Ethereum gas fees from your transactions to - the Ethereum Mainnet. You may withdraw funds from your SKALE Gas Wallet at - anytime. -

- - - - - - } - chainName='exit wallet' - balance={props.communityPoolData.balance} - token='eth' - /> - - - -

- Recharge amount -

- -
- -
-
- -
- -
-
-
-
-
-
) -} diff --git a/src/components/CommunityPool/index.ts b/src/components/CommunityPool/index.ts deleted file mode 100644 index 982e615..0000000 --- a/src/components/CommunityPool/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./CommunityPool"; \ No newline at end of file diff --git a/src/components/CurrentChain/CurrentChain.tsx b/src/components/CurrentChain/CurrentChain.tsx deleted file mode 100644 index 1c52ce7..0000000 --- a/src/components/CurrentChain/CurrentChain.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import Collapse from '@mui/material/Collapse'; -import IconButton from '@mui/material/IconButton'; -import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos'; -import { clsNames } from '../../core/helper'; -import { View } from '../../core/dataclasses/View'; - -import styles from '../WidgetUI/WidgetUI.scss'; - -import ChainsList from '../ChainsList'; - - -export default function CurrentChain(props) { - return ( - -
- {props.view === View.UNWRAP ? (
- { props.resetWidgetState(true); }} - > - - -

- UNWRAP STUCK TOKENS -

-
) : - (

- FROM -

)} - {/*
- -
*/} -
- -
- ) -} \ No newline at end of file diff --git a/src/components/CurrentChain/index.ts b/src/components/CurrentChain/index.ts deleted file mode 100644 index 13dc6ce..0000000 --- a/src/components/CurrentChain/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./CurrentChain"; diff --git a/src/components/CustomStationUrl/CustomStationUrl.tsx b/src/components/CustomStationUrl/CustomStationUrl.tsx deleted file mode 100644 index b187287..0000000 --- a/src/components/CustomStationUrl/CustomStationUrl.tsx +++ /dev/null @@ -1,52 +0,0 @@ - -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file CustomStationUrl.ts - * @copyright SKALE Labs 2023-Present - */ - - -import Link from '@mui/material/Link'; - -import { DEFAULT_FAUCET_URL } from '../../core/constants'; - -import { clsNames } from '../../core/helper'; -import { StationData } from '../../core/sfuel'; -import styles from "../WidgetUI/WidgetUI.scss"; - - -export default function CustomStationUrl(props: { - stationData: StationData, - type: 'source' | 'destination' | 'hub' -}) { - return (props.stationData && props.stationData.faucetUrl && - props.stationData.faucetUrl !== DEFAULT_FAUCET_URL ?

- 🔗 - - Custom Faucet for {props.type} chain - -

: null) -} diff --git a/src/components/CustomStationUrl/index.ts b/src/components/CustomStationUrl/index.ts deleted file mode 100644 index d753d71..0000000 --- a/src/components/CustomStationUrl/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./CustomStationUrl"; \ No newline at end of file diff --git a/src/components/Debug/Debug.tsx b/src/components/Debug/Debug.tsx deleted file mode 100644 index c6d5012..0000000 --- a/src/components/Debug/Debug.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; - -import styles from '../WidgetUI/WidgetUI.scss'; -import { clsNames } from '../../core/helper'; - -import Collapse from '@mui/material/Collapse'; -import Button from '@mui/material/Button'; - - -export default function Debug(props) { - const [open, setOpen] = React.useState(true); - - const handleClick = (_: React.MouseEvent) => { - setOpen(open ? false : true); - }; - - return (props.config.debug ? ( -
- - - transferRequest: {props.transferRequest ? JSON.stringify(props.transferRequest) : null} -
- transferRequestStatus: {props.transferRequestStatus} -
- transferRequestStep: {props.transferRequestStep} -
- transferRequestSteps len: {props.transferRequestSteps ? props.transferRequestSteps.length : null} -
- transferRequestLoading: {props.transferRequestLoading} -
- view: {props.view} -
- amount: {props.amount} -
- tokenId: {props.tokenId} -
- token: {props.token ? props.token.keyname : null} -
- chain1: {props.chain1} -
- chain2: {props.chain2} -
- actionName: {props.actionName} -
- actionSteps len: {props.actionSteps ? props.actionSteps.length : null} -
- activeStep: {props.activeStep} -
- sFuelOk: {props.sFuelOk} -
- chainId: {props.chainId} -
- extChainId: {props.extChainId} - -
-
-
-
- -
- ) : null - ) -} diff --git a/src/components/Debug/index.ts b/src/components/Debug/index.ts deleted file mode 100644 index 8b88959..0000000 --- a/src/components/Debug/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./Debug"; diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index 581511b..ce79b02 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -1,25 +1,63 @@ import Button from '@mui/material/Button'; -import { clsNames } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; +import { cls } from '../../core/helper'; +import common from "../../styles/common.scss"; +import styles from "../../styles/styles.scss"; +import { ErrorMessage } from '../../core/dataclasses'; -export default function ErrorMessage(props) { +import LinkOffRoundedIcon from '@mui/icons-material/LinkOffRounded'; +import PublicOffRoundedIcon from '@mui/icons-material/PublicOffRounded'; +import SentimentDissatisfiedRoundedIcon from '@mui/icons-material/SentimentDissatisfiedRounded'; +import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded'; + + +const ERROR_ICONS = { + 'link-off': , + 'public-off': , + 'sentiment': , + 'error': +}; + + +export default function Error(props: { errorMessage: ErrorMessage }) { if (!props.errorMessage) return return (
-
- {props.errorMessage.icon} +
+ {ERROR_ICONS[props.errorMessage.icon]}
-
-

+ Error occured +

+

+ Please check logs in developer console +

+
+

{props.errorMessage.text}

@@ -27,7 +65,7 @@ export default function ErrorMessage(props) { {props.errorMessage.fallback ? (
- - ) -} diff --git a/src/components/Route/index.ts b/src/components/Route/index.ts deleted file mode 100644 index 89c99e4..0000000 --- a/src/components/Route/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./Route"; diff --git a/src/components/SFuelBadge/SFuelBadge.scss b/src/components/SFuelBadge/SFuelBadge.scss deleted file mode 100644 index 7a2ef7c..0000000 --- a/src/components/SFuelBadge/SFuelBadge.scss +++ /dev/null @@ -1,28 +0,0 @@ -.mp__chip { - opacity: 0.9; - // color: #3fb354 !important; - - height: 10px; - margin-top: -12px; - - - :global(.MuiChip-root) { - height: 15px !important; - } - - :global(.MuiChip-label) { - font-size: 0.6525rem !important; - font-weight: 600; - letter-spacing: 0.1rem; - // opacity: 0.8; - } - - :global(.MuiChip-filledSuccess) { - background-color: #3fb354 !important; - } - - :global(svg) { - width: 9pt; - height: 9pt; - } -} \ No newline at end of file diff --git a/src/components/SFuelBadge/SFuelBadge.tsx b/src/components/SFuelBadge/SFuelBadge.tsx deleted file mode 100644 index 5e9c83c..0000000 --- a/src/components/SFuelBadge/SFuelBadge.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import Chip from '@mui/material/Chip'; -import DoneIcon from '@mui/icons-material/Done'; -import PriorityHighIcon from '@mui/icons-material/PriorityHigh'; -import ErrorIcon from '@mui/icons-material/Error'; -import Tooltip from '@mui/material/Tooltip'; - -import localStyles from './SFuelBadge.scss'; - - -const BadgeStates = { - success: { - tooltip: 'Everything is good', - color: 'success', - icon: - }, - warning: { - tooltip: 'You do not have enough sFUEL on the chain', - color: 'warning', - icon: - }, - error: { - tooltip: 'You do not have enough sFUEL on the chain', - color: 'error', - icon: - } -} - -export default function SFuelBadge(props) { - if (!props.data || props.data.ok) return; - if (Object.keys(props.data).length === 0) return; - - let type = props.from ? 'error' : 'warning'; - let badgeInfo = BadgeStates[type]; - - return ( - -
- window.open(props.data.faucetUrl, '_blank')?.focus()) : null} - icon={badgeInfo.icon} - label="sFUEL" - size="small" - /> -
-
- ) -} diff --git a/src/components/SFuelBadge/index.ts b/src/components/SFuelBadge/index.ts deleted file mode 100644 index 2b4cdb7..0000000 --- a/src/components/SFuelBadge/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./SFuelBadge"; diff --git a/src/components/SFuelWarning/SFuelWarning.tsx b/src/components/SFuelWarning/SFuelWarning.tsx deleted file mode 100644 index 31a5533..0000000 --- a/src/components/SFuelWarning/SFuelWarning.tsx +++ /dev/null @@ -1,252 +0,0 @@ - -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file SFuelWarning.ts - * @copyright SKALE Labs 2023-Present - */ - -import React, { useEffect } from 'react'; -import debug from 'debug'; - -import Button from '@mui/material/Button'; - -import LoadingButton from '@mui/lab/LoadingButton'; - -import { Collapse } from '@mui/material'; -import { - MAINNET_CHAIN_NAME, - SFUEL_CHEKCS_INTERVAL, - SFUEL_TEXT, - DEFAULT_FAUCET_URL -} from '../../core/constants'; - -import { clsNames } from '../../core/helper'; -import { Station, StationData } from '../../core/sfuel'; -import styles from "../WidgetUI/WidgetUI.scss"; -import { TransferParams } from 'core/interfaces'; -import { View } from '../../core/dataclasses/View'; - -import CustomStationUrl from '../CustomStationUrl'; - - -debug.enable('*'); -const log = debug('metaport:components:SFuel'); - - -export default function SFuelWarning(props: { - chain1: string, - chain2: string, - transferRequest: TransferParams, - config: any, - address: string, - setSFuelOk: any, - view: View -}) { - - let fromChain; - let toChain; - let hubChain; - - if (props.transferRequest && props.view !== View.SANDBOX) { - // log('Getting chains from transferRequest'); - fromChain = props.transferRequest.chains[0]; - toChain = props.transferRequest.chains[1]; - hubChain = props.transferRequest.route ? props.transferRequest.route.hub : undefined; - } else { - // log('Getting chains from props'); - fromChain = props.chain1; - toChain = props.chain2; - } - - const [loading, setLoading] = React.useState(true); - const [mining, setMining] = React.useState(false); - - const [fromChainStation, setFromChainStation] = React.useState(); - const [toChainStation, setToChainStation] = React.useState(); - const [hubChainStation, setHubChainStation] = React.useState(); - - const [updateBalanceTime, setUpdateBalanceTime] = React.useState(Date.now()); - - const [fromStationData, setFromStationData] = React.useState(); - const [toStationData, setToStationData] = React.useState(); - const [hubStationData, setHubStationData] = React.useState(); - - const [sFuelStatus, setSFuelStatus] = React.useState<'action' | 'warning' | 'error'>('action'); - - useEffect(() => { - if (!fromChain || !toChain || !props.address) return; - log('Initializing SFuelWarning web3', fromChain, toChain, hubChain, props.address); - - setFromChainStation(new Station( - fromChain, - props.config.skaleNetwork, - props.config.mainnetEndpoint, - props.config.chainsMetadata - )); - - setToChainStation(new Station( - toChain, - props.config.skaleNetwork, - props.config.mainnetEndpoint, - props.config.chainsMetadata - )); - - if (hubChain) { - setHubChainStation(new Station( - hubChain, - props.config.skaleNetwork, - props.config.mainnetEndpoint, - props.config.chainsMetadata - )); - } - - const interval = setInterval( - () => setUpdateBalanceTime(Date.now()), SFUEL_CHEKCS_INTERVAL * 1000); - return () => clearInterval(interval); - }, [fromChain, toChain, hubChain]); - - useEffect(() => { updateFromStationData(); }, [fromChainStation]); - useEffect(() => { updateToStationData(); }, [toChainStation]); - useEffect(() => { updateHubStationData(); }, [hubChainStation]); - - useEffect(() => { updateBalances(); }, [updateBalanceTime, props.address]); - - useEffect(() => { - if (!fromStationData || !toStationData) return; - setLoading(true); - if (!fromStationData.ok || (hubStationData && !hubStationData.ok)) { - setSFuelStatus('error'); - props.setSFuelOk(false); - } else { - if (!toStationData.ok) { - setSFuelStatus('warning'); - } else { - setSFuelStatus('action'); - } - props.setSFuelOk(true); - } - setLoading(false); - }, [fromStationData, toStationData, hubStationData]); - - function updateBalances() { - updateFromStationData(); - updateToStationData(); - updateHubStationData(); - } - - async function updateFromStationData() { - if (!fromChainStation) return; - setFromStationData(await fromChainStation.getData(props.address)); - } - - async function updateToStationData() { - if (!toChainStation) return; - setToStationData(await toChainStation.getData(props.address)); - } - - async function updateHubStationData() { - if (!hubChainStation) return; - setHubStationData(await hubChainStation.getData(props.address)); - } - - async function doPoW() { - let fromPowRes; - let toPowRes; - let hubPowRes; - - setMining(true); - - if (fromStationData && !fromStationData.ok) { - log(`Doing PoW on ${fromChainStation.chainName}`); - fromPowRes = await fromChainStation.doPoW(props.address); - } - if (toStationData && !toStationData.ok) { - log(`Doing PoW on ${toChainStation.chainName}`); - toPowRes = await toChainStation.doPoW(props.address); - } - if (hubStationData && !hubStationData.ok) { - log(`Doing PoW on ${hubChainStation.chainName}`); - hubPowRes = await hubChainStation.doPoW(props.address); - } - - if ( - (fromPowRes && !fromPowRes.ok) || - (toPowRes && !toPowRes.ok) || - (hubPowRes && !hubPowRes.ok) - ) { - log('PoW failed!'); - if (fromPowRes) log(fromChain, fromPowRes.message); - if (toPowRes) log(toChain, toPowRes.message); - if (hubPowRes) log(hubChain, hubPowRes.message); - window.open(DEFAULT_FAUCET_URL, '_blank'); - } - - await updateFromStationData(); - await updateToStationData(); - await updateHubStationData(); - setMining(false); - } - - const noEth = (fromStationData && !fromStationData.ok && fromChain === MAINNET_CHAIN_NAME); - const noEthDest = (toStationData && !toStationData.ok && toChain === MAINNET_CHAIN_NAME); - - function getSFuelText() { - if (noEth || (fromStationData && fromStationData.ok && noEthDest)) { - return SFUEL_TEXT['gas'][sFuelStatus]; - } - return SFUEL_TEXT['sfuel'][sFuelStatus]; - } - - return ( -

- ⛽ {getSFuelText()} -

- { - !noEth && ((fromStationData && !fromStationData.ok) || !noEthDest) ? (
- {mining ? - Getting sFUEL... - : } - - - -
- ) : null - } -
) -} diff --git a/src/components/SFuelWarning/index.ts b/src/components/SFuelWarning/index.ts deleted file mode 100644 index d96f36f..0000000 --- a/src/components/SFuelWarning/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./SFuelWarning"; \ No newline at end of file diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx new file mode 100644 index 0000000..126052b --- /dev/null +++ b/src/components/SkConnect/SkConnect.tsx @@ -0,0 +1,214 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file WidgetUI.ts + * @copyright SKALE Labs 2023-Present + */ + +import { ConnectButton } from '@rainbow-me/rainbowkit'; +import Jazzicon, { jsNumberForAddress } from 'react-jazzicon' + +import Button from '@mui/material/Button'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + +import { cls } from '../../core/helper'; + +import styles from "../../styles/styles.scss"; +import common from "../../styles/common.scss"; + +import skaleLogoFull from '../WidgetUI/skale_logo.svg'; +import { useMetaportStore } from '../../store/MetaportState'; + +import ChainIcon from "../ChainIcon"; + +export default function SkConnect() { + const transferInProgress = useMetaportStore((state) => state.transferInProgress); + return ( + + {({ + account, + chain, + openAccountModal, + openChainModal, + openConnectModal, + authenticationStatus, + mounted, + }) => { + // Note: If your app doesn't use authentication, you + // can remove all 'authenticationStatus' checks + const ready = mounted && authenticationStatus !== 'loading'; + const connected = + ready && + account && + chain && + (!authenticationStatus || + authenticationStatus === 'authenticated'); + return ( +
+ {(() => { + if (!connected) { + return ( +
+
+ +
+
+ + + + + + + + +
+

Connect a wallet to use SKALE Metaport

+ +
+ + ); + } + if (chain.unsupported) { + return ( + + + + ); + } + return ( +
+
+ {/* */} +
+
+ +
+ +
+ ); + })()} +
+ ); + }} +
) +}; \ No newline at end of file diff --git a/src/components/SkConnect/index.ts b/src/components/SkConnect/index.ts new file mode 100644 index 0000000..03890dd --- /dev/null +++ b/src/components/SkConnect/index.ts @@ -0,0 +1 @@ +export { default } from "./SkConnect"; diff --git a/src/components/SkPaper/SkPaper.tsx b/src/components/SkPaper/SkPaper.tsx new file mode 100644 index 0000000..84ed4e6 --- /dev/null +++ b/src/components/SkPaper/SkPaper.tsx @@ -0,0 +1,55 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file SkPaper.ts + * @copyright SKALE Labs 2023-Present +*/ + +import { ReactElement } from 'react'; +import { cls } from '../../core/helper'; + +import styles from "../../styles/styles.scss"; +import common from "../../styles/common.scss"; + +import { useUIStore } from '../../store/Store' + + +export default function SkPaper(props: { + className?: string, + children?: ReactElement | ReactElement[], + background?: string, + gray?: boolean, + rounded?: boolean, + fullHeight?: boolean, + margTop?: boolean +}) { + const metaportTheme = useUIStore((state) => state.theme); + const localStyle = { + 'background': props.background ?? metaportTheme.background + }; + return (
+ {props.children} +
) +} \ No newline at end of file diff --git a/src/components/SkPaper/index.ts b/src/components/SkPaper/index.ts new file mode 100644 index 0000000..d565a0d --- /dev/null +++ b/src/components/SkPaper/index.ts @@ -0,0 +1 @@ +export { default } from "./SkPaper"; diff --git a/src/components/SkeletonLoader/SkeletonLoader.tsx b/src/components/SkeletonLoader/SkeletonLoader.tsx index 62cca76..ad7d6de 100644 --- a/src/components/SkeletonLoader/SkeletonLoader.tsx +++ b/src/components/SkeletonLoader/SkeletonLoader.tsx @@ -1,11 +1,8 @@ import Skeleton from '@mui/material/Skeleton'; -import styles from '../WidgetUI/WidgetUI.scss'; - - export default function SkeletonLoader(props) { return ( -
+
{props.header ? : null} diff --git a/src/components/StepperV2/StepperV2.scss b/src/components/Stepper/SkStepper.scss similarity index 100% rename from src/components/StepperV2/StepperV2.scss rename to src/components/Stepper/SkStepper.scss diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx new file mode 100644 index 0000000..9ae61e0 --- /dev/null +++ b/src/components/Stepper/SkStepper.tsx @@ -0,0 +1,149 @@ +import { useEffect, useState } from 'react'; + +import Box from '@mui/material/Box'; +import Stepper from '@mui/material/Stepper'; +import Step from '@mui/material/Step'; +import StepLabel from '@mui/material/StepLabel'; +import StepContent from '@mui/material/StepContent'; +import Button from '@mui/material/Button'; +import LoadingButton from '@mui/lab/LoadingButton'; + +import { cls, getChainAlias, getRandom } from '../../core/helper'; +import common from '../../styles/common.scss'; +import styles from '../../styles/styles.scss'; +import localStyles from './SkStepper.scss'; +import ChainIcon from "../ChainIcon"; +import SkPaper from "../SkPaper"; + +import { useMetaportStore } from '../../store/MetaportState' +import { Collapse } from '@mui/material'; +import { SkaleNetwork } from '../../core/interfaces'; + +import { useWalletClient } from 'wagmi' + + +import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded'; + +import { useSwitchNetwork, useAccount } from 'wagmi' +import { SUCCESS_EMOJIS } from '../../core/constants'; + + +export default function SkStepper(props: { + skaleNetwork: SkaleNetwork +}) { + + const { address } = useAccount() + const { switchNetworkAsync } = useSwitchNetwork() + + const { data: walletClient } = useWalletClient() + + const stepsMetadata = useMetaportStore((state) => state.stepsMetadata); + const currentStep = useMetaportStore((state) => state.currentStep); + const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage); + const actionBtnDisabled = useMetaportStore((state) => state.actionBtnDisabled); + const loading = useMetaportStore((state) => state.loading); + const btnText = useMetaportStore((state) => state.btnText); + + const execute = useMetaportStore((state) => state.execute); + const startOver = useMetaportStore((state) => state.startOver); + + const [emoji, setEmoji] = useState(); + useEffect(() => { + setEmoji(getRandom(SUCCESS_EMOJIS)); + }, []); + + if (stepsMetadata.length === 0) return (
); + return ( + + + + + + {stepsMetadata.map((step, i) => ( + +
+
+

{step.headline}

+
+ +
+

{getChainAlias( + props.skaleNetwork, + step.onSource ? step.from : step.to + )}

+
+
+
+ + +

+ {step.text} +

+
+ {loading ? ( + + {btnText} + {/* {props.loadingTokens ? 'Loading...' : step.btnLoadingText} */} + + ) : ( + + )} +
+
+
+
))} +
+
+ + + {currentStep === stepsMetadata.length && ( +
+
+

+ {emoji} Transfer completed +

+
+ +
+ + )} +
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/components/Stepper/Stepper.scss b/src/components/Stepper/Stepper.scss deleted file mode 100644 index 479c847..0000000 --- a/src/components/Stepper/Stepper.scss +++ /dev/null @@ -1,37 +0,0 @@ -.mp__labelStep { - // font-weight: 600 !important; - text-transform: uppercase !important; - font-size: 0.6525rem !important; - line-height: 1.6 !important; - letter-spacing: 0.02857em !important; -} - -.mp__stepper { - - :global span { - font-weight: 600 !important; - text-transform: uppercase !important; - font-size: 0.6525rem !important; - line-height: 1.6 !important; - letter-spacing: 0.02857em !important; - } - - :global .MuiStepConnector-line { - min-height: 0 !important; - } - - :global .MuiBox-root { - margin-bottom: 5px !important; - } - - :global .MuiSvgIcon-root { - width: 18px; - height: 18px; - } - - :global .MuiStepContent-root { - padding-right: 0; - margin-left: 8px; - padding-left: 18px - } -} \ No newline at end of file diff --git a/src/components/Stepper/Stepper.tsx b/src/components/Stepper/Stepper.tsx deleted file mode 100644 index 0651440..0000000 --- a/src/components/Stepper/Stepper.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import Box from '@mui/material/Box'; -import Stepper from '@mui/material/Stepper'; -import Step from '@mui/material/Step'; -import StepLabel from '@mui/material/StepLabel'; -import StepContent from '@mui/material/StepContent'; -import Button from '@mui/material/Button'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import { TokenType } from '../../core/dataclasses/TokenType'; - -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; -import localStyles from './Stepper.scss'; - - -export default function ActionsStepper(props) { - const handleReset = () => { - props.setAmount(''); - props.setTokenId(''); - props.setLoading(false); - props.setAmountLocked(false); - props.setActiveStep(0); - }; - if (!props.token) return; - const nextStepDisabledAmount = [TokenType.erc20, TokenType.erc1155].includes(props.token.type) && (props.amount === '' || Number(props.amount) === 0); - const nextStepDisabledTokenId = [TokenType.erc721, TokenType.erc721meta, TokenType.erc1155].includes(props.token.type) && !props.tokenId; - const noSFuel = props.sFuelData && Object.keys(props.sFuelData).length !== 0 && !props.sFuelData.ok; - const nextStepDisabled = nextStepDisabledAmount || nextStepDisabledTokenId || props.loading || props.actionBtnDisabled || noSFuel; - - return ( - - - {props.actionSteps.map((step, _) => ( - - - {step.label} - - - -
- {props.loading ? ( - - {props.btnText} - - ) : ( - - )} -
-
-
-
- ))} -
- {props.activeStep === props.actionSteps.length && ( - - )} -
- ); -} \ No newline at end of file diff --git a/src/components/Stepper/index.ts b/src/components/Stepper/index.ts index 7512bf9..e3595f4 100644 --- a/src/components/Stepper/index.ts +++ b/src/components/Stepper/index.ts @@ -1 +1 @@ -export { default } from "./Stepper"; +export { default } from "./SkStepper"; diff --git a/src/components/StepperV2/StepperV2.tsx b/src/components/StepperV2/StepperV2.tsx deleted file mode 100644 index 623193a..0000000 --- a/src/components/StepperV2/StepperV2.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import Box from '@mui/material/Box'; -import Stepper from '@mui/material/Stepper'; -import Step from '@mui/material/Step'; -import StepLabel from '@mui/material/StepLabel'; -import StepContent from '@mui/material/StepContent'; -import Button from '@mui/material/Button'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; -import localStyles from './StepperV2.scss'; -import SkeletonLoader from "../SkeletonLoader"; - - -export default function StepperV2(props) { - if (props.transferRequestLoading || !props.transferRequestSteps) return (); - return ( - - - {props.transferRequestSteps.map((step, i) => ( - -
-
-

{step.headline}

-
- {step.chainIcon} -
-

{step.chainName}

-
-
-
- - -

- {step.text} -

-
- {props.loading || props.loadingTokens ? ( - - {props.loadingTokens ? 'Loading...' : props.btnText} - - ) : ( - - )} -
-
-
-
))} -
-
- ); -} \ No newline at end of file diff --git a/src/components/StepperV2/index.ts b/src/components/StepperV2/index.ts deleted file mode 100644 index 656d9db..0000000 --- a/src/components/StepperV2/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./StepperV2"; diff --git a/src/components/SwitchDirection/SwitchDirection.tsx b/src/components/SwitchDirection/SwitchDirection.tsx new file mode 100644 index 0000000..0eb2930 --- /dev/null +++ b/src/components/SwitchDirection/SwitchDirection.tsx @@ -0,0 +1,73 @@ +import { useRef } from 'react'; + +import IconButton from '@mui/material/IconButton'; +import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded'; +import styles from '../../styles/styles.scss'; +import common from '../../styles/common.scss'; +import { cls } from '../../core/helper'; + +import { useUIStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' + + +export default function SwitchDirection() { + + const myElement = useRef(null); + + const metaportTheme = useUIStore((state) => state.theme); + + const chainName1 = useMetaportStore((state) => state.chainName1); + const chainName2 = useMetaportStore((state) => state.chainName2); + + const setChainName1 = useMetaportStore((state) => state.setChainName1); + const setChainName2 = useMetaportStore((state) => state.setChainName2); + const startOver = useMetaportStore((state) => state.startOver); + const loading = useMetaportStore((state) => state.loading); + const transferInProgress = useMetaportStore((state) => state.transferInProgress); + + return ( +
+
+
+
+ { + const element = myElement.current; + const rotate = () => { + if (element) { + element.classList.add('spin'); + setTimeout(() => { + element.classList.remove('spin'); + }, 400); + } + }; + rotate() + let chain1 = chainName1; + setChainName1(chainName2); + setChainName2(chain1); + startOver(); + }}> + + +
+
+
+ + +
+ ) +} \ No newline at end of file diff --git a/src/components/SwitchDirection/index.ts b/src/components/SwitchDirection/index.ts new file mode 100644 index 0000000..b86abe8 --- /dev/null +++ b/src/components/SwitchDirection/index.ts @@ -0,0 +1 @@ +export { default } from "./SwitchDirection"; diff --git a/src/components/TokenIcon/TokenIcon.tsx b/src/components/TokenIcon/TokenIcon.tsx new file mode 100644 index 0000000..2332518 --- /dev/null +++ b/src/components/TokenIcon/TokenIcon.tsx @@ -0,0 +1,42 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file TokensIcon.ts + * @copyright SKALE Labs 2023-Present + */ + +import TollRoundedIcon from '@mui/icons-material/TollRounded'; +import { TokenData } from '../../core/dataclasses'; +import { tokenIconPath } from '../../core/metadata'; + +import styles from "../../styles/styles.scss"; + + +export default function TokenIcon(props: { + token?: TokenData, + size?: 'xs' | 'sm' | 'md' | 'lg' +}) { + const size = props.size ?? 'sm'; + const className = styles[`chainIcon${size}`]; + if (props.token === undefined || props.token === null) { + return + // return + } + return +} \ No newline at end of file diff --git a/src/components/TokenIcon/index.ts b/src/components/TokenIcon/index.ts new file mode 100644 index 0000000..6412a21 --- /dev/null +++ b/src/components/TokenIcon/index.ts @@ -0,0 +1 @@ +export { default } from "./TokenIcon"; diff --git a/src/components/TokenIdInput/TokenIdInput.scss b/src/components/TokenIdInput/TokenIdInput.scss deleted file mode 100644 index bc1c2ae..0000000 --- a/src/components/TokenIdInput/TokenIdInput.scss +++ /dev/null @@ -1,43 +0,0 @@ -@import '../WidgetUI/variables'; - -.mp__inputAmount { - - :global .MuiInput-root::after { - border-bottom: none !important; - } - - :global .MuiInput-root::before { - border-bottom: none !important; - } - - :global input { - border-radius: 4px 0 0 4px; - padding: 9pt 15pt; - font-weight: bold !important; - } - - :global .MuiFormControl-root { - width: 100%; - // border-radius: 4px; - } - - background-color: $sk-paper-color !important; - border-radius: $sk-border-radius !important; - - :global .MuiButton-root { - border-radius: 0 10px 10px 0 !important; - } - - input::-webkit-outer-spin-button, - input::-webkit-inner-spin-button { - /* display: none; <- Crashes Chrome on hover */ - -webkit-appearance: none; - margin: 0; - /* <-- Apparently some margin are still there even though it's hidden */ - } - - :global input[type=number] { - -moz-appearance: textfield; - /* Firefox */ - } -} \ No newline at end of file diff --git a/src/components/TokenIdInput/TokenIdInput.tsx b/src/components/TokenIdInput/TokenIdInput.tsx deleted file mode 100644 index 23cae03..0000000 --- a/src/components/TokenIdInput/TokenIdInput.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; -import TextField from '@mui/material/TextField'; - -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; -import localStyles from './TokenIdInput.scss'; - - -export default function TokenIdInput(props) { - - const handleChange = (event: React.ChangeEvent) => { - props.setTokenId(event.target.value); - }; - - if (!props.token) return; - return ( -
- -
- {/*
- -
*/} - -
-
-

- Token ID -

-
-
- ) -} diff --git a/src/components/TokenIdInput/index.ts b/src/components/TokenIdInput/index.ts deleted file mode 100644 index c0cab84..0000000 --- a/src/components/TokenIdInput/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TokenIdInput"; diff --git a/src/components/TokenList/TokenBalance.tsx b/src/components/TokenList/TokenBalance.tsx index 9cc4091..ca02b24 100644 --- a/src/components/TokenList/TokenBalance.tsx +++ b/src/components/TokenList/TokenBalance.tsx @@ -1,35 +1,37 @@ -import { TokenType } from '../../core/dataclasses/TokenType'; -import { clsNames } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; +import { formatUnits } from 'ethers'; +import { TokenType, TokenData } from '../../core/dataclasses'; +import { TokenBalancesMap } from '../../core/interfaces'; -function roundDown(number, decimals) { - decimals = decimals || 0; - return (Math.floor(number * Math.pow(10, decimals)) / Math.pow(10, decimals)); +import { cls } from '../../core/helper'; +import common from "../../styles/common.scss"; + + +function formatBalance(balance: bigint, token: TokenData): string { + return formatUnits(balance, parseInt(token.meta.decimals)); } -export default function TokenBalance(props) { +export default function TokenBalance(props: { + token: TokenData, + tokenBalances: TokenBalancesMap +}) { if ([TokenType.erc721, TokenType.erc721meta, TokenType.erc1155].includes(props.token.type)) return; - let balance = props.token.unwrappedSymbol ? props.token.unwrappedBalance : props.token.balance; - let symbol = props.token.unwrappedSymbol ? props.token.unwrappedSymbol : props.token.symbol; - if (props.token.clone) { - balance = props.token.balance; - symbol = props.token.cloneSymbol ? props.token.cloneSymbol : symbol; - } + const balance = props.tokenBalances[props.token.keyname]; - if (!balance) return; + if (balance === undefined || balance === null) return; return ( -
-

+

- {roundDown(balance, 8)} {symbol} + {formatBalance(balance, props.token)} {props.token.meta.symbol}

) diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index 2fbdcfa..629eacd 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -1,5 +1,8 @@ +import { useEffect } from 'react'; import React from 'react'; +import { useAccount } from 'wagmi' + import Accordion from '@mui/material/Accordion'; import AccordionDetails from '@mui/material/AccordionDetails'; import AccordionSummary from '@mui/material/AccordionSummary'; @@ -8,26 +11,66 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { getAvailableTokensTotal, getDefaultToken } from '../../core/tokens/helper'; -import { clsNames } from '../../core/helper'; +import { cls } from '../../core/helper'; -import ErrorMessage, { NoTokenPairsMessage } from '../ErrorMessage'; +import ErrorMessage from '../ErrorMessage'; import TokenListSection from '../TokenListSection'; import TokenBalance from './TokenBalance'; +import TokenIcon from "../TokenIcon"; + +import styles from "../../styles/styles.scss"; +import common from "../../styles/common.scss"; +import { getTokenName } from "../../core/metadata"; + +import { useCollapseStore } from '../../store/Store'; +import { useMetaportStore } from '../../store/MetaportState'; +import { TokenType, NoTokenPairsMessage } from '../../core/dataclasses'; + + +export default function TokenList() { + + const token = useMetaportStore((state) => state.token); + const tokens = useMetaportStore((state) => state.tokens); + const setToken = useMetaportStore((state) => state.setToken); + const updateTokenBalances = useMetaportStore((state) => state.updateTokenBalances); + const tokenContracts = useMetaportStore((state) => state.tokenContracts); + + const tokenBalances = useMetaportStore((state) => state.tokenBalances); + const transferInProgress = useMetaportStore((state) => state.transferInProgress); + + const expandedTokens = useCollapseStore((state) => state.expandedTokens); + const setExpandedTokens = useCollapseStore((state) => state.setExpandedTokens); -import styles from "../WidgetUI/WidgetUI.scss"; -import localStyles from "./TokenList.scss"; -import { getIconSrc, iconPath, getTokenName } from "./helper"; + const { address } = useAccount(); -export default function TokenList(props) { - let availableTokensTotal = getAvailableTokensTotal(props.availableTokens); + useEffect(() => { + updateTokenBalances(address); // Fetch users immediately on component mount + const intervalId = setInterval(() => { + updateTokenBalances(address); + }, 10000) // Fetch users every 10 seconds + + return () => { + clearInterval(intervalId) // Clear interval on component unmount + } + }, [updateTokenBalances, tokenContracts, address]); + + useEffect(() => { + const defaultToken = getDefaultToken(tokens); + if (defaultToken) { + setToken(defaultToken); + } + }, [tokens]); + + + let availableTokensTotal = getAvailableTokensTotal(tokens); let disabled = availableTokensTotal === 1; let noTokens = availableTokensTotal === 0; const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { - props.setExpanded(isExpanded ? panel : false); + setExpandedTokens(isExpanded ? panel : false); }; if (noTokens) { @@ -36,89 +79,72 @@ export default function TokenList(props) { />) } - const defaultToken = getDefaultToken(props.availableTokens); - if (defaultToken) { - props.setToken(defaultToken); - } - return (
} aria-controls="panel1bh-content" id="panel1bh-header" + className={styles.accordionSummary} + style={{paddingTop: '0'}} > - {props.token ? ( -
-
- -
-

- {getTokenName(props.token)} -

- +
+
+
- ) : ( -
-
- -
-

- Select token -

+

+ {token ? getTokenName(token) : 'Select token'} +

+
+ {token ? : null}
- ) - } +
+ {/* */} - diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index 07d6740..d1804a7 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -1,17 +1,24 @@ -import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; -import TokenData from '../../core/dataclasses/TokenData'; -import { clsNames } from '../../core/helper'; +import { TokenData, TokenType } from '../../core/dataclasses'; +import { TokenBalancesMap, TokenDataMap } from '../../core/interfaces'; +import { cls } from '../../core/helper'; import TokenBalance from '../TokenList/TokenBalance'; +import TokenIcon from '../TokenIcon'; -import styles from "../WidgetUI/WidgetUI.scss"; -import localStyles from "./TokenListSection.scss"; -import { getIconSrc, getTokenName } from "../TokenList/helper"; +import common from "../../styles/common.scss"; +import { getTokenName } from "../../core/metadata"; -export default function TokenListSection(props) { + +export default function TokenListSection(props: { + setExpanded: (expanded: string | false) => void, + setToken: (token: TokenData) => void, + tokens: TokenDataMap, + type: TokenType, + tokenBalances?: TokenBalancesMap +}) { function handle(tokenData: TokenData): void { props.setExpanded(false); @@ -21,37 +28,57 @@ export default function TokenListSection(props) { if (Object.keys(props.tokens).length === 0) return; return ( -
-

+

+

{props.type}

{Object.keys(props.tokens).map((key, _) => ( - - - +
+ ))}
) diff --git a/src/components/TransactionData/TransactionData.scss b/src/components/TransactionData/TransactionData.scss deleted file mode 100644 index 1174d9b..0000000 --- a/src/components/TransactionData/TransactionData.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import '../WidgetUI/variables'; - -.br__transactionDataIcon { - width: 30px; - height: 30px; - border-radius: 50%; - - svg, - img { - width: 15px !important; - height: 15px !important; - } -} - - -.sk__openExplorerBtn { - svg { - width: 15px !important; - height: 15px !important; - } - - background-color: $sk-paper-color !important; - - height: 30px; - width: 30px; -} \ No newline at end of file diff --git a/src/components/TransactionData/TransactionData.tsx b/src/components/TransactionData/TransactionData.tsx deleted file mode 100644 index b5db127..0000000 --- a/src/components/TransactionData/TransactionData.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -/** - * @file TransactionData.ts - * @copyright SKALE Labs 2023-Present -*/ - -import { ReactElement } from 'react'; - -import IconButton from '@mui/material/IconButton'; - -import MoveUpIcon from '@mui/icons-material/MoveUp'; -import MoveDownIcon from '@mui/icons-material/MoveDown'; -import LockOpenIcon from '@mui/icons-material/LockOpen'; -import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward'; -import OpenInNewIcon from '@mui/icons-material/OpenInNew'; -import LogoutIcon from '@mui/icons-material/Logout'; -import DoneRoundedIcon from '@mui/icons-material/DoneRounded'; - -import { getTxUrl } from '../../core/explorer'; -import { MetaportConfig, TransactionHistory } from '../../core/interfaces'; - -import styles from "../WidgetUI/WidgetUI.scss"; -import localStyles from "./TransactionData.scss"; -import { clsNames } from '../../core/helper'; - - -const actionIcons: { [actionName: string]: ReactElement; } = { - 'deposit': , - 'transferToSchain': , - 'wrap': , - 'unwrap': , - 'getMyEth': , - 'withdraw': , - 'approve': , - 'approveWrap': , - 'wrapsfuel': -} - - -const actionAliases: { [actionName: string]: string; } = { - 'deposit': 'Deposit', - 'transferToSchain': 'Transfer', - 'wrap': 'Wrap', - 'unwrap': 'Unwrap', - 'getMyEth': 'Unlock ETH', - 'withdraw': 'Withdraw', - 'approve': 'Approve', - 'approveWrap': 'Approve wrap', - 'wrapsfuel': 'Wrap sFUEL' -} - - -export default function TransactionData(props: { - transactionData: TransactionHistory, - config: MetaportConfig -}) { - const explorerUrl = getTxUrl( - props.transactionData.chainName, - props.config.skaleNetwork, - props.transactionData.tx.transactionHash - ); - return (
-
-
- {actionIcons[props.transactionData.txName]} -
-
-
-
-

- {actionAliases[props.transactionData.txName]} -

-

- {new Date(props.transactionData.timestamp * 1000).toUTCString()} -

-
-
-
- - - -
-
) -} diff --git a/src/components/TransactionData/index.ts b/src/components/TransactionData/index.ts deleted file mode 100644 index d73f5d5..0000000 --- a/src/components/TransactionData/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TransactionData"; diff --git a/src/components/TransactionsHistory/TransactionsHistory.tsx b/src/components/TransactionsHistory/TransactionsHistory.tsx deleted file mode 100644 index d2cefea..0000000 --- a/src/components/TransactionsHistory/TransactionsHistory.tsx +++ /dev/null @@ -1,119 +0,0 @@ - -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file TransactionsHistory.ts - * @copyright SKALE Labs 2023-Present - */ - -import React from 'react'; - -import { Collapse } from '@mui/material'; -import Button from '@mui/material/Button'; - -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import HistoryRoundedIcon from '@mui/icons-material/HistoryRounded'; -import ClearAllIcon from '@mui/icons-material/ClearAll'; - -import { TransactionHistory, MetaportConfig } from '../../core/interfaces'; -import TransactionData from '../TransactionData'; - -import { clsNames } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; - - -export default function TransactionsHistory(props: { - transactionsHistory: TransactionHistory[], - clearTransactionsHistory: any, - config: MetaportConfig, - setExpanded: any, - expanded: string | false, - transferRequestView?: boolean -}) { - - const handleChange = - (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { - props.setExpanded(isExpanded ? panel : false); - }; - - function clearTransferHistory() { - props.clearTransactionsHistory(); - props.setExpanded(false); - } - - return ( - -
-
- - } - aria-controls="panel1a-content" - id="panel1a-header" - > -
-
- -
-

- Completed transactions ({props.transactionsHistory.length}) -

-
-
- -
- {props.transactionsHistory.map((transactionData: any) => ( - - ))} - -
- -
- -
-
-
-
) -} diff --git a/src/components/TransactionsHistory/index.ts b/src/components/TransactionsHistory/index.ts deleted file mode 100644 index 6519b11..0000000 --- a/src/components/TransactionsHistory/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TransactionsHistory"; \ No newline at end of file diff --git a/src/components/TransferETA/TransferETA.tsx b/src/components/TransferETA/TransferETA.tsx deleted file mode 100644 index ebe5374..0000000 --- a/src/components/TransferETA/TransferETA.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect } from 'react'; -import Tooltip from '@mui/material/Tooltip'; -import InfoIcon from '@mui/icons-material/Info'; -import Skeleton from '@mui/material/Skeleton'; - -import { isMainnet, clsNames } from '../../core/helper'; -import { IMA_M2S_WAIT, IMA_S2S_WAIT, IMA_HUB_WAIT } from '../../core/constants'; -import { getAvgWaitTime } from '../../core/gas_station'; -import styles from '../WidgetUI/WidgetUI.scss'; - - -export default function TransferETA(props) { - const [eta, setEta] = React.useState(); - const [isLoaded, setIsLoaded] = React.useState(false); - - async function calcETA() { - setIsLoaded(false); - let baseETA = 0; - const fromMainnet = isMainnet(props.transferRequest.chains[0]); - const toMainnet = isMainnet(props.transferRequest.chains[1]); - baseETA += fromMainnet || toMainnet ? IMA_M2S_WAIT : IMA_S2S_WAIT; - if (props.transferRequest.route && props.transferRequest.route.hub) baseETA += IMA_HUB_WAIT; - if (fromMainnet || toMainnet) baseETA += await getAvgWaitTime(); - setEta(baseETA) - setIsLoaded(true); - } - - useEffect(() => { - if (props.transferRequest) calcETA(); - }, [props.transferRequest]); - - const tooltipText = 'Estimated transfer time (may vary depending on the network load)'; - - return ( - -
-
-

- ETA -

- -
- {isLoaded ? ( -

~{eta}-{eta + 2} min

) : ( - - )} -
-
- ) -} diff --git a/src/components/TransferETA/index.ts b/src/components/TransferETA/index.ts deleted file mode 100644 index f7a5cb6..0000000 --- a/src/components/TransferETA/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TransferETA"; diff --git a/src/components/TransferETF/TransferETF.tsx b/src/components/TransferETF/TransferETF.tsx deleted file mode 100644 index 626a592..0000000 --- a/src/components/TransferETF/TransferETF.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useEffect } from 'react'; -import Tooltip from '@mui/material/Tooltip'; -import InfoIcon from '@mui/icons-material/Info'; -import Skeleton from '@mui/material/Skeleton'; - -import { isMainnet, clsNames } from '../../core/helper'; -import { getTransactionFee } from '../../core/fee_calculator'; -import styles from '../WidgetUI/WidgetUI.scss'; - - -function roundDown(number, decimals) { - decimals = decimals || 0; - return (Math.floor(number * Math.pow(10, decimals)) / Math.pow(10, decimals)); -} - - -export default function TransferETF(props) { - const [etf, setEtf] = React.useState(); - const [isLoaded, setIsLoaded] = React.useState(false); - - async function calcETF() { - setIsLoaded(false); - const fromMainnet = isMainnet(props.transferRequest.chains[0]); - let baseETF = 0; - if (fromMainnet) baseETF = await getTransactionFee(props.transferRequest); - setEtf(baseETF) - setIsLoaded(true); - } - - useEffect(() => { - if (props.transferRequest) calcETF(); - }, [props.transferRequest]); - - const tooltipText = 'Estimated transaction fee (You pay only for Mainnet transactions, all transfers within SKALE are free)'; - const etfText = (etf === 0) ? 'Free' : `~${roundDown(etf, 3)} USD` - - return ( - -
-
-

- Estimated Transaction Fee -

- -
- {isLoaded ? ( -

{etfText}

) : ( - - )} -
-
- ) -} diff --git a/src/components/TransferETF/index.ts b/src/components/TransferETF/index.ts deleted file mode 100644 index 2e377d0..0000000 --- a/src/components/TransferETF/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TransferETF"; diff --git a/src/components/TransferRequest/TransferRequest.tsx b/src/components/TransferRequest/TransferRequest.tsx deleted file mode 100644 index 689bab6..0000000 --- a/src/components/TransferRequest/TransferRequest.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import Collapse from '@mui/material/Collapse'; -import Button from '@mui/material/Button'; - -import styles from "../WidgetUI/WidgetUI.scss"; -import { clsNames, getChainName } from '../../core/helper'; - -import ErrorMessage from '../ErrorMessage'; -import StepperV2 from '../StepperV2'; -import TransferSummary from '../TransferSummary'; -import Route from '../Route'; -import { getIconSrc } from "../TokenList/helper"; - -import TokenData from '../../core/dataclasses/TokenData'; -import EthTokenData from '../../core/dataclasses/EthTokenData'; -import { TransferRequestStatus } from '../../core/dataclasses'; -import { TokenType } from '../../core/dataclasses/TokenType'; -import * as interfaces from '../../core/interfaces/index'; -import { isTransferRequestSteps } from '../../core/views'; - -import TransactionsHistory from '../TransactionsHistory'; -import SkeletonLoader from '../SkeletonLoader'; -import WrappedTokensWarning from '../WrappedTokensWarning'; -import SFuelWarning from '../SFuelWarning'; -import AmountErrorMessage from '../AmountErrorMessage'; -import CommunityPool from '../CommunityPool'; - - -function getTokenDataFromConfig( - configTokens: interfaces.TokensMap, - transferRequest: interfaces.TransferParams -): TokenData { - // TODO: refactor! - - if (transferRequest.tokenType === TokenType.eth) { - return new EthTokenData(false); - } - - let configToken; - let isClone = false; - - const fromChainName = transferRequest.chains[0]; - const toChainName = transferRequest.route ? transferRequest.route.hub : transferRequest.chains[1]; - - if (configTokens[fromChainName] && - configTokens[fromChainName][transferRequest.tokenType] && - configTokens[fromChainName][transferRequest.tokenType][transferRequest.tokenKeyname]) { - configToken = configTokens[fromChainName][transferRequest.tokenType][transferRequest.tokenKeyname]; - } - if (configTokens[toChainName] && - configTokens[toChainName][transferRequest.tokenType] && - configTokens[toChainName][transferRequest.tokenType][transferRequest.tokenKeyname]) { - configToken = configTokens[toChainName][transferRequest.tokenType][transferRequest.tokenKeyname]; - isClone = true; - } - if (!configToken) return; - - let unwrappedSymbol; - let unwrappedAddress; - let unwrappedIconUrl; - if (configToken.wraps) { - unwrappedSymbol = configToken.wraps.symbol; - unwrappedAddress = configToken.wraps.address; - unwrappedIconUrl = configToken.wraps.iconUrl; - } - - return new TokenData( - null, - configToken.address, - configToken.name, - configToken.symbol, - configToken.cloneSymbol, - isClone, - configToken.iconUrl, - configToken.decimals, - transferRequest.tokenType, - unwrappedSymbol, - unwrappedAddress, - unwrappedIconUrl - ); -} - - -export default function TransferRequest(props) { - if (!props.transferRequest) { - return () - } - - const trReq: interfaces.TransferParams = props.transferRequest; - const token = getTokenDataFromConfig(props.config.tokens, trReq); - - const fromChainName = getChainName( - props.config.chainsMetadata, - trReq.chains[0], - props.config.skaleNetwork, - trReq.fromApp - ); - const toChainName = getChainName( - props.config.chainsMetadata, - trReq.chains[1], - props.config.skaleNetwork, - trReq.toApp - ); - - let explanationText = 'Transfer assets from ' + fromChainName + ' to ' + toChainName + '.'; - if (trReq.route) { - const hubChainName = getChainName( - props.config.chainsMetadata, - trReq.route.hub, - props.config.skaleNetwork - ); - if (token.clone) { - explanationText += ' Tokens will be unwrapped on ' + hubChainName + '.'; - } else { - explanationText += ' Tokens will be wrapped on ' + hubChainName + '.'; - } - }; - - const showAmount = trReq.lockValue && trReq.amount; - let amountText = showAmount ? `${trReq.amount} ${token.symbol}` : token.symbol; - amountText += trReq.tokenId ? ` (#${trReq.tokenId})` : ''; - - return ( -
- -
-

Transfer

- -

- {amountText} -

-
- - - - - - - {isTransferRequestSteps(props.view) ? - : - - } - - - -

- 💫 You've successfully transferred {amountText} from {fromChainName} to {toChainName}. -

- -
- - {props.transferRequestStep === 0 ? () : null} - - - -
- - - - - - -
- ) -} diff --git a/src/components/TransferRequest/index.ts b/src/components/TransferRequest/index.ts deleted file mode 100644 index 51e3ef6..0000000 --- a/src/components/TransferRequest/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TransferRequest"; diff --git a/src/components/TransferSummary/TransferSummary.scss b/src/components/TransferSummary/TransferSummary.scss deleted file mode 100644 index abd3c86..0000000 --- a/src/components/TransferSummary/TransferSummary.scss +++ /dev/null @@ -1,71 +0,0 @@ -.mp__labelStep { - // font-weight: 600 !important; - text-transform: uppercase !important; - font-size: 0.6525rem !important; - line-height: 1.6 !important; - letter-spacing: 0.02857em !important; -} - -.mp__stepper { - - :global span { - font-weight: 600 !important; - text-transform: none !important; - font-size: 0.9025rem !important; - line-height: 1.6 !important; - //letter-spacing: 0.02857em !important; - font-family: inherit !important; - } - - :global .MuiStepConnector-line { - min-height: 0 !important; - } - - :global .MuiBox-root { - margin-bottom: 5px !important; - } - - :global .MuiSvgIcon-root { - width: 22px; - height: 22px; - align-items: start; - } - - :global .MuiStepContent-root { - padding-right: 0; - margin-left: 8px; - padding-left: 18px; - border-left: none !important; - } - - :global(.MuiChip-icon) { - width: 15pt; - height: 15pt; - } - - :global(.MuiStepLabel-root) { - align-items: start; - } - - :global(.MuiStepLabel-iconContainer) { - margin-top: 11pt; - } - - :global(.MuiStepLabel-labelContainer) { - img { - filter: saturate(0) opacity(0.7); - } - - } - - - :global(.MuiStepLabel-label.Mui-active) { - span, svg { - color: inherit; - } - - img { - filter: inherit; - } - } -} \ No newline at end of file diff --git a/src/components/TransferSummary/TransferSummary.tsx b/src/components/TransferSummary/TransferSummary.tsx deleted file mode 100644 index ba82dd7..0000000 --- a/src/components/TransferSummary/TransferSummary.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; -import AmountInput from "../AmountInput"; - -import TransferETA from '../TransferETA'; -import TransferETF from '../TransferETF'; -import { Collapse } from '@mui/material'; - - -export default function TransferSummary(props) { - const text = props.transferRequest && props.transferRequest.text ? props.transferRequest.text : props.explanationText; - return ( - -
- -
- -
-
- - {props.transferRequest.lockValue ? null :
-

- Amount -

- -
- } - -

- {text} -

- -
-
- ); -} \ No newline at end of file diff --git a/src/components/TransferSummary/index.ts b/src/components/TransferSummary/index.ts deleted file mode 100644 index 3de6f45..0000000 --- a/src/components/TransferSummary/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TransferSummary"; diff --git a/src/components/TransferUI/TransferUI.tsx b/src/components/TransferUI/TransferUI.tsx deleted file mode 100644 index 97bb79a..0000000 --- a/src/components/TransferUI/TransferUI.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import Collapse from '@mui/material/Collapse'; -import Skeleton from '@mui/material/Skeleton'; -import IconButton from '@mui/material/IconButton'; -import Tooltip from '@mui/material/Tooltip'; -import ImportExportIcon from '@mui/icons-material/ImportExport'; - -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; - -import ChainsList from '../ChainsList'; -import TokenList from '../TokenList'; -import AmountInput from '../AmountInput'; -import TokenIdInput from '../TokenIdInput'; -import Stepper from '../Stepper'; -import AmountErrorMessage from '../AmountErrorMessage'; -import CommunityPool from '../CommunityPool'; - -import { TokenType } from '../../core/dataclasses/TokenType'; - - -export default function TransferUI(props) { - return ( -
- - - { - let chain1 = props.chain1; - props.cleanData(); - props.setChain1(props.chain2); - props.setChain2(chain1); - }}> - - - - - - -
-

- TO -

- {/*
- -
*/} -
- -
- - - -

- Token -

-
- -
- {(props.loadingTokens || props.transferRequest) ? ( - ) : ()} -
-
- -
- -
-
- -
- -
-
- -
- -
- {!props.token ? ( -
- ) : ( -
- {(!props.actionSteps) ? () : ()} -
- )} -
-
- - - -
- ) -} \ No newline at end of file diff --git a/src/components/TransferUI/index.ts b/src/components/TransferUI/index.ts deleted file mode 100644 index 4a06d20..0000000 --- a/src/components/TransferUI/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TransferUI"; diff --git a/src/components/UnwrapUI/UnwrapUI.tsx b/src/components/UnwrapUI/UnwrapUI.tsx deleted file mode 100644 index c9bc287..0000000 --- a/src/components/UnwrapUI/UnwrapUI.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import Skeleton from '@mui/material/Skeleton'; -import Button from '@mui/material/Button'; -import Collapse from '@mui/material/Collapse'; -import RocketLaunchIcon from '@mui/icons-material/RocketLaunch'; - -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; - -import Stepper from '../Stepper'; -import TokenList from '../TokenList'; -import AmountErrorMessage from '../AmountErrorMessage'; - - -export default function UnwrapUI(props) { - if (!props.wrappedTokens || !props.wrappedTokens.erc20) return; - const wrappedTokens = Object.entries(props.wrappedTokens.erc20); - if (wrappedTokens.length === 0) return ( -
-
- -
-
-

- You don't have any wrapped tokens -

-
- -
- ); - - return ( -
- - - - - -
- {!props.token ? ( -
- ) : ( -
- {(!props.actionSteps) ? () : ()} -
- )} -
-
-
- ) -} \ No newline at end of file diff --git a/src/components/UnwrapUI/index.ts b/src/components/UnwrapUI/index.ts deleted file mode 100644 index 2271cd1..0000000 --- a/src/components/UnwrapUI/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./UnwrapUI"; diff --git a/src/components/WalletConnector/Connector.tsx b/src/components/WalletConnector/Connector.tsx deleted file mode 100644 index 47ce418..0000000 --- a/src/components/WalletConnector/Connector.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Paper from '@mui/material/Paper'; -import ButtonBase from '@mui/material/ButtonBase/ButtonBase'; - -import metamaskLogo from './metamask.svg'; - -import { clsNames } from '../../core/helper'; -import styles from '../WidgetUI/WidgetUI.scss'; - - -export function Connector(props) { - return ( -
-

- Connect a wallet -

- - -

Metamask

- logo -
-
- {/* -
- - -

WalletConnect

- logo -
-
-
-
*/} -
- ) -} \ No newline at end of file diff --git a/src/components/WalletConnector/MetamaskConnector.ts b/src/components/WalletConnector/MetamaskConnector.ts deleted file mode 100644 index 67eba54..0000000 --- a/src/components/WalletConnector/MetamaskConnector.ts +++ /dev/null @@ -1,95 +0,0 @@ -import Web3 from 'web3'; - -export const CHAIN_IDS = { - 'staging': '0x4', - 'staging3': '0x5', - 'legacy': '0x5', - 'qatestnet': '0x4', - 'mainnet': '0x1' -} - - -export async function changeMetamaskNetwork(networkParams) { - try { - await window.ethereum.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: networkParams.chainId }], - }); - } catch (switchError) { - // This error code indicates that the chain has not been added to MetaMask. - if (switchError.code === 4902) { - try { - await window.ethereum.request({ - method: 'wallet_addEthereumChain', - params: [networkParams], - }); - return [0, new Web3(window.ethereum)]; - } catch (addError) { - return [1, addError]; - } - } - return [1, switchError]; - } - return [0, new Web3(window.ethereum)]; -} - - -export const connect = (connectFallback) => { - window.ethereum - .request({ method: 'eth_requestAccounts' }) - .then(connectFallback) - .catch((err) => { - if (err.code === 4001) { - // EIP-1193 userRejectedRequest error - // If this happens, the user rejected the connection request. - // console.log('Please connect to MetaMask.'); - } else { - // console.error(err); - } - }); -} - - -export const addAccountChangedListener = (accountsChangedFallback) => { - window.ethereum.on('accountsChanged', accountsChangedFallback); // todo: do only once!!!! - window.ethereum - .request({ method: 'eth_accounts' }) - .then(accountsChangedFallback) - .catch((_) => { - // Some unexpected error. - // For backwards compatibility reasons, if no accounts are available, - // eth_accounts will return an empty array. - // console.error(err); - }); -} - - -export const addChainChangedListener = (chainChangedFallback) => { - window.ethereum.on('chainChanged', chainChangedFallback); -} - - -export function schainNetworkParams( - chainName: string, - schainChainUrl: string, - schainChainId: string -) { - return { - chainId: schainChainId, - chainName: "[S] " + chainName, - rpcUrls: [schainChainUrl], - nativeCurrency: { - name: "sFUEL", - symbol: "sFUEL", - decimals: 18 - } - }; -} - - -export function mainnetNetworkParams(network: string, mainnetEndpoint: string) { - return { - chainId: CHAIN_IDS[network], - rpcUrls: [mainnetEndpoint], - }; -} diff --git a/src/components/WalletConnector/index.ts b/src/components/WalletConnector/index.ts deleted file mode 100644 index 35ce818..0000000 --- a/src/components/WalletConnector/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./MetamaskConnector"; -export * from "./Connector"; diff --git a/src/components/WalletConnector/metamask.svg b/src/components/WalletConnector/metamask.svg deleted file mode 100644 index 6cb41ba..0000000 --- a/src/components/WalletConnector/metamask.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/WalletConnector/walletconnect.svg b/src/components/WalletConnector/walletconnect.svg deleted file mode 100644 index d90457a..0000000 --- a/src/components/WalletConnector/walletconnect.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/Widget/Widget.mdx b/src/components/Widget/Widget.mdx index fd677d5..bdf93cc 100644 --- a/src/components/Widget/Widget.mdx +++ b/src/components/Widget/Widget.mdx @@ -9,7 +9,6 @@ import Button from "@mui/material/Button"; import { internalEvents } from "../../core/events"; import * as WidgetStories from "./Widget.stories"; -import { TransferRequestEditor } from "../WidgetUI/StorybookHelper"; # Functional Metaport Demo @@ -21,8 +20,5 @@ import { TransferRequestEditor } from "../WidgetUI/StorybookHelper"; -## Transfer request mode - You can try functional Metaport demo in a Transfer request mode here. - {TransferRequestEditor()} diff --git a/src/components/Widget/Widget.stories.tsx b/src/components/Widget/Widget.stories.tsx index 4b15ec7..c052ee8 100644 --- a/src/components/Widget/Widget.stories.tsx +++ b/src/components/Widget/Widget.stories.tsx @@ -1,16 +1,15 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { Widget } from "./Widget"; -import { storyDecorator } from "../WidgetUI/StorybookHelper"; +import Widget from "./Widget"; +// import { storyDecorator } from "../WidgetUI/StorybookHelper"; - -const METAPORT_CONFIG = require('../../configs/metaportConfigStaging.json'); +const METAPORT_CONFIG = require('../../metadata/metaportConfigStaging.json'); METAPORT_CONFIG.mainnetEndpoint = process.env.STORYBOOK_MAINNET_ENDPOINT; const meta: Meta = { title: "Functional/Widget", component: Widget, - decorators: [storyDecorator], + // decorators: [storyDecorator], }; export default meta; diff --git a/src/components/Widget/Widget.tsx b/src/components/Widget/Widget.tsx index d865d0a..e2df2f7 100644 --- a/src/components/Widget/Widget.tsx +++ b/src/components/Widget/Widget.tsx @@ -1,908 +1,144 @@ -import React, { useEffect } from 'react'; -import debug from 'debug'; -import WidgetUI from '../WidgetUI'; -import { getWidgetTheme } from '../WidgetUI/Themes'; -import { WrongNetworkMessage, TransactionErrorMessage } from '../ErrorMessage'; +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file Widget.ts + * @copyright SKALE Labs 2023-Present + */ + +import { useEffect } from 'react'; import { - initSChain, - initSChainMetamask, - initMainnet, - initMainnetMetamask, - updateWeb3SChain, - updateWeb3Mainnet, - updateWeb3SChainMetamask, - updateWeb3MainnetMetamask, - getChainId -} from '../../core'; + RainbowKitProvider, + darkTheme, + lightTheme +} from '@rainbow-me/rainbowkit'; +import { configureChains, createConfig, WagmiConfig } from 'wagmi'; +import { mainnet, goerli } from 'wagmi/chains'; +import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'; +import { connectorsForWallets } from '@rainbow-me/rainbowkit'; -import { getCommunityPoolData, getEmptyCommunityPoolData } from '../../core/community_pool'; -import { getAvailableTokens, getTokenBalances } from '../../core/tokens/index'; -import { getWrappedTokens } from '../../core/tokens/erc20'; -import { getEmptyTokenDataMap, getDefaultToken } from '../../core/tokens/helper'; -import { MainnetChain, SChain } from '@skalenetwork/ima-js'; -import { connect, addAccountChangedListener, addChainChangedListener } from '../WalletConnector' -import { externalEvents } from '../../core/events'; import { - MAINNET_CHAIN_NAME, - DEFAULT_ERROR_MSG, - DEFAULT_ERC20_DECIMALS, - COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, - BALANCE_UPDATE_INTERVAL_SECONDS -} from '../../core/constants'; -import { getActionName, getActionSteps } from '../../core/actions'; -import { ActionType } from '../../core/actions/action'; + injectedWallet, + coinbaseWallet, + metaMaskWallet +} from '@rainbow-me/rainbowkit/wallets'; -import { isTransferRequestActive, delay } from '../../core/helper'; -import { getTransferSteps } from '../../core/transfer_steps'; +import { MetaportConfig } from "core/interfaces" -import * as interfaces from '../../core/interfaces/index'; -import { TransactionHistory, CommunityPoolData, MetaportTheme } from '../../core/interfaces'; -import TokenData from '../../core/dataclasses/TokenData'; -import { TransferRequestStatus } from '../../core/dataclasses/TransferRequestStatus'; -import { View } from '../../core/dataclasses/View'; -import { TokenType } from '../../core/dataclasses'; +import WidgetUI from '../WidgetUI' +import { useUIStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' +import { getWidgetTheme } from '../../core/themes'; +import MetaportCore from '../../core/metaport' -import { toWei } from '../../core/convertation'; +import '@rainbow-me/rainbowkit/styles.css'; +import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network'; -debug.enable('*'); -const log = debug('metaport:WidgetV2'); - -export function Widget(props) { - - // STATE - - const [walletConnected, setWalletConnected] = React.useState(undefined); - - const [availableTokens, setAvailableTokens] = React.useState( - getEmptyTokenDataMap() - ); - const [wrappedTokens, setWrappedTokens] = React.useState( - getEmptyTokenDataMap() - ); - - const [chainId, setChainId] = React.useState(undefined); - const [extChainId, setExtChainId] = React.useState(undefined); - - const [amountLocked, setAmountLocked] = React.useState(false); - - const [address, setAddress] = React.useState(undefined); - - const [amount, setAmount] = React.useState(''); - const [tokenId, setTokenId] = React.useState(); - - const [sFuelData1, setSFuelData1] = React.useState(undefined); - const [sFuelData2, setSFuelData2] = React.useState(undefined); - - const [chainName1, setChainName1] = React.useState(undefined); - const [chainName2, setChainName2] = React.useState(undefined); - - const [sChain1, setSChain1] = React.useState(undefined); - const [sChain2, setSChain2] = React.useState(undefined); - const [mainnet, setMainnet] = React.useState(undefined); - - const [token, setToken] = React.useState(undefined); - - const [activeStep, setActiveStep] = React.useState(0); - const [actionName, setActionName] = React.useState(undefined); - const [actionSteps, setActionSteps] = React.useState(undefined); - - const [loading, setLoading] = React.useState(false); - const [loadingTokens, setLoadingTokens] = React.useState(false); - const [actionBtnDisabled, setActionBtnDisabled] = React.useState(false); - - const [open, setOpen] = React.useState(props.config.openOnLoad); - const [firstOpen, setFirstOpen] = React.useState(props.config.openOnLoad); - - const [sFuelOk, setSFuelOk] = React.useState(false); - - const [view, setView] = React.useState(View.SANDBOX); - - const [theme, setTheme] = React.useState(getWidgetTheme(props.config.theme)); - - const [transferRequest, setTransferRequest] = React.useState( - undefined); - const [transferRequestStatus, setTransferRequestStatus] = React.useState( - TransferRequestStatus.NO_REQEST); - const [transferRequestStep, setTransferRequestStep] = React.useState(0); - const [transferRequestSteps, setTransferRequestSteps] = React.useState>(); - const [transferRequestLoading, setTransferRequestLoading] = React.useState(false); - - const [errorMessage, setErrorMessage] = React.useState(undefined); - const [amountErrorMessage, setAmountErrorMessage] = React.useState(undefined); - - const [transactionsHistory, setTransactionsHistory] = React.useState([]); - - const [btnText, setBtnText] = React.useState(); - - const [communityPoolData, setCommunityPoolData] = React.useState( - getEmptyCommunityPoolData()); - const [rechargeAmount, setRechargeAmount] = React.useState(''); - const [loadingCommunityPool, setLoadingCommunityPool] = React.useState(false); - const [updateCommunityDataFlag, setUpdateCommunityDataFlag] = React.useState(false); - - // EFFECTS - - useEffect(() => { - setWalletConnected(false); - addAccountChangedListener(accountsChangedFallback); - addChainChangedListener(chainChangedFallback); - addinternalEventsListeners(); - updateTransactionCompletedEventListener(); - const interval = setInterval( - () => { setUpdateCommunityDataFlag((updateCommunityDataFlag) => !updateCommunityDataFlag) }, - BALANCE_UPDATE_INTERVAL_SECONDS * 1000 - ); - return () => { - window.removeEventListener('metaport_transactionCompleted', transactionCompleted, false); - clearInterval(interval); - }; - }, []); - - useEffect(() => { - if (open && !firstOpen) setFirstOpen(true); - }, [open]); - - useEffect(() => { - if (!open && !firstOpen) return; - if (chainName1 !== MAINNET_CHAIN_NAME && chainName2 !== MAINNET_CHAIN_NAME) setMainnet(null); - if (chainName1) setChainId(getChainId(props.config.skaleNetwork, chainName1)); - if (address && chainName1) { - if (chainName1 == MAINNET_CHAIN_NAME) { - initMainnet1(); - } else { - initSchain1(); - } - } - }, [chainName1, address]); - - useEffect(() => { - if (chainName1 !== MAINNET_CHAIN_NAME && chainName2 !== MAINNET_CHAIN_NAME) setMainnet(null); - if (address && chainName2) { - if (chainName2 == MAINNET_CHAIN_NAME) { - setMainnet(initMainnet(props.config.skaleNetwork, props.config.mainnetEndpoint)) - } else { - log(`Running initSchain2: ${chainName2}`); - setSChain2(initSChain( - props.config.skaleNetwork, - chainName2 - )); - } - log(`chain2 changed ${chainName2}`); - } - }, [chainName2, address]); - - useEffect(() => { - if (props.config.tokens) checkWrappedTokens(); - if (((sChain1 && sChain2) || (sChain1 && mainnet) || (mainnet && sChain2))) { - externalEvents.connected(); - setToken(undefined); - setLoading(false); - setActiveStep(0); - updateCommunityPoolData(); - tokenLookup(); - } - }, [sChain1, sChain2, mainnet]); - - useEffect(() => { - if (!loadingCommunityPool) { - updateCommunityPoolData(); - } - }, [updateCommunityDataFlag]); - - useEffect(() => { - if (communityPoolData.recommendedRechargeAmount) { - setRechargeAmount(communityPoolData.recommendedRechargeAmount); - } - }, [communityPoolData]); - - async function updateCommunityPoolData() { - const cpData = await getCommunityPoolData( - address, - chainName1, - chainName2, - mainnet, - sChain1 - ); - setCommunityPoolData(cpData); - return cpData; - } - - useEffect(() => { - log(`view changed: ${view}`); - - setToken(undefined); - setAmountLocked(false); - setActiveStep(0); - setActionSteps(undefined); - setActionName(undefined); - setTransferRequestStep(0); - - if (view !== null) return; - if (transferRequest) { - if (transferRequestStatus === TransferRequestStatus.RECEIVED) { - setView(View.TRANSFER_REQUEST_SUMMARY); - } else { - setView(View.TRANSFER_REQUEST_STEPS); - } - } else { - setView(View.SANDBOX); - } - }, [view]); - - useEffect(() => { - if (isTransferRequestActive(transferRequestStatus)) { - const tokenKeyname = (transferRequest.route && transferRequestStatus === TransferRequestStatus.IN_PROGRESS_HUB) ? transferRequest.route.tokenKeyname : transferRequest.tokenKeyname; - const tokenType = (transferRequest.route && transferRequestStatus === TransferRequestStatus.IN_PROGRESS_HUB) ? transferRequest.route.tokenType : transferRequest.tokenType; - log(`Loading transfer request - ${tokenKeyname}, ${transferRequest.amount}, ${transferRequest.tokenId}`); - const tokenData = availableTokens[tokenType][tokenKeyname]; - if (!tokenData) { - setTransferRequestStatus(TransferRequestStatus.ERROR); - log(`No token data: ${tokenKeyname}`); - log(availableTokens); - return - } - setToken(tokenData); - setAmount(transferRequest.amount); - setTokenId(transferRequest.tokenId); - // setTransferRequestStatus(TransferRequestStatus.IN_PROGRESS); - setTransferRequestLoading(false); - log(`Loading transfer request - DONE`); - } - }, [availableTokens]); - - useEffect(() => { - setDefaultWrappedToken(); - }, [wrappedTokens]); - - useEffect(() => { - setAmountErrorMessage(undefined); - setActiveStep(0); - setActionSteps(undefined); - setActionName(undefined); - if (token === undefined) return; - let actionName = getActionName(chainName1, chainName2, token.type, view); - setActionName(actionName); - }, [chainName1, chainName2, token, availableTokens, view]); - - useEffect(() => { - if (!actionName || !token) return; - setActionSteps(getActionSteps(actionName, token)); - if (actionName === 'erc20_unwrap') { // TODO: tmp fix to unwrap - log('Setting max amount for unwrap: ' + token.balance); - setAmount(token.balance); - } - }, [actionName, token]); - - useEffect(() => { - setAmountErrorMessage(undefined); - runPreAction(); - }, [actionSteps, activeStep, amount, tokenId]); - - useEffect(() => { - if (transferRequestStatus === TransferRequestStatus.DONE) { - log('Transfer request completed'); - externalEvents.transferRequestCompleted(transferRequest); - setTransferRequestStep(0); - } - }, [transferRequestStatus]); - - useEffect(() => { - if (token && transferRequest) { - if (transferRequestStatus === TransferRequestStatus.IN_PROGRESS) { - setTransferRequestSteps(getTransferSteps(transferRequest, props.config, theme, token)); - } - } - }, [token, transferRequest]); - - useEffect(() => { - // TODO: tmp fix for unwrap - const isUnwrapActionSteps = activeStep === 1 || activeStep === 2; - const isUwrapAction = token && token.unwrappedSymbol && token.clone && isUnwrapActionSteps; - const isUnlockAction = actionName === 'eth_s2m' && isUnwrapActionSteps; - if (extChainId && chainId && extChainId !== chainId && !isUwrapAction && !isUnlockAction - && !transferRequestLoading) { - log('_MP_INFO: setting WrongNetworkMessage'); - setTransferRequestLoading(true); - setErrorMessage(new WrongNetworkMessage(enforceMetamaskNetwork)); - } else { - setErrorMessage(undefined); - } - }, [extChainId, chainId, token, activeStep, transferRequest, view]); - - - useEffect(() => { - updateTransactionCompletedEventListener() - }, [transactionsHistory]); - - // FALLBACKS & HANDLERS - - function transactionCompleted(e: any) { - transactionsHistory.push(e.detail); // todo: fix - setTransactionsHistory([...transactionsHistory]); - } - - function updateTransactionCompletedEventListener() { - window.removeEventListener("metaport_transactionCompleted", transactionCompleted, false); - window.addEventListener("metaport_transactionCompleted", transactionCompleted, false); - } - - function clearTransactionsHistory() { - updateTransactionCompletedEventListener(); - setTransactionsHistory([]); - } - - function addinternalEventsListeners() { - window.addEventListener("_metaport_transfer", transferHandler, false); - window.addEventListener("_metaport_close", closeHandler, false); - window.addEventListener("_metaport_open", openHandler, false); - window.addEventListener("_metaport_reset", resetHandler, false); - window.addEventListener("_metaport_setTheme", themeHandler, false); - } - - function chainChangedFallback(_extChainId: string): void { - setExtChainId(_extChainId); - } - - function accountsChangedFallback(accounts) { - if (accounts.length === 0) { - // MetaMask is locked or the user has not connected any accounts - console.log('Please connect to MetaMask!'); - } else { - setAddress(accounts[0]); - setWalletConnected(true); - } - } - - function networkConnectFallback(accounts) { - if (accounts.length === 0) { - // MetaMask is locked or the user has not connected any accounts - console.log('Please connect to MetaMask!'); - } - // todo: handle accounts in Metamask module - - setAddress(accounts[0]); - setWalletConnected(true); - externalEvents.connected(); - } - - function transferHandler(e) { - resetWidgetState(false); - const params: interfaces.TransferParams = e.detail.params; - - const { - tokenType, - tokenId, - amount, - } = params; - - if (tokenType === TokenType.erc20 || tokenType === TokenType.erc1155) { - if (!amount) { - log('! ERROR: amount is required for this token type'); - return; - } - if (tokenType === TokenType.erc20 && tokenId) { - log('! WARNING: tokenId will be ignored for this token type'); - params.tokenId = undefined; - } - } - - if ( - tokenType === TokenType.erc721 || - tokenType === TokenType.erc721meta || - tokenType === TokenType.erc1155 - ) { - if (!tokenId) { - log('! ERROR: tokenId is required for this token type'); - } - if ( - (tokenType === TokenType.erc721 || tokenType === TokenType.erc721meta) && - amount - ) { - log('! WARNING: amount will be ignored for this token type'); - params.amount = undefined; - } - } - - params.lockValue = true; // todo: tmp fix - setTransferRequestStatus(TransferRequestStatus.RECEIVED); - setTransferRequest(params); - setView(View.TRANSFER_REQUEST_SUMMARY); - setOpen(true); - } - - function closeHandler(_) { - setOpen(false); - log('closeWidget event processed'); - } - - function openHandler(_) { - setOpen(true); - log('openWidget event processed'); - } - - function resetHandler(_) { - resetWidgetState(false); - log('resetWidget event processed'); - } - - function themeHandler(e) { - const theme: MetaportTheme = e.detail.theme; - setTheme(getWidgetTheme(theme)); - log('setTheme event processed'); - } - - function connectMetamask() { - console.log('connectMetamask...'); - connect(networkConnectFallback); - externalEvents.connected(); - console.log('Done: connectMetamask...'); - } - - const handleNextStep = async () => { - setLoading(true); - const ActionClass: ActionType = actionSteps[activeStep]; - try { - await new ActionClass( - mainnet, - sChain1, - sChain2, - chainName1, - chainName2, - address, - amount, - tokenId, - token, - switchMetamaskChain, - setActiveStep, - activeStep, - setAmountErrorMessage, - setBtnText - ).execute(); - } catch (err) { - console.error(err); - const msg = err.message ? err.message : DEFAULT_ERROR_MSG; - setErrorMessage(new TransactionErrorMessage(msg, errorMessageClosedFallback)); - return; - } - await updateTokenBalances(); - setLoading(false); - setActiveStep((prevActiveStep) => prevActiveStep + 1); - setTransferRequestStep((prevTrReqStep) => prevTrReqStep + 1); - if (transferRequestStatus === TransferRequestStatus.IN_PROGRESS && transferRequest.route) { - // TODO: tmp fix for unwrap - if (actionSteps.length !== 2 || (actionSteps.length === 2 && transferRequestStep === 1)) { - moveToHub(); - } - } - if (transferRequestSteps && transferRequestSteps.length !== 0 && - transferRequestStep === transferRequestSteps.length - 1) { - setTransferRequestStatus(TransferRequestStatus.DONE); - } - }; - - function errorMessageClosedFallback() { - setLoading(false); - setAmountLocked(false); - setErrorMessage(undefined); - } - - // CORE FUNCTIONS - - async function enforceMetamaskNetwork() { - if (!chainName1) { - setErrorMessage(undefined); - return; - } - if (chainName1 === MAINNET_CHAIN_NAME) { - await initMainnet1(); - } else { - await initSchain1(); - } - } - - function confirmSummary() { - setLoading(true); - setView(View.TRANSFER_REQUEST_STEPS); - setTransferRequestStatus(TransferRequestStatus.IN_PROGRESS); - const fromChain = transferRequest.chains[0]; - const toChain = transferRequest.route ? transferRequest.route.hub : transferRequest.chains[1]; - log(`confirmSummary - fromChain: ${fromChain}, toChain: ${toChain}`); - setChainName1(fromChain); - setChainName2(toChain); - } - - function moveToHub() { - setTransferRequestLoading(true); - setTransferRequestStatus(TransferRequestStatus.IN_PROGRESS_HUB); - const fromChain = transferRequest.route.hub; - const toChain = transferRequest.chains[1]; - log(`moveToHub - fromChain: ${fromChain}, toChain: ${toChain}`); - setChainName1(fromChain); - setChainName2(toChain); - } - - async function initSchain1() { - log('Running initSchain1...'); - setSChain1(await initSChainMetamask( - props.config.skaleNetwork, - chainName1, - props.config.chainsMetadata - )); - } - - async function initMainnet1() { - log(`Running initSchain1: ${chainName1}`); - const mainnetMetamask = await initMainnetMetamask( - props.config.skaleNetwork, - props.config.mainnetEndpoint - ); - setMainnet(mainnetMetamask); - return mainnetMetamask; - } - - async function checkWrappedTokens() { - if (!sChain1 || !chainName1) { - log('No chainName1 or sChain1, skipping checkWrappedTokens'); - setWrappedTokens(getEmptyTokenDataMap()); - return; - } - log('_MP_INFO: Running checkWrappedTokens'); - try { - const wrappedTokens = await getWrappedTokens(sChain1, chainName1, props.config.tokens, address); - if (Object.entries(wrappedTokens).length === 0 && view !== View.SANDBOX) { - setAmount(''); - setView(View.SANDBOX); - } - setWrappedTokens(wrappedTokens); - } catch (err) { - log('_MP_ERROR: checkWrappedTokens failed!'); - log(err); - } - } - - function setDefaultWrappedToken() { - const defaultToken = getDefaultToken(wrappedTokens); - if (defaultToken && view === View.UNWRAP) { - log(`Setting defaultToken: ${defaultToken.keyname} from wrappedTokens`) - setToken(defaultToken); - } - } - - async function tokenLookup() { - setLoadingTokens(true); - try { - let tokens = await getAvailableTokens( +const { chains, webSocketPublicClient } = configureChains( + [ mainnet, - sChain1, - sChain2, - chainName1, - chainName2, - props.config.tokens, - props.autoLookup - ); - await getTokenBalances( - tokens, - chainName1, - mainnet, - sChain1, - address - ); - setAvailableTokens(tokens); - } catch (err) { - log('_MP_ERROR: tokenLookup failed'); - log(err); - } - setLoadingTokens(false); - } - - async function updateTokenBalances() { - let tokens = availableTokens; - await getTokenBalances( - tokens, - chainName1, - mainnet, - sChain1, - address - ); - setAvailableTokens(tokens); - checkWrappedTokens(); - } - - function resetWidgetState(keepTransferRequest: boolean) { - setAvailableTokens(getEmptyTokenDataMap()); - setWrappedTokens(getEmptyTokenDataMap()); - setToken(undefined); - - setAmount(''); - setTokenId(0); - - setChainName1(null); - setChainName2(null); - - setTransferRequestStep(0); - - setSChain1(null); - setSChain2(null); - setMainnet(null); - - setLoading(false); - - setView(View.SANDBOX); - - setSFuelData1({}); - setSFuelData2({}); - - setAmountLocked(false); - setActiveStep(0); - setActionSteps(undefined); - setActionName(undefined); - setAmountErrorMessage(undefined); - setActionBtnDisabled(false); - setTransferRequestLoading(false); - setTransferRequestStatus(TransferRequestStatus.NO_REQEST); - - setCommunityPoolData(getEmptyCommunityPoolData()); - - if (transferRequest && keepTransferRequest) { - setTransferRequestStatus(TransferRequestStatus.RECEIVED); - setView(View.TRANSFER_REQUEST_SUMMARY); - } - if (!keepTransferRequest) { - setTransferRequest(undefined); - } - } - - async function runPreAction() { - if (actionSteps && actionSteps[activeStep] && token) { - log('Running preAction'); - setActionBtnDisabled(true); - const ActionClass: ActionType = actionSteps[activeStep]; - try { - await new ActionClass( - mainnet, - sChain1, - sChain2, - chainName1, - chainName2, - address, - amount, - tokenId, - token, - switchMetamaskChain, - setActiveStep, - activeStep, - setAmountErrorMessage, - setBtnText - ).preAction(); - } catch (e) { - console.error(e); - } finally { - setActionBtnDisabled(false); - log('preAction done'); - } - } - } - - async function switchMetamaskChain(switchBack?: boolean): Promise { - // if (chainName1 === MAINNET_CHAIN_NAME) { - // updateWeb3SChain( - // switchBack ? sChain2 : sChain1, - // props.network, - // switchBack ? chainName2 : chainName1 - // ); - // await updateWeb3SChainMetamask( - // switchBack ? sChain1 : sChain2, - // props.network, - // switchBack ? chainName1 : chainName2 - // ); - // } - if (chainName2 === MAINNET_CHAIN_NAME) { - if (switchBack) { - await updateWeb3SChainMetamask( - sChain1, - props.config.skaleNetwork, - chainName1, - props.config.chainsMetadata - ); - updateWeb3Mainnet(mainnet, props.config.mainnetEndpoint); - } else { - updateWeb3SChain( - sChain1, - props.config.skaleNetwork, - chainName1 - ) - await updateWeb3MainnetMetamask( - mainnet, - props.config.skaleNetwork, - props.config.mainnetEndpoint - ) - } - return; - } - updateWeb3SChain( - switchBack ? sChain2 : sChain1, - props.config.skaleNetwork, - switchBack ? chainName2 : chainName1 - ); - await updateWeb3SChainMetamask( - switchBack ? sChain1 : sChain2, - props.config.skaleNetwork, - switchBack ? chainName1 : chainName2, - props.config.chainsMetadata - ); - } - - function cleanData() { - setAmountErrorMessage(undefined); - setActionBtnDisabled(false); - setTokenId(undefined); - setAmount(''); - setLoading(false); - setActiveStep(0); - setTransferRequestLoading(false); - setTransferRequestStatus(TransferRequestStatus.NO_REQEST); - } - - async function rechargeCommunityPool() { - // todo: optimize - setLoadingCommunityPool('recharge'); - try { - log('Recharging community pool...') - const sChain = initSChain( - props.config.skaleNetwork, - chainName1 - ); - const mainnetMetamask = await initMainnet1(); - setChainId(getChainId(props.config.skaleNetwork, MAINNET_CHAIN_NAME)); - await mainnetMetamask.communityPool.recharge(chainName1, address, { - address: address, - value: toWei(rechargeAmount, DEFAULT_ERC20_DECIMALS) - }); - setLoadingCommunityPool('activate'); - let active = false; - const chainHash = mainnet.web3.utils.soliditySha3(chainName1); - let counter = 0; - while (!active) { - log('Waiting for account activation...'); - let activeM = await mainnet.communityPool.contract.methods.activeUsers( - address, - chainHash - ).call(); - let activeS = await sChain.communityLocker.contract.methods.activeUsers( - address - ).call(); - active = activeS && activeM; - await delay(BALANCE_UPDATE_INTERVAL_SECONDS * 1000); - counter++; - if (counter >= 10) break; - } - } catch (err) { - console.error(err); - const msg = err.message ? err.message : DEFAULT_ERROR_MSG; - setErrorMessage(new TransactionErrorMessage(msg, errorMessageClosedFallback)); - } finally { - await initSchain1(); - setChainId(getChainId(props.config.skaleNetwork, chainName1)); - setMainnet(initMainnet(props.config.skaleNetwork, props.config.mainnetEndpoint)); - await updateCommunityPoolData(); - setLoadingCommunityPool(false); - } - } - - async function withdrawCommunityPool() { - // todo: optimize - setLoadingCommunityPool('withdraw'); - try { - log('Withdrawing community pool...') - setSChain1(null); - const mainnetMetamask = await initMainnet1(); - setChainId(getChainId(props.config.skaleNetwork, MAINNET_CHAIN_NAME)); - await mainnetMetamask.communityPool.withdraw(chainName1, communityPoolData.balance, { - address: address, - customGasLimit: COMMUNITY_POOL_WITHDRAW_GAS_LIMIT - }); - } catch (err) { - console.error(err); - const msg = err.message ? err.message : DEFAULT_ERROR_MSG; - setErrorMessage(new TransactionErrorMessage(msg, errorMessageClosedFallback)); - } finally { - await initSchain1(); - setChainId(getChainId(props.config.skaleNetwork, chainName1)); - setMainnet(initMainnet(props.config.skaleNetwork, props.config.mainnetEndpoint)); - const cpData = await updateCommunityPoolData(); - setRechargeAmount(cpData.recommendedRechargeAmount); - setLoadingCommunityPool(false); - } - } - - return () + goerli, + + constructWagmiChain('staging', "staging-legal-crazy-castor"), + constructWagmiChain('staging', "staging-utter-unripe-menkar"), + constructWagmiChain('staging', "staging-faint-slimy-achird"), + constructWagmiChain('staging', "staging-perfect-parallel-gacrux"), + constructWagmiChain('staging', "staging-severe-violet-wezen"), + constructWagmiChain('staging', "staging-weepy-fitting-caph"), + + constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), + constructWagmiChain('mainnet', 'elated-tan-skat'), + constructWagmiChain('mainnet', 'affectionate-immediate-pollux') + ], + [ + jsonRpcProvider({ + rpc: chain => ({ + http: chain.rpcUrls.default.http[0], + webSocket: getWebSocketUrl(chain) + }), + }) + ] +); + + +const connectors = connectorsForWallets([ + { + groupName: 'Supported Wallets', + wallets: [ + metaMaskWallet({ chains, projectId: '' }), + injectedWallet({ chains }), + coinbaseWallet({ chains, appName: 'TEST' }) + ], + } +]); + + +const wagmiConfig = createConfig({ + autoConnect: true, + connectors, + publicClient: webSocketPublicClient +}); + + + + +export default function Widget(props: { + config: MetaportConfig +}) { + const widgetTheme = getWidgetTheme(props.config.theme); + const theme = widgetTheme.mode === 'dark' ? darkTheme() : lightTheme(); + + const setTheme = useUIStore((state) => state.setTheme); + const setMpc = useMetaportStore((state) => state.setMpc); + const setOpen = useUIStore((state) => state.setOpen); + + theme.colors.connectButtonInnerBackground = widgetTheme.background; + theme.colors.connectButtonBackground = widgetTheme.background; + + useEffect(() => { + setOpen(props.config.openOnLoad); + }, []); + + useEffect(() => { + setTheme(widgetTheme); + }, [setTheme]); + + useEffect(() => { + setMpc(new MetaportCore(props.config)); + }, [setMpc]); + + return ( + + + + + + ) } \ No newline at end of file diff --git a/src/components/Widget/index.ts b/src/components/Widget/index.ts index 1cb0e8c..820393d 100644 --- a/src/components/Widget/index.ts +++ b/src/components/Widget/index.ts @@ -1 +1 @@ -export { Widget } from "./Widget"; +export { default } from "./Widget"; diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index e9e2fcd..249eb00 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -1,164 +1,95 @@ -import React from 'react'; - -import Collapse from '@mui/material/Collapse'; - -import { View } from '../../core/dataclasses/View'; -import { isTransferRequestView } from '../../core/views'; - -import CurrentChain from '../CurrentChain'; -import ErrorMessage from '../ErrorMessage'; -import UnwrapUI from '../UnwrapUI'; -import TransferUI from '../TransferUI'; -import WrappedTokensWarning from '../WrappedTokensWarning'; -import TransferRequest from '../TransferRequest'; -import SFuelWarning from '../SFuelWarning'; -import TransactionsHistory from '../TransactionsHistory'; - - -export default function WidgetBody(props) { - - const [expandedFrom, setExpandedFrom] = React.useState(false); - const [expandedTo, setExpandedTo] = React.useState(false); - const [expandedTokens, setExpandedTokens] = React.useState(false); - const [expandedHistory, setExpandedHistory] = React.useState(false); - const [expandedExit, setExpandedExit] = React.useState(false); - - // TODO: tmp wrap tokens fix - const wrapTransferAction = props.actionSteps && props.actionSteps.length === 2 && props.activeStep > 0; - - if (props.errorMessage) { - return () - } - - if (isTransferRequestView(props.view)) { - return - } - - if (props.view === View.UNWRAP) { - return
- - - - -
- } +import { useCollapseStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' + +import TokenList from '../TokenList'; +import ChainsList from '../ChainsList'; +import AmountInput from '../AmountInput'; +import SkStepper from '../Stepper'; +import SkPaper from '../SkPaper'; +import AmountErrorMessage from '../AmountErrorMessage'; +import SwitchDirection from '../SwitchDirection'; + +import common from '../../styles/common.scss'; +import { cls } from '../../core/helper'; +import { Collapse } from '@mui/material'; + + +export function WidgetBody(props) { + + const expandedFrom = useCollapseStore((state) => state.expandedFrom); + const setExpandedFrom = useCollapseStore((state) => state.setExpandedFrom); + + const expandedTo = useCollapseStore((state) => state.expandedTo); + const setExpandedTo = useCollapseStore((state) => state.setExpandedTo); + + const token = useMetaportStore((state) => state.token); + const chainName1 = useMetaportStore((state) => state.chainName1); + const chainName2 = useMetaportStore((state) => state.chainName2); + + const setChainName1 = useMetaportStore((state) => state.setChainName1); + const setChainName2 = useMetaportStore((state) => state.setChainName2); + + const transferInProgress = useMetaportStore((state) => state.transferInProgress); return (
- - + + - - - - + +
+ +
+
+
+ + + + + -
- - + + + - -
- ) -} \ No newline at end of file + + {/*
+
+ +
+
+ {token ? : null} +
+
*/} + + + + +
+ + ); +} + + +export default WidgetBody; diff --git a/src/components/WidgetUI/Common.stories.tsx b/src/components/WidgetUI/Common.stories.tsx deleted file mode 100644 index da95c95..0000000 --- a/src/components/WidgetUI/Common.stories.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from "react"; -import { WidgetUI } from "./WidgetUI"; -import { commonProps, defaultTokenData, commonConfig } from './StoriesHelper'; -import { getEmptyTokenDataMap } from '../../core/tokens/helper'; -import { Positions } from '../../core/dataclasses/Position'; -import { getWidgetTheme } from '../WidgetUI/Themes'; - - -export default { - title: "Common elements" -}; - - -export const ConnectScreen = () => ( - -); - - -export const ConnectScreenLight = () => ( - -); - - -export const ClosedLight = () => ( - -); - - -export const TopLeft = () => ( - -); - - -export const ClosedDark = () => ( - -); - - -export const SelectChains = () => ( - -); - -export const SelectChainsLight = () => ( - -); - - -export const LoadingTokens = () => ( - -); - - -export const NoButton = () => ( - -); \ No newline at end of file diff --git a/src/components/WidgetUI/ERC1155.stories.tsx b/src/components/WidgetUI/ERC1155.stories.tsx deleted file mode 100644 index 35086fa..0000000 --- a/src/components/WidgetUI/ERC1155.stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -import { WidgetUI } from "./WidgetUI"; -import { commonProps, defaultERC1155TokenData } from './StoriesHelper'; - - -export default { - title: "ERC1155 UI" -}; - -export const TransferNFT = () => ( - -); - \ No newline at end of file diff --git a/src/components/WidgetUI/ERC20.stories.tsx b/src/components/WidgetUI/ERC20.stories.tsx deleted file mode 100644 index cce6fc1..0000000 --- a/src/components/WidgetUI/ERC20.stories.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import React from "react"; -import { WidgetUI } from "./WidgetUI"; -import { - commonProps, - defaultTokenData, - generateTokenData, - getWrapActionSteps, - getUnwrapActionSteps, - generateWrappedTokens -} from './StoriesHelper'; -import { getWidgetTheme } from '../WidgetUI/Themes'; - -import { View } from '../../core/dataclasses/View'; - - -export default { - title: "ERC20 UI" -}; - -export const TransferUIDark = () => ( - -); - -export const TransferUILight = () => ( - -); - - -export const MainnetTransfer = () => ( - { }} - setChain2={() => { }} - setToken={() => { }} - setActiveStep={() => { }} - - chain1='mainnet' - chain2='staging-severe-violet-wezen' - theme={getWidgetTheme(null)} - /> -); - -export const MainnetTransferLight = () => ( - { }} - setChain2={() => { }} - setToken={() => { }} - setActiveStep={() => { }} - - chain1='mainnet' - chain2='staging-severe-violet-wezen' - theme={getWidgetTheme({ mode: 'light' })} - /> -); - -export const LoadingSteps = () => ( - { }} - setChain2={() => { }} - setToken={() => { }} - setActiveStep={() => { }} - - loading={true} - - chain1='staging-perfect-parallel-gacrux' - chain2='staging-severe-violet-wezen' - theme={getWidgetTheme(null)} - /> -); - - -export const SelectToken = () => ( - -); - - -export const Approved = () => ( - -); - - -export const Loading = () => ( - -); - - -export const LoadingCustom = () => ( - -); - - -export const LoadingLight = () => ( - -); - - -export const TransferComplete = () => ( - -); - - -export const WrapUI = () => ( - -); - - -export const UnwrapWarning = () => ( - -); - - -export const UnwrapWarningLight = () => ( - -); - - -export const UnwrapUINoTokens = () => ( - -); - - -export const UnwrapUILoading = () => ( - -); - -export const UnwrapUI = () => ( - -); \ No newline at end of file diff --git a/src/components/WidgetUI/ERC721.stories.tsx b/src/components/WidgetUI/ERC721.stories.tsx deleted file mode 100644 index 62310be..0000000 --- a/src/components/WidgetUI/ERC721.stories.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; -import { WidgetUI } from "./WidgetUI"; -import { commonProps, defaultERC721TokenData, generateTokenData } from './StoriesHelper'; - -import PublicOffIcon from '@mui/icons-material/PublicOff'; - - -export default { - title: "ERC721 UI" -}; - -export const TransferNFT = () => ( - -); diff --git a/src/components/WidgetUI/Errors.stories.tsx b/src/components/WidgetUI/Errors.stories.tsx deleted file mode 100644 index 2697846..0000000 --- a/src/components/WidgetUI/Errors.stories.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from "react"; - -import PublicOffIcon from '@mui/icons-material/PublicOff'; - -import { WidgetUI } from "./WidgetUI"; -import { commonProps, defaultTokenData, generateTokenData } from './StoriesHelper'; -import { getEmptyTokenDataMap } from '../../core/tokens/helper'; -import { getWidgetTheme } from '../WidgetUI/Themes'; - - -export default { - title: "Errors" -}; - - -export const NoTokenPairs = () => (); - - -export const NoTokenPairsLight = () => ( - -); - - -export const WrongNetwork = () => ( - , - text: 'test test test test test test test test test test', - btnText: 'aaaa', - fallback: () => { console.log('test test test') } - }} - /> -); - - -export const WrongNetworkLight = () => ( - , - text: 'test test test test test test test test test test', - btnText: 'aaaa', - fallback: () => { console.log('test test test') } - }} - /> -); - - -export const Error = () => ( - -); - diff --git a/src/components/WidgetUI/Guidelines.stories.mdx b/src/components/WidgetUI/Guidelines.stories.mdx deleted file mode 100644 index 1fe5390..0000000 --- a/src/components/WidgetUI/Guidelines.stories.mdx +++ /dev/null @@ -1,50 +0,0 @@ -import { - Meta, - ColorPalette, - ColorItem, - Title, - Subtitle, -} from "@storybook/blocks"; -import Button from "@mui/material/Button"; - -import { clsNames } from "../../core/helper"; -import styles from "../WidgetUI/WidgetUI.scss"; - - - -# SKALE Design Guidelines - -## SKALE Brand colors - - - - - - -## SKALE Components - -### Buttons - -{/*
- -
*/} diff --git a/src/components/WidgetUI/Multichain.stories.tsx b/src/components/WidgetUI/Multichain.stories.tsx deleted file mode 100644 index b0349ac..0000000 --- a/src/components/WidgetUI/Multichain.stories.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import React from "react"; -import { WidgetUI } from "./WidgetUI"; -import { - commonProps, - defaultTokenData, - generateTokenData, - getWrapActionSteps, - getUnwrapActionSteps, - generateWrappedTokens, - generateTransferRequest, - generateTransferRequestSimple, - generateTransferRequestUnwrap, - generateConfigTokens, - generateTransferRequestSteps -} from './StoriesHelper'; -import { getWidgetTheme } from '../WidgetUI/Themes'; - -import { View } from '../../core/dataclasses/View'; - - -export default { - title: "Multichain UI" -}; - - - -export const TransferSummaryLoading = () => ( - -); - -export const TransferSummary = () => ( - -); - -export const TransferSummaryLight = () => ( - -); - -export const TransferSummarySimple = () => ( - -); - -export const TransferSummaryUnwrap = () => ( - -); - -export const TransferSummaryApps = () => ( - -); - - -export const TransferRequestSteps = () => ( - -); - -export const TransferRequestStepsLight = () => ( - -); - - -export const TransferRequestStepsLoading = () => ( - -); - - - - -export const TransferRequestStepsApps = () => ( - -); \ No newline at end of file diff --git a/src/components/WidgetUI/StoriesHelper.ts b/src/components/WidgetUI/StoriesHelper.ts deleted file mode 100644 index f06462a..0000000 --- a/src/components/WidgetUI/StoriesHelper.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { getActionSteps } from '../../core/actions'; -import TokenData from '../../core/dataclasses/TokenData'; -import { TokenType } from '../../core/dataclasses/TokenType'; -import { getEmptyTokenDataMap } from '../../core/tokens/helper'; -import { getWidgetTheme } from '../WidgetUI/Themes'; -export * as dataclasses from '../../core/dataclasses/index'; -import * as interfaces from '../../core/interfaces/index'; -import { View } from '../../core/dataclasses/View'; -import { getTransferSteps } from '../../core/transfer_steps'; - -function setMock() { return }; - - -function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min) + min); -} - - -export const commonConfig: interfaces.MetaportConfig = { - skaleNetwork: 'staging3', - openButton: true, - openOnLoad: true, - tokens: generateConfigTokens(), - chains: ['Europa Chain', 'Calypso'], - chainsMetadata: { - 'staging-perfect-parallel-gacrux': { - alias: 'Europa Hub', // optional - minSfuelWei: '27000000000000', // optional - faucetUrl: 'https://github.com/skalenetwork/skale-network', - apps: { - "ruby": { - "alias": "Ruby Exchange", - "background": "#02001f", - "url": "https://ruby.exchange/" - } - } - }, - 'staging-severe-violet-wezen': { - alias: 'Calypso Hub', - apps: { - "nftrade": { - "alias": "NFTrade", - "background": "#ffffff", - "url": "https://nftrade.com/" - } - } - } - } -} - - -export const commonProps = { - sFuelOk: true, - view: View.SANDBOX, - open: true, - config: commonConfig, - chain1: 'staging-perfect-parallel-gacrux', - chain2: 'staging-severe-violet-wezen', - setChain1: setMock, - setChain2: setMock, - setToken: setMock, - setLoading: setMock, - setView: setMock, - setActiveStep: () => { return }, - walletConnected: true, - actionSteps: getActionSteps('erc20_s2s', new TokenData( - '', - null, - '', - 'test', - null, - null, - null, - null, - TokenType.erc20, - null, - null, - null - )), - theme: getWidgetTheme(null), - transferRequestLoading: true -} - - - -export function getWrapActionSteps() { - return getActionSteps('erc20_s2s', new TokenData( - '', - null, - '', - 'test', - null, - null, - null, - null, - TokenType.erc20, - 'ETHC', - '0x0', - null - )) -} - - -export function getUnwrapActionSteps() { - return getActionSteps('erc20_unwrap', new TokenData( - '', - null, - '', - 'test', - null, - null, - null, - null, - TokenType.erc20, - 'ETHC', - '0x0', - null - )) -} - - -export function generateTokenData(tokenSymbol, tokenName, wrapped = false) { - const data = { - token: tokenSymbol, - amount: getRandomInt(1000, 10000), - availableTokens: getEmptyTokenDataMap(), - wrappedTokens: getEmptyTokenDataMap(), - wrappedToken: undefined - } - // tslint:disable-next-line - const unwrappedIconUrl = wrapped ? "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Globe%20showing%20americas/3D/globe_showing_americas_3d.png" : null - data.availableTokens.erc20[tokenSymbol] = new TokenData( - '0x0', - '0x0', - tokenName, - tokenSymbol, - undefined, - false, - undefined, - '18', - TokenType.erc20, - undefined, - undefined, - unwrappedIconUrl - ); - if (wrapped) { - data.availableTokens.erc20[tokenSymbol].unwrappedBalance = getRandomInt( - 10000, 70000).toString(); - data.availableTokens.erc20[tokenSymbol].unwrappedSymbol = 'u' + tokenSymbol; - }; - data.availableTokens.erc20[tokenSymbol].balance = getRandomInt(10000, 70000).toString(); - data.token = data.availableTokens.erc20[tokenSymbol]; - return data; -} - -export const defaultTokenData = generateTokenData('usdt', 'Tether'); - - -export function generateERC721TokenData(tokenSymbol, tokenName) { - const data = { - token: tokenSymbol, - amount: getRandomInt(1000, 10000), - availableTokens: getEmptyTokenDataMap() - } - data.availableTokens.erc721[tokenSymbol] = new TokenData( - '0x0', - '0x0', - tokenName, - tokenSymbol, - tokenSymbol, - false, - undefined, - undefined, - TokenType.erc721, - undefined, - undefined, - undefined - ); - data.token = data.availableTokens.erc721[tokenSymbol]; - return data; -} - -export const defaultERC721TokenData = generateERC721TokenData('skl', 'SKALE Space'); - - -export function generateERC1155TokenData(tokenSymbol, tokenName) { - const data = { - token: tokenSymbol, - amount: getRandomInt(1000, 10000), - availableTokens: getEmptyTokenDataMap() - } - data.availableTokens.erc1155[tokenSymbol] = new TokenData( - '0x0', - '0x0', - tokenName, - tokenSymbol, - undefined, - false, - undefined, - '18', - TokenType.erc1155, - undefined, - undefined, - undefined - ); - // data.availableTokens.erc1155[tokenSymbol].balance = getRandomInt(10000, 70000).toString(); - data.token = data.availableTokens.erc1155[tokenSymbol]; - return data; -} - -export const defaultERC1155TokenData = generateERC1155TokenData('XEM', 'SKALIENS'); - - - -export function generateWrappedTokens() { - const data = generateTokenData('eth', 'ETH'); - data.wrappedTokens.erc20 = data.availableTokens.erc20; - data.token.balance = undefined; - return data; -} - -export function generateTransferRequest(apps?: boolean) { - const trReq = { - toApp: undefined, - amount: getRandomInt(100, 1000).toString(), - chains: ['mainnet', 'staging-severe-violet-wezen'], - tokenKeyname: 'eth', - tokenType: TokenType.eth, - lockValue: true, - route: { - hub: 'staging-perfect-parallel-gacrux', - tokenKeyname: '_wrETH_0xBA3f8192e28224790978794102C0D7aaa65B7d70' - }, - text: 'Your assets will be routed though Europa Hub - all transactions on Europa and\ - Calypso are free.' - }; - if (apps) { - trReq.toApp = 'nftrade'; - } - return trReq; -} - -export function generateTransferRequestUnwrap() { - return { - amount: getRandomInt(100, 1000), - chains: ['staging-severe-violet-wezen', 'mainnet'], - tokenKeyname: 'eth', - tokenType: TokenType.eth, - lockValue: true, - route: { - hub: 'staging-perfect-parallel-gacrux', - tokenKeyname: '_wrETH_0xBA3f8192e28224790978794102C0D7aaa65B7d70' - }, - text: 'Your assets will be routed though Europa Hub - all transactions on Europa and Calypso \ - are free.' - }; -} - - -export function generateTransferRequestSimple(apps?: boolean) { - const trReq = { - fromApp: undefined, - toApp: undefined, - amount: getRandomInt(100, 1000), - chains: ['staging-perfect-parallel-gacrux', 'staging-severe-violet-wezen'], - tokenKeyname: 'eth', - tokenType: TokenType.eth, - lockValue: true - }; - if (apps) { - trReq.fromApp = 'ruby'; - trReq.toApp = 'nftrade'; - } - return trReq; -} - - -export function generateConfigTokens(): interfaces.TokensMap { - return { - mainnet: { - eth: { - chains: [ - 'staging-perfect-parallel-gacrux' - ] - } - }, - 'staging-perfect-parallel-gacrux': { - 'erc20': { - "WRETH": { - "address": "0xBA3f8192e28224790978794102C0D7aaa65B7d70", - "name": "ETH", - "symbol": "ETH", - "cloneSymbol": "ETH", - "wraps": { - "address": "0xD2Aaa00700000000000000000000000000000000", - "symbol": "ETH" - } - }, - "usdc": { - "address": "0xBA3f8192e28224790978794102C0D7aaa65B7d70", - "name": "usdc", - "symbol": "usdc", - "cloneSymbol": "usdc" - } - } - } - } -} - - -export function generateTransferRequestSteps(apps?: boolean) { - return getTransferSteps( - generateTransferRequest(apps) as interfaces.TransferParams, - commonConfig as interfaces.MetaportConfig, getWidgetTheme(null), new TokenData( - '', - null, - '', - 'test', - null, - null, - null, - null, - TokenType.erc20, - 'ETHC', - '0x0', - null - )) -} \ No newline at end of file diff --git a/src/components/WidgetUI/StorybookHelper.tsx b/src/components/WidgetUI/StorybookHelper.tsx deleted file mode 100644 index 85c63fc..0000000 --- a/src/components/WidgetUI/StorybookHelper.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { useEffect, useState } from 'react'; - -import TextField from "@mui/material/TextField"; -import Button from "@mui/material/Button"; - -import { internalEvents } from "../../core/events"; - - -const btnStyles = { - fontSize: '0.7525rem', - lineHeight: '2.6', - letterSpacing: '0.02857em', - fontWeight: '600', - borderRadius: '15px' -}; - -const styles = { - transform: 'scale(1)', - height: '85vh', - backgroundColor: 'rgb(243 243 243)', - borderRadius: '15px', - boxShadow: 'rgba(0, 0, 0, 0.10) 0 1px 3px 0', - border: '1px solid hsla(203, 50%, 30%, 0.15)' -}; - - -const ERC20_TR_REQ_SAMPLE = { - "amount": "100", - "chains": ["mainnet", "staging-perfect-parallel-gacrux"], - "tokenKeyname": "_skl_0x2868716b3B4AEa43E8387922AFE71a77D101854e", - "tokenType": "erc20", - "lockValue": true, - "toApp": "ruby" -}; - - -const ERC721_TR_REQ_SAMPLE = { - "tokenId": "1", - "chains": ["mainnet", "staging-perfect-parallel-gacrux"], - "tokenKeyname": "_SPACE_0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6", - "tokenType": "erc721meta", - "lockValue": true, - "toApp": "ruby" -} - -const ERC1155_TR_REQ_SAMPLE = { - "tokenId": "1", - "amount": "5", - "chains": ["mainnet", "staging-perfect-parallel-gacrux"], - "tokenKeyname": "_SKALIENS_0x6cb73D413970ae9379560aA45c769b417Fbf33D6", - "tokenType": "erc1155", - "lockValue": true, - "toApp": "ruby" -} - -const ERC20_S2S_TR_REQ_SAMPLE = { - "amount": "10", - "chains": ["staging-perfect-parallel-gacrux", "staging-severe-violet-wezen"], - "tokenKeyname": "_SKL_0x099A46F35b627CABee27dc917eDA253fFbC55Be6", - "tokenType": "erc20", - "lockValue": true, - "fromApp": "ruby", - "toApp": "nftrade" -} - -const ETH_ROUTED_TR_REQ_SAMPLE = { - "amount": "0.01", - "chains": ["mainnet", "staging-severe-violet-wezen"], - "tokenKeyname": "eth", - "tokenType": "eth", - "lockValue": true, - "toApp": "nftrade", - "route": { - "hub": "staging-perfect-parallel-gacrux", - "tokenType": "erc20", - "tokenKeyname": "_ETH_0xBA3f8192e28224790978794102C0D7aaa65B7d70" - } -} - -const ETH_ROUTED_REVERSED_TR_REQ_SAMPLE = { - "amount": "0.01", - "chains": ["staging-severe-violet-wezen", "mainnet"], - "tokenKeyname": "_ETH_0xBA3f8192e28224790978794102C0D7aaa65B7d70", - "tokenType": "erc20", - "lockValue": true, - "toApp": "nftrade", - "route": { - "hub": "staging-perfect-parallel-gacrux", - "tokenType": "eth", - "tokenKeyname": "eth" - } -} - -const ETH_S2M_TR_REQ_SAMPLE = { - "amount": "0.01", - "chains": ["staging-perfect-parallel-gacrux", "mainnet"], - "tokenKeyname": "eth", - "tokenType": "eth", - "lockValue": true -}; - - -export const storyDecorator = storyFn =>
-
-
-
- {storyFn()} -
; - - -export const TransferRequestEditor = () => { - const [inputValue, setInputValue] = useState(JSON.stringify(ERC20_TR_REQ_SAMPLE)); - const [events, setEvents] = useState([]); - - const handleInputChange = (event: React.ChangeEvent) => { - setInputValue(event.target.value); - }; - - const handleButtonClick = () => { - try { - const parsedJSON = JSON.parse(inputValue); - internalEvents.transfer(parsedJSON); - } catch (error) { - console.error('Invalid JSON object'); - } - }; - - useEffect(() => { - window.addEventListener('metaport_actionStateUpdated', eventHandled, false); - return () => { - window.removeEventListener('metaport_actionStateUpdated', eventHandled, false); - }; - }, []); - - function eventHandled(e: any) { - events.push(e.detail); - setEvents([...events]); - } - - return ( -
- - - - - - - - - - - - - - - - - - - -
-

Events

- {events.map((event, key) => ( -
{event.actionName} - {event.actionState}
- ))} -
-
- ); -} \ No newline at end of file diff --git a/src/components/WidgetUI/Themes.stories.tsx b/src/components/WidgetUI/Themes.stories.tsx deleted file mode 100644 index 0ffc4ec..0000000 --- a/src/components/WidgetUI/Themes.stories.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import React from "react"; -import { WidgetUI } from "./WidgetUI"; -import { storyDecorator } from "./StorybookHelper"; -import { commonProps, generateTokenData } from './StoriesHelper'; -import { Positions } from '../../core/dataclasses/Position'; - - -const meta: Meta = { - title: 'Themes/Customization', - component: WidgetUI, - tags: ['autodocs'], - argTypes: { - theme: { - description: "The theme to use for the widget", - }, - "theme.primary": { - control: { - type: 'color' - } - } - }, - decorators: [storyDecorator], - -}; - -export default meta; -type Story = StoryObj; - - -export const ThemeRuby: Story = { - args: { - ...commonProps, - ...generateTokenData('ruby', 'Ruby'), - theme: { - primary: '#b01571', - background: '#f3f2ff', - mode: 'light', - position: Positions.bottomRight - } - } -}; - - -export const DarkBlue: Story = { - args: { - ...commonProps, - ...generateTokenData('zrx', '0x'), - theme: { - primary: '#00d4ff', - background: '#0a2540', - mode: 'dark', - position: Positions.bottomLeft - } - } -}; - - -export const LightOrange: Story = { - args: { - ...commonProps, - ...generateTokenData('link', 'Chainlink'), - theme: { - primary: '#f96300', - background: '#ffffff', - mode: 'light', - position: Positions.topRight - } - } -}; - - -export const LightViolet: Story = { - args: { - ...commonProps, - ...generateTokenData('skl', 'Skale'), - theme: { - primary: '#9a66ff', - background: '#fbf8ff', - mode: 'light', - position: Positions.bottomRight - } - } -}; - - -export const CustomZIndex: Story = { - args: { - ...commonProps, - ...generateTokenData('skl', 'Skale'), - theme: { - primary: '#9a66ff', - background: '#fbf8ff', - mode: 'light', - position: Positions.bottomRight, - zIndex: 9991 - } - } -}; \ No newline at end of file diff --git a/src/components/WidgetUI/WidgetUI.scss b/src/components/WidgetUI/WidgetUI.scss deleted file mode 100644 index cbf3afd..0000000 --- a/src/components/WidgetUI/WidgetUI.scss +++ /dev/null @@ -1,725 +0,0 @@ -@import './variables'; - -.imaWidgetBody { - position: fixed; -} - -.skaleLogoSm { - width: 18pt; -} - -.mp__popupWrapper, -.mp__fullsreenWrapper { - // margin: 20pt; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif !important; - -webkit-font-smoothing: antialiased; - - p { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif !important; - -webkit-font-smoothing: antialiased; - margin-top: 0; - margin-bottom: 0; - } - - .mp__p__marg { - margin-top: 1em !important; - margin-bottom: 1em !important; - } - - button { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif !important; - -webkit-font-smoothing: antialiased; - } - - :global(.MuiPaper-root) { - background-image: none; - } - - - :global(.MuiSvgIcon-fontSizeSmall) { - width: 10pt; - height: 10pt; - } -} - -.mp__popup { - padding: 14pt; - max-height: calc(100vh - 110pt); - - :global(.MuiButton-root) { - font-weight: 600 !important; - box-shadow: none !important; - border-radius: $sk-border-radius !important; - } -} - -.mp__paper { - border-radius: 20px !important; - border: 1px #7f7f7f45 solid; -} - -.mp__flex { - display: flex; - vertical-align: middle; -} - -.fl-right { - justify-content: end; -} - -.mp__flexCentered { - align-items: center; - justify-content: center; -} - -.mp__flexCenteredVert { - align-items: center; -} - -.mp__flexGrow { - flex-grow: 1; -} - -.mp_flexRow { - flex-direction: row; - flex-wrap: wrap; -} - -.mp__margTop10 { - margin-top: 10px !important; -} - -.mp__margTop20 { - margin-top: 20px !important; -} - -.mp__margTop40 { - margin-top: 40px !important; -} - - -.mp__margLeft5 { - margin-left: 5px !important; -} - -.mp__margLeft10 { - margin-left: 10px !important; -} - -.mp__margLeft20 { - margin-left: 20px !important; -} - -.mp__margRi20 { - margin-right: 20px !important; -} - -.mp__margRi10 { - margin-right: 10px !important; -} - -.mp__margRi5 { - margin-right: 5px !important; -} - -.mp__margBottMin10 { - margin-bottom: -10px !important; -} - -.mp__margBottMin15 { - margin-bottom: -15px !important; -} - -.marg-top-20 { - margin-top: 20px !important; -} - -.mp__margTop20Pt { - margin-top: 20pt !important; -} - -.marg-top-30 { - margin-top: 30px !important; -} - -.mp__margBott20 { - margin-bottom: 20px !important; -} - -.mp__margBott15 { - margin-bottom: 15px !important; -} - -.mp__margBott10 { - margin-bottom: 10px !important; -} - -.marg-bott-40 { - margin-bottom: 40px !important; -} - -.mp__margBott5 { - margin-bottom: 5px !important; -} - -.mp__margTop5 { - margin-top: 5px !important; -} - - -.mp__noMargTop { - margin-top: 0 !important; -} - - -.mp__noMargBott { - margin-bottom: 0 !important; -} - -.mp__noMarg { - margin: 0 !important; -} - -.mp__paddTop10 { - padding-top: 10px !important; -} - - -.mp__capitalize { - text-transform: capitalize; -} - -.mp__chainName { - font-weight: bold; - padding-left: 10pt; - margin-top: 1em !important; - margin-bottom: 1em !important; -} - - -.mp__chainsList { - overflow: auto; - max-height: 45vh; -} - -.mp__btnChain { - width: 100%; - text-align: left; - text-transform: uppercase; - font-size: 0.6525rem !important; - line-height: 1.6; - letter-spacing: 0.02857em; -} - -.mp__btnAction { - width: 100%; - text-transform: uppercase; - font-size: 0.7525rem !important; - line-height: 1.6 !important; - letter-spacing: 0.02857em !important; - font-weight: 600 !important; - padding-top: 0.8em !important; - padding-bottom: 0.8em !important; - min-height: $sk-btn-height !important; -} - - -.mp__popup { - :global .MuiAccordionSummary-content { - margin: 0 !important; - } - - :global(.MuiAccordionSummary-root) { - min-height: $sk-btn-height !important; - } - - :global .MuiAccordion-root { - border-radius: $sk-border-radius !important; - background-color: $sk-paper-color !important; - box-shadow: none !important; - - .Mui-disabled { - .MuiAccordionSummary-expandIconWrapper { - display: none !important; - } - opacity: 1 !important; - } - } -} - -.mp__btnSwitch { - width: 100%; - text-align: center; - margin-top: -6pt; - margin-bottom: -24pt; - - button { - border: 10px #1a1a1a solid; - } - - :global(svg) { - width: 14pt; - height: 14pt; - } -} - - -.darkTheme { - .br__action_deposit { - background-image: linear-gradient(45deg, #8A463C 0%, #b02d50 100%); - } - - .br__action_transferToSchain { - background-image: linear-gradient(120deg, #9c27b0 0%, #3f51b5 100%) - } - - .br__action_wrap { - background-image: linear-gradient(160deg, #237bc3 0%, #1a3b94 100%); - } - - .br__action_unwrap { - background-image: linear-gradient(160deg, #1a3b94 0%, #237bc3 100%); - } - - .br__action_withdraw { - background-image: linear-gradient(45deg, #b02d50 0%, #8A463C 100%); - } - - .br__action_getMyEth { - background-image: linear-gradient(45deg, #009688 0%, #0c5f57 100%); - } - - .br__action_approve { - background-image: linear-gradient(135deg, #29b1a9 0%, #14532a 100%); - } - - .br__action_approveWrap { - background-image: linear-gradient(135deg, #14532a 0%, #29b1a9 100%); - } - - .br__action_wallet { - background-image: linear-gradient(135deg, #dedede 0%, #aeaeae 100%); - } - - .br__action_wrapsfuel { - background-image: linear-gradient(160deg, #237bc3 0%, #1a3b94 100%); - } - - .mp__chainName { - color: white !important; - } - - .eth-logo { - filter: invert(1) contrast(1.5); - } - - .skaleLogoSm { - filter: invert(1); - } - - .mp__p, - .mp__infoIcon, - .mp__backIcon { - color: rgb(255 255 255 / 65%) !important; - } - - .sk__colorText, - .mp__chainName, - .mp__pWhite { - color: white !important; - } - - .mp__textGray, - .mp__iconGray { - color: #bdbdbd; - } - - :global(.MuiStepIcon-root.Mui-active, - .MuiStepIcon-root.Mui-completed) { - color: #ffffff !important; - } - - :global .MuiIconButton-root { - svg { - color: #000000; - } - } - - :global(.MuiIconButton-root.Mui-disabled) { - svg { - color: rgba(255, 255, 255, 0.3); - } - } - - .mp__btnSwitch { - :global(.Mui-disabled) { - background-color: $sk-paper-color !important; - } - } - - .mp__routeIcon { - svg { - width: 14pt; - color: rgba(255, 255, 255, 0.65) !important; - } - } - - .mp__routeSmall { - h4 { - color: rgba(255, 255, 255, 0.65) !important; - } - } -} - - -.lightTheme { - - .br__action_deposit { - background-image: linear-gradient(45deg, #f7c9b1 0%, #f8d4da 100%); - } - - .br__action_transferToSchain { - background-image: linear-gradient(120deg, #e0baf0 0%, #c1ccf8 100%); - } - - .br__action_wrap { - background-image: linear-gradient(160deg, #9dcff1 0%, #8ab1dd 100%); - } - - .br__action_unwrap { - background-image: linear-gradient(160deg, #8ab1dd 0%, #9dcff1 100%); - } - - .br__action_withdraw { - background-image: linear-gradient(45deg, #f8d4da 0%, #f7c9b1 100%); - } - - .br__action_getMyEth { - background-image: linear-gradient(45deg, #92f7e1 0%, #76afaa 100%); - } - - .br__action_approve { - background-image: linear-gradient(135deg, #89e0ce 0%, #58a47c 100%); - } - - .br__action_approveWrap { - background-image: linear-gradient(135deg, #58a47c 0%, #89e0ce 100%); - } - - .br__action_wallet { - background-image: linear-gradient(135deg, #f5f5f5 0%, #dfdfdf 100%); - } - - .br__action_wrapsfuel { - background-image: linear-gradient(160deg, #9dcff1 0%, #8ab1dd 100%); - } - - .mp__chainName { - color: black !important; - } - - .mp__p, - .mp__infoIcon, - .mp__backIcon { - color: rgb(0 0 0 / 65%) !important; - } - - .sk__colorText { - color: black !important; - } - - .MuiInput-root { - .Mui-disabled { - color: rgb(0 0 0 / 60%); - } - } - - .mp__textGray, - .mp__iconGray { - color: #4f4f4f; - } - - .MuiStepIcon-root.Mui-active, - .MuiStepIcon-root.Mui-completed { - color: rgba(0, 0, 0, 0.87) !important; - } - - .MuiButton-root.Mui-disabled { - color: rgba(0, 0, 0, 0.26) !important; - } - - .mp__btnSwitch { - :global(.Mui-disabled) { - background-color: rgb(224 224 224) !important; - } - } - - :global(.MuiIconButton-root) { - svg { - color: #ffffff; - } - } - - :global(.MuiIconButton-root.Mui-disabled) { - svg { - color: rgba(0, 0, 0, 0.26); - } - } - - .mp__routeIcon { - svg { - width: 14pt; - color: rgba(0, 0, 0, 0.65) !important; - } - } - - .mp__routeSmall { - h4 { - color: rgba(0, 0, 0, 0.65) !important; - } - } -} - -.mp__routeSmall { - h4 { - font-weight: 500 !important; - text-transform: none !important; - font-size: 0.8525rem !important; - line-height: 1.6 !important; - letter-spacing: 0.03857em !important; - } -} - -.mp__p3 { - margin: 0; - font-weight: 600 !important; - // text-transform: uppercase !important; - font-size: 0.6525rem !important; - line-height: 1.6 !important; - letter-spacing: 0.02857em !important; -} - -.mp__p2 { - margin: 0; - font-weight: 600 !important; - // text-transform: uppercase !important; - font-size: 0.8525rem !important; - line-height: 1.6 !important; - letter-spacing: 0.03857em !important; -} - -.mp__completeText { - font-weight: 600 !important; - text-transform: none !important; - font-size: .8525rem !important; - line-height: 1.6 !important; - letter-spacing: .03857em !important; - color: white !important; -} - -.mp__code { - font-size: 0.75rem; -} - -.mp__codeWrap { - position: fixed; - top: 0; - left: 0; - margin: 20px; - padding: 20px; - background-color: black; -} - -.mp_p_desc { - margin: 0; - font-weight: 500 !important; - text-transform: none !important; - font-size: 0.8025rem !important; - line-height: 1.6 !important; - letter-spacing: 0.03857em !important; -} - - -.mp__btnConnect { - width: 100%; - margin-top: 10pt; - border-radius: 15px !important; - - text-align: left; - - .mp__iconConnect { - width: 20pt !important; - } - - :global(.MuiPaper-root) { - background-color: $sk-paper-color !important; - width: 100%; - padding: 10pt 15pt; - border-radius: 15px !important; - } - - .mp__iconGray { - width: 12pt; - } -} - -:global(.Mui-disabled) { - .mp__iconConnect { - filter: saturate(0) !important; - } -} - - -.mp__textToken { - padding-top: 25pt !important; -} - -.btn-bg { - background-color: #0d0d0d !important; -} - -.hidden { - opacity: 0; -} - -.noDisplay { - display: none !important; -} - -.skaleBtnHidden { - display: none !important; -} - -.skaleBtn { - border: 1px #7f7f7f45 solid; -} - -.mp__popper { - width: 300pt; -} - -.mp__transferToFix { - margin-top: 26pt !important; -} - -.mp__textCentered { - text-align: center; -} - - -.mp__infoIcon { - svg { - height: 25pt; - width: 100%; - } -} - -.mp__minContent { - width: min-content; -} - -.tokenIdIcon { - padding: 0 0 0 16px; - - :global(svg) { - //width: 20px; - } -} - -.mp__backIcon { - width: 8pt !important; - height: 8pt !important; - // margin-left: 2pt !important; -} - -.mp__amountChip { - :global(.MuiChip-label) { - text-transform: uppercase !important; - } -} - -.mp__amount { - text-transform: uppercase !important; -} - -.mp__amountIcon { - width: 18pt; -} - -.mp__chainIcon { - img { - width: 15pt !important; - height: 15pt !important; - } - - svg { - width: 16pt !important; - height: 16pt !important; - color: white; - } -} - - -.mp__summaryChip { - :global(.MuiChip-icon) { - width: 17pt; - height: 17pt; - } - - margin-left: 5px; - - span { - font-size: 1.3em; - font-weight: 600; - } -} - - -.br__history { - :global .MuiAccordion-root:before { - opacity: 0; - } -} - -.sk__smBth { - font-size: 0.7rem !important; - padding-left: 10px !important; - padding-right: 10px !important; -} - -.sk__uppercase { - text-transform: uppercase !important; -} - -.sk__skeleton { - border-radius: $sk-border-radius !important; -} - - -.sk__balanceCard { - .chainIcon { - width: 15px !important; - height: 15px !important; - } -} - - -.mp__chainIconSm { - :global(img) { - width: 22px; - height: 22px; - } -} - -.mp__chainIconXs { - :global(img) { - width: 17px; - height: 17px; - } -} - -.sk__chainApps { - background-color: $sk-paper-color; - border-radius: $sk-border-radius !important; - padding: 5px; -} \ No newline at end of file diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index 2d7e12e..101065d 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -1,67 +1,112 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file WidgetUI.ts + * @copyright SKALE Labs 2023-Present + */ + import React from 'react'; import { StyledEngineProvider } from '@mui/material/styles'; +import { useAccount } from 'wagmi'; + +import Collapse from '@mui/material/Collapse'; import Fab from '@mui/material/Fab'; import CloseIcon from '@mui/icons-material/Close'; -import Paper from '@mui/material/Paper'; import { createTheme, ThemeProvider } from '@mui/material/styles'; -import { getMuiZIndex } from './Themes'; +import { getMuiZIndex } from '../../core/themes'; import skaleLogo from './skale_logo_short.svg'; +import { useUIStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' +import SkPaper from '../SkPaper'; + import WidgetBody from '../WidgetBody'; -import { Connector } from '../WalletConnector'; -import Debug from '../Debug'; -import { clsNames } from '../../core/helper'; -import styles from "./WidgetUI.scss"; + +import { cls } from '../../core/helper'; + +import styles from "../../styles/styles.scss"; +import common from "../../styles/common.scss"; +import { PaletteMode } from '@mui/material'; + +import SkConnect from '../SkConnect'; +import ErrorMessage from '../ErrorMessage'; export function WidgetUI(props) { + + const { address } = useAccount(); + + const metaportTheme = useUIStore((state) => state.theme); + const isOpen = useUIStore((state) => state.open); + const setOpen = useUIStore((state) => state.setOpen); + + const errorMessage = useMetaportStore((state) => state.errorMessage); + + if (!metaportTheme) return
+ let theme = createTheme({ - zIndex: getMuiZIndex(props.theme), + zIndex: getMuiZIndex(metaportTheme), palette: { - mode: props.theme.mode, + mode: metaportTheme.mode as PaletteMode, background: { - paper: props.theme.background + paper: metaportTheme.background }, primary: { - main: props.theme.primary, + main: metaportTheme.primary, }, secondary: { - main: props.theme.background + main: metaportTheme.background }, }, }); const handleClick = (_: React.MouseEvent) => { - props.setOpen(props.open ? false : true); + setOpen(isOpen ? false : true); }; - const themeCls = props.theme.mode === 'dark' ? styles.darkTheme : styles.lightTheme; + const themeCls = metaportTheme.mode === 'dark' ? styles.darkTheme : styles.lightTheme; + const commonThemeCls = metaportTheme.mode === 'dark' ? common.darkTheme : common.lightTheme; let fabTop: boolean = false; let fabLeft: boolean = false; - if (props.theme) { - fabTop = props.theme.position.bottom === 'auto'; - fabLeft = props.theme.position.right === 'auto'; + if (metaportTheme) { + fabTop = metaportTheme.position.bottom === 'auto'; + fabLeft = metaportTheme.position.right === 'auto'; } - const fabButton = (
-
-
+ const fabButton = (
+
+
- {props.open ? ( + {isOpen ? ( ) : (
-
+
{fabTop ? fabButton : null}
-
-
- -
- - {props.walletConnected ? ( - - ) : ( - - )} -
-
-
-
-
+ + + + + + + + + {address ? :
} +
+
+
+
{fabTop ? null : fabButton}
diff --git a/src/components/WidgetUI/_variables.scss b/src/components/WidgetUI/_variables.scss deleted file mode 100644 index 5737ead..0000000 --- a/src/components/WidgetUI/_variables.scss +++ /dev/null @@ -1,7 +0,0 @@ -$sk-border-radius: 15px; - -$sk-paper-color: rgb(136 135 135 / 15%); -$sk-gray-background-color: rgba(161, 161, 161, 0.2); - - -$sk-btn-height: 47px; \ No newline at end of file diff --git a/src/components/WrappedTokensWarning/WrappedTokensWarning.tsx b/src/components/WrappedTokensWarning/WrappedTokensWarning.tsx deleted file mode 100644 index 36e5313..0000000 --- a/src/components/WrappedTokensWarning/WrappedTokensWarning.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file WrappedTokensWarning.ts - * @copyright SKALE Labs 2022-Present - */ - -import Button from '@mui/material/Button'; - -import { View } from '../../core/dataclasses/View'; - -import { clsNames } from '../../core/helper'; -import styles from "../WidgetUI/WidgetUI.scss"; - - -export default function WrappedTokensWarning(props) { - if (!props.wrappedTokens || !props.wrappedTokens.erc20) return; - const wrappedTokens = Object.entries(props.wrappedTokens.erc20); - if (wrappedTokens.length === 0) return; - return (
-

- ❗ You have wrapped tokens in your wallet. Please unwrap them before proceeding with your transfer. -

- -
) -} diff --git a/src/components/WrappedTokensWarning/index.ts b/src/components/WrappedTokensWarning/index.ts deleted file mode 100644 index 081c48c..0000000 --- a/src/components/WrappedTokensWarning/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./WrappedTokensWarning"; \ No newline at end of file diff --git a/src/configs/metaportConfigStaging.json b/src/configs/metaportConfigStaging.json deleted file mode 100644 index 5026dec..0000000 --- a/src/configs/metaportConfigStaging.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "skaleNetwork": "staging3", - "autoLookup": false, - "openOnLoad": true, - "openButton": true, - "debug": false, - "chains": [ - "mainnet", - "staging-perfect-parallel-gacrux", - "staging-severe-violet-wezen", - "staging-legal-crazy-castor", - "staging-utter-unripe-menkar", - "staging-faint-slimy-achird", - "staging-weepy-fitting-caph" - ], - "tokens": { - "mainnet": { - "eth": { - "chains": [ - "staging-perfect-parallel-gacrux", - "staging-legal-crazy-castor" - ] - }, - "erc20": { - "_SKL_0x493D4442013717189C9963a2e275Ad33bfAFcE11": { - "name": "SKL", - "address": "0x493D4442013717189C9963a2e275Ad33bfAFcE11", - "symbol": "SKL" - }, - "_skl_0x2868716b3B4AEa43E8387922AFE71a77D101854e": { - "address": "0x2868716b3B4AEa43E8387922AFE71a77D101854e", - "decimals": "18", - "name": "SKALE", - "symbol": "skl", - "mappings": { - "staging-perfect-parallel-gacrux": "0x6FA34A9A1eb81d550D6bB5E129B9FDc35aa0F021" - } - }, - "_usdc_0xdE7EBAA09961D4BdD9E41206De23D2214e22b5d2": { - "address": "0xdE7EBAA09961D4BdD9E41206De23D2214e22b5d2", - "decimals": "18", - "name": "USDC", - "symbol": "usdc" - }, - "_usdt_0xB89fCb83F7868d8Cfd104c768FF93FDF74C8a590": { - "address": "0xB89fCb83F7868d8Cfd104c768FF93FDF74C8a590", - "decimals": "18", - "name": "USDT", - "symbol": "usdt" - }, - "_wbtc_0xd7BAd1A8E66050178a815355567E5e0218a5B659": { - "address": "0xd7BAd1A8E66050178a815355567E5e0218a5B659", - "decimals": "18", - "name": "WBTC", - "symbol": "wbtc" - } - }, - "erc721meta": { - "_SPACE_0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6": { - "address": "0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6", - "name": "SKALE Space", - "symbol": "SPACE", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png" - } - }, - "erc1155": { - "_SKALIENS_0x6cb73D413970ae9379560aA45c769b417Fbf33D6": { - "address": "0x6cb73D413970ae9379560aA45c769b417Fbf33D6", - "name": "SKALIENS Collection", - "symbol": "SKALIENS", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png" - } - } - }, - "staging-utter-unripe-menkar": { - "erc721": { - "_TANK_0x4aaa1bb85d9339811b65566fa1aae11a8a9db28d": { - "name": "TANK", - "address": "0x4aaa1bb85d9339811b65566fa1aae11a8a9db28d", - "symbol": "TANK", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Bomb/3D/bomb_3d.png" - } - } - }, - "staging-legal-crazy-castor": { - "erc20": { - "_ETH_0xa270484784f043e159f74C03B691F80B6F6e3c24": { - "name": "ETH", - "address": "0xa270484784f043e159f74C03B691F80B6F6e3c24", - "symbol": "ETH", - "iconUrl": "https://ruby.exchange/images/tokens/eth-square.jpg", - "wraps": { - "iconUrl": "https://ruby.exchange/images/tokens/eth-square.jpg", - "address": "0xD2Aaa00700000000000000000000000000000000", - "symbol": "ETHC" - } - }, - "_SKL_0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6": { - "name": "SKL", - "symbol": "SKL", - "address": "0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6", - "iconUrl": "https://ruby.exchange/images/tokens/skl-square.jpg", - "wraps": { - "address": "0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6", - "symbol": "SKL", - "iconUrl": "https://ruby.exchange/images/tokens/skl-square.jpg" - } - }, - "_USDC_0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0": { - "name": "USDC", - "symbol": "USDC", - "address": "0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0", - "iconUrl": "https://ruby.exchange/images/tokens/usdc-square.jpg", - "wraps": { - "address": "0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33", - "symbol": "USDC", - "iconUrl": "https://ruby.exchange/images/tokens/usdc-square.jpg" - } - } - } - }, - "staging-perfect-parallel-gacrux": { - "erc20": { - "_ETH_0xBA3f8192e28224790978794102C0D7aaa65B7d70": { - "address": "0xBA3f8192e28224790978794102C0D7aaa65B7d70", - "name": "ETH", - "symbol": "ETH", - "cloneSymbol": "ETH", - "wraps": { - "address": "0xD2Aaa00700000000000000000000000000000000", - "symbol": "ETH", - "name": "aaaa" - } - }, - "_SKILL_0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA": { - "address": "0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA", - "name": "SKILL Token", - "symbol": "SKILL", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Compass/3D/compass_3d.png" - }, - "DMT": { - "address": "0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA", - "name": "DMT", - "symbol": "DMT", - "cloneSymbol": "DMTC", - "wraps": { - "address": "0x688f6d050B935BF06531b51B5e598318788fA7a5", - "symbol": "DMT", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Compass/3D/compass_3d.png" - } - }, - "_SKL_0x099A46F35b627CABee27dc917eDA253fFbC55Be6": { - "address": "0x099A46F35b627CABee27dc917eDA253fFbC55Be6", - "decimals": "18", - "name": "SKL S2S", - "symbol": "SKL" - } - }, - "erc721": { - "_SPS_0x30216880A73B67133F37de35e769b8e1A943D35c": { - "address": "0x30216880A73B67133F37de35e769b8e1A943D35c", - "name": "SKALE Space S2S", - "symbol": "SPS", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Glowing%20star/3D/glowing_star_3d.png" - } - }, - "erc1155": { - "skaliens": { - "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", - "name": "SKALIENS Collection", - "symbol": "SKALIENS2S", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png" - }, - "medals": { - "address": "0x5D8bD602dC5468B3998e8514A1851bd5888E9639", - "name": "Medals", - "symbol": "MEDALS2S", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/1st%20place%20medal/3D/1st_place_medal_3d.png" - }, - "_ANIMALS_0xDf87EEF0977148129969b01b329379b17756cdDE": { - "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", - "name": "Funny Animals", - "symbol": "ANIMALS", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Frog/3D/frog_3d.png" - } - } - } - }, - "theme": { - "mode": "dark" - } -} \ No newline at end of file diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index d206977..2332845 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -21,11 +21,27 @@ * @copyright SKALE Labs 2022-Present */ +import debug from 'debug'; + +import { Chain } from '@wagmi/core'; +import { WalletClient } from 'viem'; +import { Contract, Provider } from 'ethers'; + import { MainnetChain, SChain } from '@skalenetwork/ima-js'; -import TokenData from '../dataclasses/TokenData'; +import { TokenData, CustomAbiTokenType } from '../dataclasses'; +import MetaportCore, { createTokenData } from '../metaport'; import { externalEvents } from '../events'; import { toWei } from '../convertation'; import { ActionState, LOADING_BUTTON_TEXT } from './actionState'; +import { isMainnet } from '../helper'; + +import { IMA_ABIS } from '../contracts'; +import { isMainnetChainId, getMainnetAbi } from '../network'; + +import { walletClientToSigner } from '../ethers'; + +debug.enable('*'); +const log = debug('metaport:actions'); export type ActionType = typeof Action; @@ -35,72 +51,162 @@ export class Action { execute(): void { return; }; preAction(): void { return; }; - static label: string = ''; - static buttonText: string = ''; - static loadingText: string = ''; + mpc: MetaportCore mainnet: MainnetChain sChain1: SChain sChain2: SChain + chainName1: string chainName2: string address: string amount: string - amountWei: string + amountWei: bigint tokenId: number - tokenData: TokenData + token: TokenData + + walletClient: WalletClient + + sourceToken: Contract + destToken: Contract + unwrappedToken: Contract | undefined + + originAddress: string - switchMetamaskChain: (switchBack: boolean) => Promise - setActiveStep: React.Dispatch> activeStep: number + setActiveStep: React.Dispatch> setAmountErrorMessage: React.Dispatch> setBtnText: (btnText: string) => void - wrap: boolean + _switchNetwork: (chainId: number | bigint) => Chain | undefined constructor( - mainnet: MainnetChain, - sChain1: SChain, - sChain2: SChain, + mpc: MetaportCore, + // mainnet: MainnetChain, + // sChain1: SChain, + // sChain2: SChain, chainName1: string, chainName2: string, address: string, amount: string, tokenId: number, - tokenData: TokenData, - switchMetamaskChain: (switchBack: boolean) => Promise, - setActiveStep: React.Dispatch>, - activeStep: number, - setAmountErrorMessage: React.Dispatch>, - setBtnText: (btnText: string) => void + token: TokenData, + setAmountErrorMessage: (amountErrorMessage: string) => void, + setBtnText: (btnText: string) => void, + switchNetwork: (chainId: number | bigint) => Chain | undefined, + walletClient: WalletClient ) { - this.mainnet = mainnet; - this.sChain1 = sChain1; - this.sChain2 = sChain2; + this.mpc = mpc; + // this.mainnet = mainnet; + // this.sChain1 = sChain1; + // this.sChain2 = sChain2; this.chainName1 = chainName1; this.chainName2 = chainName2; this.address = address; this.amount = amount; - if (amount) this.amountWei = toWei(amount, tokenData.decimals); + if (amount) this.amountWei = toWei(amount, token.meta.decimals); this.tokenId = Number(tokenId); - this.tokenData = tokenData; - this.switchMetamaskChain = switchMetamaskChain; - this.setActiveStep = setActiveStep; - this.activeStep = activeStep; + this.token = createTokenData(token.keyname, chainName1, token.type, this.mpc.config); + //! todo: init token here!!!!!, do not pass !!! + + // todo: init token contracts! + + if (isMainnet(chainName1)) { + this.mainnet = this.mpc.mainnet() + } else { + this.sChain1 = this.mpc.schain(this.chainName1) + } + if (isMainnet(chainName2)) { + this.mainnet = this.mpc.mainnet() + } else { + this.sChain2 = this.mpc.schain(this.chainName2) + } + + const provider1 = isMainnet(chainName1) ? this.mainnet.provider : this.sChain1.provider; + const provider2 = isMainnet(chainName2) ? this.mainnet.provider : this.sChain2.provider; + + this.sourceToken = mpc.tokenContract( + chainName1, + token.keyname, + token.type, + provider1, + this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, + this.token.wrapper(this.chainName2) ? this.chainName2 : null + ); + + this.originAddress = this.mpc.originAddress( + chainName1, chainName2, token.keyname, token.type); + + + console.log('----') + console.log(this.chainName2) + console.log(token) + console.log(token.wrapper(this.chainName2)) + console.log('----') + + if (this.token.wrapper(this.chainName2)) { + this.unwrappedToken = mpc.tokenContract( + chainName1, + token.keyname, + token.type, + provider1 + ); + } + + // todo: use wrapper address! + const destWrapperAddress = this.mpc.config.connections[this.chainName2][this.token.type][this.token.keyname].chains[this.chainName1].wrapper; + if (this.token.isClone(this.chainName2) && destWrapperAddress) { + this.destToken = mpc.tokenContract( + chainName2, + token.keyname, + token.type, + provider2, + CustomAbiTokenType.erc20wrap, + this.chainName1 + ) + } else { + this.destToken = mpc.tokenContract( + chainName2, + token.keyname, + token.type, + provider2 + ) + } + + // this.switchMetamaskChain = switchMetamaskChain; + + // this.setActiveStep = setActiveStep; + // this.activeStep = activeStep; this.setAmountErrorMessage = setAmountErrorMessage; this.setBtnText = setBtnText; + this._switchNetwork = switchNetwork; + this.walletClient = walletClient; - if (this.tokenData) this.wrap = !!this.tokenData.unwrappedSymbol && !this.tokenData.clone; + // if (this.tokenData) this.wrap = !!this.token.unwrappedSymbol && !this.token.clone; } + // tokenContract( + // provider: Provider, + // source: boolean = true + // ): Contract { + // return this.mpc.tokenContract( + // source ? this.chainName1 : this.chainName2, + // this.token.keyname, + // this.token.type, + // provider + // ) + // } + updateState( currentState: ActionState, transactionHash?: string, timestamp?: string | number ) { + log(`actionStateUpd: ${this.constructor.name} - ${currentState} - ${this.token.keyname} \ +- ${this.chainName1} -> ${this.chainName2}`); externalEvents.actionStateUpdated( this.constructor.name, currentState, @@ -117,28 +223,53 @@ export class Action { ); this.setBtnText(LOADING_BUTTON_TEXT[currentState]); } + + async getConnectedChain( + provider: Provider, + customAbiTokenType?: CustomAbiTokenType, + destChainName?: string + ): Promise { + let chain: MainnetChain | SChain; + this.updateState('switch'); + const currentChainId = this.walletClient.chain.id; + const { chainId } = await provider.getNetwork(); + log(`Current chainId: ${currentChainId}, required chainId: ${chainId} `); + if (currentChainId !== Number(chainId)) { + log(`Switching network to ${chainId}...`); + const chain = await this._switchNetwork(Number(chainId)); + if (!chain) { + throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `); + } + log(`Network switched to ${chainId}...`); + } + const signer = walletClientToSigner(this.walletClient) + if (isMainnetChainId(chainId, this.mpc.config.skaleNetwork)) { + chain = new MainnetChain(signer.provider, getMainnetAbi(this.mpc.config.skaleNetwork)); + } else { + chain = new SChain(signer.provider, IMA_ABIS.schain); + } + const token = this.mpc.tokenContract( + destChainName === this.chainName1 ? this.chainName2 : this.chainName1, + this.token.keyname, + this.token.type, + chain.provider, + customAbiTokenType, + destChainName + ); + chain.erc20.addToken(this.token.keyname, token); + return chain; + } } export abstract class TransferAction extends Action { - static label = 'Transfer' - static buttonText = 'Transfer' - static loadingText = 'Transferring' - transferComplete(tx): void { externalEvents.transferComplete( tx, this.chainName1, this.chainName2, - this.tokenData.keyname, + this.token.keyname, false ); } } - - -export abstract class ApproveAction extends Action { - static label = 'Approve transfer' - static buttonText = 'Approve' - static loadingText = 'Approving' -} diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index 972aec3..f971539 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -23,11 +23,11 @@ import debug from 'debug'; -import { Contract } from 'web3-eth-contract'; +import { Contract } from "ethers"; import { MainnetChain, SChain } from '@skalenetwork/ima-js'; import { fromWei } from '../convertation'; -import TokenData from '../dataclasses/TokenData'; +import { TokenData } from '../dataclasses/TokenData'; import * as interfaces from '../interfaces'; import { addressesEqual } from '../helper'; import { DEFAULT_ERC20_DECIMALS, SFUEL_RESERVE_AMOUNT } from '../constants'; @@ -48,9 +48,9 @@ export async function checkEthBalance( // TODO: optimize balance checks try { const balance = await chain.ethBalance(address); log(`address: ${address}, eth balance: ${balance}, amount: ${amount}`); - const balanceEther = fromWei(balance, tokenData.decimals); + const balanceEther = fromWei(balance, tokenData.meta.decimals); if (Number(amount) + SFUEL_RESERVE_AMOUNT > Number(balanceEther)) { - checkRes.msg = `Current balance: ${balanceEther} ${tokenData.symbol}. \ + checkRes.msg = `Current balance: ${balanceEther} ${tokenData.meta.symbol}. \ ${SFUEL_RESERVE_AMOUNT} ETH will be reserved to cover transfer costs.`; } else { checkRes.res = true; @@ -73,11 +73,11 @@ export async function checkERC20Balance( const checkRes: interfaces.CheckRes = { res: false }; if (!amount || Number(amount) === 0) return checkRes; try { - const balance = await tokenContract.methods.balanceOf(address).call(); + const balance = await tokenContract.balanceOf(address); log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`); - const balanceEther = fromWei(balance, tokenData.decimals); + const balanceEther = fromWei(balance, tokenData.meta.decimals); if (Number(amount) > Number(balanceEther)) { - checkRes.msg = `Current balance: ${balanceEther} ${tokenData.symbol}`; + checkRes.msg = `Insufficient balance: ${balanceEther} ${tokenData.meta.symbol}`; } else { checkRes.res = true; } @@ -97,7 +97,7 @@ export async function checkSFuelBalance( const checkRes: interfaces.CheckRes = { res: false }; if (!amount || Number(amount) === 0) return checkRes; try { - const balance = await sChain.web3.eth.getBalance(address); + const balance = await sChain.provider.getBalance(address); log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`); const balanceEther = fromWei(balance, DEFAULT_ERC20_DECIMALS); if (Number(amount) + SFUEL_RESERVE_AMOUNT > Number(balanceEther)) { @@ -125,11 +125,11 @@ export async function checkERC20Allowance( const checkRes: interfaces.CheckRes = { res: false }; if (!amount || Number(amount) === 0) return checkRes; try { - const allowance = await tokenContract.methods.allowance( + const allowance = await tokenContract.allowance( address, approvalAddress - ).call(); - const allowanceEther = fromWei(allowance, tokenData.decimals); + ) + const allowanceEther = fromWei(allowance, tokenData.meta.decimals); log(`allowanceEther: ${allowanceEther}, amount: ${amount}`); checkRes.res = Number(allowanceEther) >= Number(amount); return checkRes; @@ -151,7 +151,7 @@ export async function checkERC721( const checkRes: interfaces.CheckRes = { res: true, approved: false }; if (!tokenId) return checkRes; try { - approvedAddress = await tokenContract.methods.getApproved(tokenId).call(); + approvedAddress = await tokenContract.getApproved(tokenId) log(`approvedAddress: ${approvedAddress}, address: ${address}`); } catch (err) { log(err); @@ -159,7 +159,7 @@ export async function checkERC721( return checkRes; } try { - const currentOwner = await tokenContract.methods.ownerOf(tokenId).call();; + const currentOwner = await tokenContract.ownerOf(tokenId); log(`currentOwner: ${currentOwner}, address: ${address}`); if (!addressesEqual(currentOwner, address)) { checkRes.msg = 'This account is not an owner of this tokenId'; @@ -187,15 +187,15 @@ export async function checkERC1155( if (!tokenId || !amount) return checkRes; try { - const balance = await tokenContract.methods.balanceOf(address, tokenId).call(); + const balance = await tokenContract.balanceOf(address, tokenId) log(`address: ${address}, balanceEther: ${balance}, amount: ${amount}`); if (Number(amount) > Number(balance)) { - checkRes.msg = `Current balance: ${balance} ${tokenData.symbol}`; + checkRes.msg = `Current balance: ${balance} ${tokenData.meta.symbol}`; } - checkRes.approved = await tokenContract.methods.isApprovedForAll( + checkRes.approved = await tokenContract.isApprovedForAll( address, approvalAddress - ).call(); + ) } catch (err) { log(err); checkRes.msg = 'Something went wrong, check developer console'; diff --git a/src/core/actions/erc1155.ts b/src/core/actions/erc1155.ts deleted file mode 100644 index 66f0d42..0000000 --- a/src/core/actions/erc1155.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file erc1155.ts - * @copyright SKALE Labs 2022-Present - */ - - -import debug from 'debug'; - -import { TransferAction } from './action'; -import { checkERC1155 } from './checks'; -import { externalEvents } from '../events'; - - -debug.enable('*'); -const log = debug('metaport:actions:erc1155'); - - -export class TransferERC1155M extends TransferAction { - async approve(): Promise { - const checkRes = await checkERC1155( - this.address, - this.mainnet.erc1155.address, - this.tokenId, - this.amount, - this.tokenData, - this.mainnet.erc1155.tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) { - this.updateState('approve'); - log(`TransferERC1155M:execute - approving token ${this.tokenId} (${this.chainName1})`); - const approveTx = await this.mainnet.erc1155.approveAll( - this.tokenData.keyname, - { address: this.address } - ) - const txBlock = await this.mainnet.web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.transactionHash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approve'); - log('TransferERC1155M:execute - approve tx completed: %O', approveTx); - } - } -} - - -export class TransferERC1155S extends TransferAction { - async approve(): Promise { - const checkRes = await checkERC1155( - this.address, - this.sChain1.erc1155.address, - this.tokenId, - this.amount, - this.tokenData, - this.sChain1.erc1155.tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) { - this.updateState('approve'); - log(`TransferERC1155S:execute - approving token ${this.tokenId} (${this.chainName1})`); - const approveTx = await this.sChain1.erc1155.approveAll( - this.tokenData.keyname, - this.tokenId, - { address: this.address } - ); - const txBlock = await this.sChain1.web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.transactionHash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approve'); - log('TransferERC1155M:execute - approve tx completed: %O', approveTx); - } - } -} - - -export class TransferERC1155M2S extends TransferERC1155M { - async execute() { - this.updateState('init'); - await this.approve(); - this.updateState('transfer'); - const destTokenContract = this.sChain2.erc1155.tokens[this.tokenData.keyname] - const balanceOnDestination = await this.sChain2.getERC1155Balance( - destTokenContract, - this.address, - this.tokenId - ); - const tx = await this.mainnet.erc1155.deposit( - this.chainName2, - this.tokenData.keyname, - this.tokenId, - this.amount, - { address: this.address } - ); - const block = await this.mainnet.web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted( - tx, block.timestamp, this.chainName1, 'deposit'); - log('TransferERC1155M2S:execute - tx completed %O', tx); - await this.sChain2.waitERC1155BalanceChange( - destTokenContract, this.address, this.tokenId, balanceOnDestination); - this.updateState('received'); - log('TransferERC1155M2S:execute - tokens received to destination chain'); - this.transferComplete(tx); - } - - async preAction() { - const checkRes = await checkERC1155( - this.address, - this.mainnet.erc1155.address, - this.tokenId, - this.amount, - this.tokenData, - this.mainnet.erc1155.tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) this.setActiveStep(0); - } -} - - -export class TransferERC1155S2M extends TransferERC1155S { - async execute() { - this.updateState('init'); - await this.approve(); - this.updateState('transfer'); - const destTokenContract = this.mainnet.erc1155.tokens[this.tokenData.keyname]; - const balanceOnDestination = await this.mainnet.getERC1155Balance( - destTokenContract, - this.address, - this.tokenId - ); - const tx = await this.sChain1.erc1155.withdraw( - this.tokenData.originAddress, - this.tokenId, - this.amount, - { address: this.address } - ); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted( - tx, block.timestamp, this.chainName1, 'withdraw'); - await this.mainnet.waitERC1155BalanceChange( - destTokenContract, this.address, this.tokenId, balanceOnDestination); - this.updateState('received'); - log('TransferERC1155S2M:execute - tokens received to destination chain'); - this.transferComplete(tx); - } - - async preAction() { - const checkRes = await checkERC1155( - this.address, - this.sChain1.erc1155.address, - this.tokenId, - this.amount, - this.tokenData, - this.sChain1.erc1155.tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) this.setActiveStep(0); - } -} - - -export class TransferERC1155S2S extends TransferERC1155S { - async execute() { - this.updateState('init'); - await this.approve(); - this.updateState('transfer'); - const destTokenContract = this.sChain2.erc1155.tokens[this.tokenData.keyname]; - const ownerOnDestination = await this.sChain2.getERC1155Balance( - destTokenContract, - this.address, - this.tokenId - ); - const tx = await this.sChain1.erc1155.transferToSchain( - this.chainName2, - this.tokenData.originAddress, - this.tokenId, - this.amount, - { address: this.address } - ); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted( - tx, block.timestamp, this.chainName1, 'transferToSchain'); - await this.sChain2.waitERC1155BalanceChange( - destTokenContract, this.address, this.tokenId, ownerOnDestination); - this.updateState('received'); - log('TransferERC1155S2S:execute - tokens received to destination chain'); - this.transferComplete(tx); - } - - async preAction() { - const checkRes = await checkERC1155( - this.address, - this.sChain1.erc1155.address, - this.tokenId, - this.amount, - this.tokenData, - this.sChain1.erc1155.tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) this.setActiveStep(0); - } -} \ No newline at end of file diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index 5e73483..18b549e 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -24,12 +24,15 @@ import debug from 'debug'; +import { MainnetChain, SChain } from '@skalenetwork/ima-js'; + import { externalEvents } from '../events'; import { toWei } from '../convertation'; import { MAX_APPROVE_AMOUNT } from '../constants'; -import { TransferAction, Action } from './action'; +import { TransferAction, Action } from '../actions/action'; import { checkERC20Balance, checkERC20Allowance, checkSFuelBalance } from './checks'; +import { CustomAbiTokenType } from '../dataclasses'; debug.enable('*'); @@ -38,28 +41,29 @@ const log = debug('metaport:actions:erc20'); export class TransferERC20S2S extends TransferAction { async execute() { - log('TransferERC20S2S:execute - starting'); this.updateState('init'); - - const tokenContract = this.sChain1.erc20.tokens[this.tokenData.keyname]; const checkResAllowance = await checkERC20Allowance( this.address, this.sChain1.erc20.address, this.amount, - this.tokenData, - tokenContract + this.token, + this.sourceToken ); - + const sChain = await this.getConnectedChain( + this.sChain1.provider, + this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, + this.token.wrapper(this.chainName2) ? this.chainName2 : null + ) as SChain; if (!checkResAllowance.res) { this.updateState('approve'); - const approveTx = await this.sChain1.erc20.approve( - this.tokenData.keyname, + const approveTx = await sChain.erc20.approve( + this.token.keyname, MAX_APPROVE_AMOUNT, - this.sChain1.erc20.address, + sChain.erc20.address, { address: this.address } ); - const txBlock = await this.sChain1.web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.transactionHash, txBlock.timestamp); + const txBlock = await sChain.provider.getBlock(approveTx.blockNumber); + this.updateState('approveDone', approveTx.hash, txBlock.timestamp); externalEvents.transactionCompleted( approveTx, txBlock.timestamp, this.chainName1, 'approve'); log('ApproveERC20S:execute - tx completed: %O', approveTx); @@ -69,92 +73,74 @@ export class TransferERC20S2S extends TransferAction { this.updateState('transfer'); - const amountWei = toWei(this.amount, this.tokenData.decimals); - const destTokenContract = this.sChain2.erc20.tokens[this.tokenData.keyname]; + const amountWei = toWei(this.amount, this.token.meta.decimals); let balanceOnDestination; - if (this.tokenData.wrapsSFuel && this.tokenData.clone) { - balanceOnDestination = await this.sChain2.web3.eth.getBalance(this.address); + const tokenConnection = this.token.connections[this.chainName2]; + + const isDestinationSFuel = tokenConnection.wrapsSFuel && tokenConnection.clone; // TODO! + + if (isDestinationSFuel) { + balanceOnDestination = await this.sChain2.provider.getBalance(this.address); } else { balanceOnDestination = await this.sChain2.getERC20Balance( - destTokenContract, + this.destToken, this.address ); } - - const tx = await this.sChain1.erc20.transferToSchain( + const tx = await sChain.erc20.transferToSchain( this.chainName2, - this.tokenData.originAddress, + this.originAddress, amountWei, { address: this.address } ); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted( - tx, block.timestamp, this.chainName1, 'transferToSchain'); - log('TransferERC20S2S:execute - tx completed %O', tx); - - if (this.tokenData.wrapsSFuel && this.tokenData.clone) { + const block = await sChain.provider.getBlock(tx.blockNumber); + this.updateState('transferDone', tx.hash, block.timestamp); + if (isDestinationSFuel) { await this.sChain2.waitETHBalanceChange( this.address, balanceOnDestination ); } else { await this.sChain2.waitERC20BalanceChange( - destTokenContract, + this.destToken, this.address, balanceOnDestination ); } - this.updateState('received'); - log('TransferERC20S2S:execute - tokens received to destination chain'); - - const unwrap = !!this.tokenData.unwrappedSymbol && this.tokenData.clone; - externalEvents.transferComplete( - tx, - this.chainName1, - this.chainName2, - this.tokenData.keyname, - unwrap - ); } async preAction() { - const tokenContract = this.sChain1.erc20.tokens[this.tokenData.keyname]; - const checkResBalance = await checkERC20Balance( this.address, this.amount, - this.tokenData, - tokenContract + this.token, + this.sourceToken ); - if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg); + return } + this.setAmountErrorMessage(null); } } export class WrapSFuelERC20S extends Action { - static label = 'Wrap token' - static buttonText = 'Wrap token' - static loadingText = 'Wrapping token' - async execute() { log('WrapSFuelERC20S:execute - starting'); this.updateState('wrap'); const tx = await this.sChain1.erc20.fundExit( - this.tokenData.keyname, + this.token.keyname, { address: this.address, value: this.amountWei } ); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - this.updateState('wrapDone', tx.transactionHash, block.timestamp); + const block = await this.sChain1.provider.getBlock(tx.blockNumber); + this.updateState('wrapDone', tx.hash, block.timestamp); externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'wrapsfuel'); log('WrapSFuelERC20S:execute - tx completed %O', tx); } @@ -170,154 +156,158 @@ export class WrapSFuelERC20S extends Action { this.setAmountErrorMessage(checkResBalance.msg); return } + this.setAmountErrorMessage(null); } } export class WrapERC20S extends Action { - static label = 'Wrap' - static buttonText = 'Wrap' - static loadingText = 'Wrapping' - async execute() { - log('WrapERC20S:execute - starting'); this.updateState('init'); - - const tokenContract = this.sChain1.erc20.tokens[this.tokenData.unwrappedSymbol]; const checkResAllowance = await checkERC20Allowance( this.address, - this.tokenData.originAddress, + this.token.connections[this.chainName2].wrapper, this.amount, - this.tokenData, - tokenContract + this.token, + this.unwrappedToken ); - + const sChain = await this.getConnectedChain(this.sChain1.provider) as SChain; + const wrapperToken = this.mpc.tokenContract( + this.chainName1, + this.token.keyname, + this.token.type, + sChain.provider, + CustomAbiTokenType.erc20wrap, + this.chainName2 + ); + sChain.erc20.addToken(`wrap_${this.token.keyname}`, wrapperToken); if (!checkResAllowance.res) { this.updateState('approveWrap'); - log('ApproveWrapERC20S:execute - starting'); - const approveTx = await this.sChain1.erc20.approve( - this.tokenData.unwrappedSymbol, + const approveTx = await sChain.erc20.approve( + this.token.keyname, MAX_APPROVE_AMOUNT, - this.tokenData.originAddress, + this.token.address, { address: this.address } ); - const txBlock = await this.sChain1.web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveWrapDone', approveTx.transactionHash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approveWrap'); - log('ApproveWrapERC20S:execute - tx completed %O', approveTx); + const txBlock = await this.sChain1.provider.getBlock(approveTx.blockNumber); + this.updateState('approveWrapDone', approveTx.hash, txBlock.timestamp); } - this.updateState('wrap'); - - const amountWei = toWei(this.amount, this.tokenData.decimals); - const tx = await this.sChain1.erc20.wrap( - this.tokenData.keyname, + const amountWei = toWei(this.amount, this.token.meta.decimals); + const tx = await sChain.erc20.wrap( + `wrap_${this.token.keyname}`, amountWei, { address: this.address } ); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - this.updateState('wrapDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'wrap'); - log('WrapERC20S:execute - tx completed %O', tx); + const block = await this.sChain1.provider.getBlock(tx.blockNumber); + this.updateState('wrapDone', tx.hash, block.timestamp); } async preAction() { - const tokenContract = this.sChain1.erc20.tokens[this.tokenData.unwrappedSymbol]; const checkResBalance = await checkERC20Balance( this.address, this.amount, - this.tokenData, - tokenContract + this.token, + this.unwrappedToken ); if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg); return } + this.setAmountErrorMessage(null); } } -export class UnWrapERC20S2S extends Action { - static label = 'Unwrap' - static buttonText = 'Unwrap' - static loadingText = 'Unwrapping' - async execute() { - log('UnWrapERC20S2S:execute - starting'); - this.updateState('switch'); - await this.switchMetamaskChain(false); - this.updateState('unwrap'); - try { - const amountWei = toWei(this.amount, this.tokenData.decimals); - const tx = await this.sChain2.erc20.unwrap( - this.tokenData.keyname, - amountWei, - { address: this.address } - ); - const block = await this.sChain2.web3.eth.getBlock(tx.blockNumber); - this.updateState('unwrapDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName2, 'unwrap'); - externalEvents.unwrapComplete(tx, this.chainName2, this.tokenData.keyname); - log('UnWrapERC20S2S:execute - tx completed %O', tx); - } finally { - // log('UnWrapERC20S2S:execute - switchMetamaskChain back'); - // this.switchMetamaskChain(true); - } - } - - async preAction() { - log('preAction: UnWrapERC20S2S'); - const tokenContract = this.sChain2.erc20.tokens[this.tokenData.keyname]; - const checkResBalance = await checkERC20Balance( - this.address, - this.amount, - this.tokenData, - tokenContract - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - } -} +// export class UnWrapERC20S2S123 extends Action { +// static label = 'Unwrap' +// static buttonText = 'Unwrap' +// static loadingText = 'Unwrapping' +// async execute() { +// log('UnWrapERC20S2S:execute - starting'); + +// const sChain = await this.getConnectedChain(this.sChain2.provider) as SChain; +// this.updateState('unwrap'); +// try { +// const amountWei = toWei(this.amount, this.token.meta.decimals); +// const tx = await sChain.erc20.unwrap( +// this.token.keyname, +// amountWei, +// { address: this.address } +// ); +// const block = await sChain.provider.getBlock(tx.blockNumber); +// this.updateState('unwrapDone', tx.hash, block.timestamp); +// externalEvents.transactionCompleted(tx, block.timestamp, this.chainName2, 'unwrap'); +// externalEvents.unwrapComplete(tx.hash, this.chainName2, this.token.keyname); +// log('UnWrapERC20S2S:execute - tx completed %O', tx); +// } finally { +// // log('UnWrapERC20S2S:execute - switchMetamaskChain back'); +// // this.switchMetamaskChain(true); +// } +// } + +// async preAction() { +// log('preAction: UnWrapERC20S2S'); +// const tokenContract = this.sChain2.erc20.tokens[this.token.keyname]; +// const checkResBalance = await checkERC20Balance( +// this.address, +// this.amount, +// this.token, +// tokenContract +// ); +// if (!checkResBalance.res) { +// this.setAmountErrorMessage(checkResBalance.msg); +// return +// } +// } +// } export class UnWrapERC20S extends Action { - static label = 'Unwrap stuck tokens' - static buttonText = 'Unwrap All' - static loadingText = 'Unwrapping' - async execute() { - log('UnWrapERC20S:execute - starting'); + const sChain = await this.getConnectedChain( + this.sChain2.provider, + CustomAbiTokenType.erc20wrap, + this.chainName1 + ) as SChain; + // const token = this.mpc.tokenContract( + // this.chainName2, + // this.token.keyname, + // this.token.type, + // sChain.provider, + // CustomAbiTokenType.erc20wrap, + // this.chainName1 + // ); + // sChain.erc20.addToken(this.token.keyname, token); this.updateState('unwrap'); let tx; - if (this.tokenData.wrapsSFuel) { - tx = await this.sChain1.erc20.undoExit( - this.tokenData.keyname, + if (this.token.connections[this.chainName2].wrapsSFuel) { + tx = await sChain.erc20.undoExit( + this.token.keyname, { address: this.address } ); } else { - const amountWei = toWei(this.amount, this.tokenData.decimals); - tx = await this.sChain1.erc20.unwrap( - this.tokenData.keyname, + const amountWei = toWei(this.amount, this.token.meta.decimals); + tx = await sChain.erc20.unwrap( + this.token.keyname, amountWei, { address: this.address } ); } log('UnWrapERC20S:execute - tx completed %O', tx); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - this.updateState('unwrapDone', tx.transactionHash, block.timestamp); + const block = await sChain.provider.getBlock(tx.blockNumber); + this.updateState('unwrapDone', tx.hash, block.timestamp); externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'unwrap'); - externalEvents.unwrapComplete(tx, this.chainName2, this.tokenData.keyname); + externalEvents.unwrapComplete(tx, this.chainName2, this.token.keyname); } async preAction() { log('preAction: UnWrapERC20S'); - const tokenContract = this.sChain1.erc20.tokens[this.tokenData.keyname]; + const tokenContract = this.sChain1.erc20.tokens[this.token.keyname]; const checkResBalance = await checkERC20Balance( this.address, this.amount, - this.tokenData, + this.token, tokenContract ); if (!checkResBalance.res) { @@ -330,150 +320,136 @@ export class UnWrapERC20S extends Action { export class TransferERC20M2S extends TransferAction { async execute() { - log('TransferERC20M2S:execute - starting'); this.updateState('init'); // check approve + approve - - const tokenContract = this.mainnet.erc20.tokens[this.tokenData.keyname]; const checkResAllowance = await checkERC20Allowance( this.address, this.mainnet.erc20.address, this.amount, - this.tokenData, - tokenContract + this.token, + this.sourceToken ); - + const mainnet = await this.getConnectedChain(this.mainnet.provider) as MainnetChain; if (!checkResAllowance.res) { this.updateState('approve'); - const approveTx = await this.mainnet.erc20.approve( - this.tokenData.keyname, + const approveTx = await mainnet.erc20.approve( + this.token.keyname, MAX_APPROVE_AMOUNT, { address: this.address } ); - const txBlock = await this.mainnet.web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.transactionHash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approve'); - log('ApproveERC20S:execute - tx completed: %O', approveTx); + const txBlock = await mainnet.provider.getBlock(approveTx.blockNumber); + this.updateState('approveDone', approveTx.hash, txBlock.timestamp); } - this.updateState('transfer'); - - const amountWei = toWei(this.amount, this.tokenData.decimals); - const destTokenContract = this.sChain2.erc20.tokens[this.tokenData.keyname]; + const amountWei = toWei(this.amount, this.token.meta.decimals); + // const destTokenContract = this.sChain2.erc20.tokens[this.token.keyname]; const balanceOnDestination = await this.sChain2.getERC20Balance( - destTokenContract, + this.destToken, this.address ); - const tx = await await this.mainnet.erc20.deposit( + const tx = await await mainnet.erc20.deposit( this.chainName2, - this.tokenData.keyname, + this.token.keyname, amountWei, { address: this.address } ); - const block = await this.mainnet.web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); + const block = await mainnet.provider.getBlock(tx.blockNumber); + this.updateState('transferDone', tx.hash, block.timestamp); externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'deposit'); log('TransferERC20M2S:execute - tx completed %O', tx); await this.sChain2.waitERC20BalanceChange( - destTokenContract, this.address, balanceOnDestination); + this.destToken, this.address, balanceOnDestination); this.updateState('received'); log('TransferERC20M2S:execute - tokens received to destination chain'); externalEvents.transferComplete( - tx, + tx.hash, this.chainName1, this.chainName2, - this.tokenData.keyname, + this.token.keyname, false ); } async preAction() { - const tokenContract = this.mainnet.erc20.tokens[this.tokenData.keyname]; const checkResBalance = await checkERC20Balance( this.address, this.amount, - this.tokenData, - tokenContract + this.token, + this.sourceToken ); if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg); return } + this.setAmountErrorMessage(null); } } export class TransferERC20S2M extends TransferAction { async execute() { - log('TransferERC20S2M:execute - starting'); this.updateState('init'); // check approve + approve - const tokenContract = this.sChain1.erc20.tokens[this.tokenData.keyname]; const checkResAllowance = await checkERC20Allowance( this.address, this.sChain1.erc20.address, this.amount, - this.tokenData, - tokenContract + this.token, + this.sourceToken ); - + const sChain = await this.getConnectedChain(this.sChain1.provider) as SChain; if (!checkResAllowance.res) { this.updateState('approve'); - const approveTx = await this.sChain1.erc20.approve( - this.tokenData.keyname, + const approveTx = await sChain.erc20.approve( + this.token.keyname, MAX_APPROVE_AMOUNT, - this.sChain1.erc20.address, + sChain.erc20.address, { address: this.address } ); - const txBlock = await this.sChain1.web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.transactionHash, txBlock.timestamp); + const txBlock = await sChain.provider.getBlock(approveTx.blockNumber); + this.updateState('approveDone', approveTx.hash, txBlock.timestamp); externalEvents.transactionCompleted( approveTx, txBlock.timestamp, this.chainName1, 'approve'); log('ApproveERC20S:execute - tx completed: %O', approveTx); } - this.updateState('transfer'); - - const amountWei = toWei(this.amount, this.tokenData.decimals); - const destTokenContract = this.mainnet.erc20.tokens[this.tokenData.keyname]; + const amountWei = toWei(this.amount, this.token.meta.decimals); const balanceOnDestination = await this.mainnet.getERC20Balance( - destTokenContract, this.address); - - const tx = await this.sChain1.erc20.withdraw( - this.tokenData.originAddress, + this.destToken, this.address); + const tx = await sChain.erc20.withdraw( + this.originAddress, amountWei, { address: this.address } ); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); + const block = await sChain.provider.getBlock(tx.blockNumber); + this.updateState('transferDone', tx.hash, block.timestamp); externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'withdraw'); log('TransferERC20S2M:execute - tx completed %O', tx); - this.mainnet.waitERC20BalanceChange(destTokenContract, this.address, balanceOnDestination); + this.mainnet.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination); this.updateState('received'); log('TransferERC20S2M:execute - tokens received to destination chain'); externalEvents.transferComplete( - tx, + tx.hash, this.chainName1, this.chainName2, - this.tokenData.keyname, + this.token.keyname, false ); } async preAction() { - const tokenContract = this.sChain1.erc20.tokens[this.tokenData.keyname]; const checkResBalance = await checkERC20Balance( this.address, this.amount, - this.tokenData, - tokenContract + this.token, + this.sourceToken ); if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg); return } + this.setAmountErrorMessage(null); } } diff --git a/src/core/actions/erc721.ts b/src/core/actions/erc721.ts deleted file mode 100644 index 1f92c04..0000000 --- a/src/core/actions/erc721.ts +++ /dev/null @@ -1,236 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file erc721.ts - * @copyright SKALE Labs 2022-Present - */ - - -import debug from 'debug'; - -import { Action } from './action'; -import { TokenType } from '../dataclasses/TokenType'; -import { externalEvents } from '../events'; -import { checkERC721 } from './checks'; - - -debug.enable('*'); -const log = debug('metaport:actions:erc721'); - - -class ERC721Action extends Action { - isMeta(): boolean { return this.tokenData.type === TokenType.erc721meta; }; - mn() { return this.isMeta() ? this.mainnet.erc721meta : this.mainnet.erc721; }; - s1() { return this.isMeta() ? this.sChain1.erc721meta : this.sChain1.erc721; }; - s2() { return this.isMeta() ? this.sChain2.erc721meta : this.sChain2.erc721; }; -} - - -class ERC721Transfer extends ERC721Action { - static label = 'Transfer' - static buttonText = 'Transfer' - static loadingText = 'Transferring' - - transferComplete(tx): void { - externalEvents.transferComplete( - tx, - this.chainName1, - this.chainName2, - this.tokenData.keyname, - false - ); - } -} - - -class ERC721TransferS extends ERC721Transfer { - async approve(): Promise { - const checkRes = await checkERC721( - this.address, - this.s1().address, - this.tokenId, - this.s1().tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) { - this.updateState('approve'); - log(`ERC721TransferS:execute - approving token ${this.tokenId} (${this.chainName1})`); - const approveTx = await this.s1().approve( - this.tokenData.keyname, - this.tokenId, - { address: this.address } - ); - const txBlock = await this.s1().web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.transactionHash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approve'); - log('ERC721TransferS:execute - approve tx completed: %O', approveTx); - } - } -} - - -class ERC721TransferM extends ERC721Transfer { - async approve(): Promise { - const checkRes = await checkERC721( - this.address, - this.mn().address, - this.tokenId, - this.mn().tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) { - this.updateState('approve'); - log(`ERC721TransferM:execute - approving token ${this.tokenId} (${this.chainName1})`); - const approveTx = await this.mn().approve( - this.tokenData.keyname, - this.tokenId, - { address: this.address } - ) - const txBlock = await this.mn().web3.eth.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.transactionHash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approve'); - log('ERC721TransferS:execute - approve tx completed: %O', approveTx); - } - } -} - - -export class TransferERC721M2S extends ERC721TransferM { - async execute() { - this.updateState('init'); - await this.approve(); - this.updateState('transfer'); - const destTokenContract = this.s2().tokens[this.tokenData.keyname] - const ownerOnDestination = await this.sChain2.getERC721OwnerOf( - destTokenContract, - this.tokenId - ); - const tx = await this.mn().deposit( - this.chainName2, - this.tokenData.keyname, - this.tokenId, - { address: this.address } - ); - const block = await this.mn().web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted( - tx, block.timestamp, this.chainName1, 'deposit'); - log('TransferERC721M2S:execute - tx completed %O', tx); - await this.sChain2.waitERC721OwnerChange( - destTokenContract, this.tokenId, ownerOnDestination); - this.updateState('received'); - log('Token received to destination chain'); - this.transferComplete(tx); - } - - async preAction() { - const checkRes = await checkERC721( - this.address, - this.mn().address, - this.tokenId, - this.mn().tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) this.setActiveStep(0); - } -} - - -export class TransferERC721S2M extends ERC721TransferS { - async execute() { - this.updateState('init'); - await this.approve(); - this.updateState('transfer'); - const destTokenContract = this.mn().tokens[this.tokenData.keyname]; - const ownerOnDestination = await this.mainnet.getERC721OwnerOf( - destTokenContract, - this.tokenId - ); - const tx = await this.s1().withdraw( - this.tokenData.originAddress, - this.tokenId, - { address: this.address } - ); - - const block = await this.s1().web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted( - tx, block.timestamp, this.chainName1, 'withdraw'); - log('TransferERC721S2M:execute - tx completed %O', tx); - await this.mainnet.waitERC721OwnerChange( - destTokenContract, this.tokenId, ownerOnDestination); - this.updateState('received'); - log('TransferERC721S2M:execute - tokens received to destination chain'); - this.transferComplete(tx); - } - - async preAction() { - const checkRes = await checkERC721( - this.address, - this.s1().address, - this.tokenId, - this.s1().tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) this.setActiveStep(0); - } -} - - -export class TransferERC721S2S extends ERC721TransferS { - async execute() { - this.updateState('init'); - await this.approve(); - this.updateState('transfer'); - const destTokenContract = this.s2().tokens[this.tokenData.keyname]; - const ownerOnDestination = await this.sChain2.getERC721OwnerOf( - destTokenContract, - this.tokenId - ); - const tx = await this.s1().transferToSchain( - this.chainName2, - this.tokenData.originAddress, - this.tokenId, - { address: this.address } - ); - const block = await this.s1().web3.eth.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.transactionHash, block.timestamp); - externalEvents.transactionCompleted( - tx, block.timestamp, this.chainName1, 'transferToSchain'); - log('TransferERC721S2S:execute - tx completed %O', tx); - await this.sChain2.waitERC721OwnerChange( - destTokenContract, this.tokenId, ownerOnDestination); - this.updateState('received'); - log('TransferERC721S2S:execute - tokens received to destination chain'); - this.transferComplete(tx); - } - - async preAction() { - const checkRes = await checkERC721( - this.address, - this.s1().address, - this.tokenId, - this.s1().tokens[this.tokenData.keyname] - ); - this.setAmountErrorMessage(checkRes.msg); - if (!checkRes.approved) this.setActiveStep(0); - } -} \ No newline at end of file diff --git a/src/core/actions/eth.ts b/src/core/actions/eth.ts deleted file mode 100644 index 1832718..0000000 --- a/src/core/actions/eth.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file eth.ts - * @copyright SKALE Labs 2022-Present - */ - - -import debug from 'debug'; - -import { externalEvents } from '../events'; -import { toWei } from '../convertation'; -import { TransferAction, Action } from './action'; -import { checkEthBalance } from './checks'; - - -debug.enable('*'); -const log = debug('metaport:actions:eth'); - - -export class TransferEthM2S extends TransferAction { - async execute() { - log('TransferEthM2S: started'); - this.updateState('transferETH'); - const amountWei = toWei(this.amount, this.tokenData.decimals); - const sChainBalanceBefore = await this.sChain2.ethBalance(this.address); - const tx = await this.mainnet.eth.deposit( - this.chainName2, - { - address: this.address, - value: amountWei - } - ); - this.updateState('transferETHDone'); - const block = await this.mainnet.web3.eth.getBlock(tx.blockNumber); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'deposit'); - await this.sChain2.waitETHBalanceChange(this.address, sChainBalanceBefore); - this.updateState('receivedETH'); - externalEvents.transferComplete( - tx, this.chainName1, this.chainName2, this.tokenData.keyname); - } - - async preAction() { - const checkResBalance = await checkEthBalance( - this.mainnet, - this.address, - this.amount, - this.tokenData - ); - if (!checkResBalance.res) this.setAmountErrorMessage(checkResBalance.msg); - } -} - - -export class TransferEthS2M extends TransferAction { - async execute() { - log('TransferEthS2M: started'); - this.updateState('transferETH'); - const amountWei = toWei(this.amount, this.tokenData.decimals); - const lockedETHAmount = await this.mainnet.eth.lockedETHAmount(this.address); - const tx = await this.sChain1.eth.withdraw( - amountWei, - { address: this.address } - ); - this.updateState('transferETHDone'); - const block = await this.sChain1.web3.eth.getBlock(tx.blockNumber); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'withdraw'); - await this.mainnet.eth.waitLockedETHAmountChange(this.address, lockedETHAmount); - this.updateState('receivedETH'); - externalEvents.transferComplete( - tx, this.chainName1, this.chainName2, this.tokenData.keyname); - } - - async preAction() { - const checkResBalance = await checkEthBalance( - this.sChain1, - this.address, - this.amount, - this.tokenData - ); - if (!checkResBalance.res) this.setAmountErrorMessage(checkResBalance.msg); - } -} - - -export class UnlockEthM extends Action { - static label = 'Unlock ETH' - static buttonText = 'Unlock' - static loadingText = 'Unlocking' - - async execute() { - log('UnlockEthM: started'); - this.updateState('switch'); - await this.switchMetamaskChain(false); - this.updateState('unlock'); - const tx = await this.mainnet.eth.getMyEth( - { address: this.address } - ); - const block = await this.mainnet.web3.eth.getBlock(tx.blockNumber); - externalEvents.transactionCompleted(tx, block.timestamp, 'mainnet', 'getMyEth'); - this.updateState('unlockDone'); - externalEvents.ethUnlocked(tx); - } -} - diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index 7fd1742..875cc23 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -22,42 +22,25 @@ */ import debug from 'debug'; -import { - TransferEthM2S, - TransferEthS2M, - UnlockEthM -} from './eth'; + import { TransferERC20S2S, WrapERC20S, UnWrapERC20S, - UnWrapERC20S2S, TransferERC20M2S, - TransferERC20S2M, - WrapSFuelERC20S + TransferERC20S2M } from './erc20'; -import { - TransferERC721M2S, - TransferERC721S2M, - TransferERC721S2S -} from './erc721'; -import { - TransferERC1155M2S, - TransferERC1155S2M, - TransferERC1155S2S -} from './erc1155'; + +import { Action } from './action'; import { isMainnet } from '../helper'; -import TokenData from '../dataclasses/TokenData'; +import { ActionType, TokenType } from '../dataclasses'; import { S2S_POSTFIX, M2S_POSTFIX, S2M_POSTFIX, } from '../constants'; -import { View } from '../../core/dataclasses/View'; -import { TokenType } from 'core/dataclasses'; - debug.enable('*'); const log = debug('metaport:actions'); @@ -66,10 +49,8 @@ const log = debug('metaport:actions'); export function getActionName( chainName1: string, chainName2: string, - tokenType: TokenType, - view: View + tokenType: TokenType ): string { - if (chainName1 && view === View.UNWRAP) return 'erc20_unwrap'; if (!chainName1 || !chainName2 || !tokenType) return; log(`Getting action name: ${chainName1} ${chainName2} ${tokenType}`); let postfix = S2S_POSTFIX; @@ -81,54 +62,27 @@ export function getActionName( } -const wrapActions = [WrapERC20S]; -const unwrapActions = [UnWrapERC20S2S]; -const sFuelWrapActions = [WrapSFuelERC20S]; +export const ACTIONS: { [actionType in ActionType]: typeof Action; } = { + // eth_m2s: [TransferEthM2S], + // eth_s2m: [TransferEthS2M, UnlockEthM], + // eth_s2s: [], + wrap: WrapERC20S, + unwrap: UnWrapERC20S, -export const ACTIONS = { - eth_m2s: [TransferEthM2S], - eth_s2m: [TransferEthS2M, UnlockEthM], - eth_s2s: [], + erc20_m2s: TransferERC20M2S, + erc20_s2m: TransferERC20S2M, + erc20_s2s: TransferERC20S2S, - erc20_m2s: [TransferERC20M2S], - erc20_s2m: [TransferERC20S2M], - erc20_s2s: [TransferERC20S2S], + // erc721_m2s: [TransferERC721M2S], + // erc721_s2m: [TransferERC721S2M], + // erc721_s2s: [TransferERC721S2S], - erc20_unwrap: [UnWrapERC20S], + // erc721meta_m2s: [TransferERC721M2S], + // erc721meta_s2m: [TransferERC721S2M], + // erc721meta_s2s: [TransferERC721S2S], - erc721_m2s: [TransferERC721M2S], - erc721_s2m: [TransferERC721S2M], - erc721_s2s: [TransferERC721S2S], - - erc721meta_m2s: [TransferERC721M2S], - erc721meta_s2m: [TransferERC721S2M], - erc721meta_s2s: [TransferERC721S2S], - - erc1155_m2s: [TransferERC1155M2S], - erc1155_s2m: [TransferERC1155S2M], - erc1155_s2s: [TransferERC1155S2S] -} - - -export function getActionSteps( - actionName: string, - tokenData: TokenData -) { - log(`Getting action steps ${actionName}, ${tokenData.keyname}`); - const actionsList = []; - // TODO: tmp fix - if (tokenData.unwrappedSymbol && !tokenData.clone && actionName !== 'erc20_unwrap') { - actionsList.push(...wrapActions); - } - if (tokenData.wrapsSFuel && !tokenData.clone && actionName !== 'erc20_unwrap') { - actionsList.push(...sFuelWrapActions); - } - actionsList.push(...ACTIONS[actionName]); - if (tokenData.unwrappedSymbol && tokenData.clone) { - actionsList.push(...unwrapActions); - } - log('actionsList'); - log(actionsList); - return actionsList; -} + // erc1155_m2s: [TransferERC1155M2S], + // erc1155_s2m: [TransferERC1155S2M], + // erc1155_s2s: [TransferERC1155S2S] +} \ No newline at end of file diff --git a/src/core/chain_id.ts b/src/core/chain_id.ts new file mode 100644 index 0000000..7d3cf08 --- /dev/null +++ b/src/core/chain_id.ts @@ -0,0 +1,48 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file chain_id.ts + * @copyright SKALE Labs 2023-Present + */ + +import { ethers } from 'ethers'; + + +export function remove0x(s: any) { + if (!s.startsWith('0x')) return s; + return s.slice(2); +} + + +function calcChainId(chainName: string): number { + let h = ethers.solidityPackedKeccak256(['string'], [chainName]); + // let h = soliditySha3(sChainName); + h = remove0x(h).toLowerCase(); + while (h.length < 64) + h = "0" + h; + h = h.substr(0, 13); + h = h.replace(/^0+/, ''); + return ethers.getNumber("0x" + h); +} + + +export function getChainId(chainName: string): number { + // if (chainName === MAINNET_CHAIN_NAME) return CHAIN_IDS[network]; + return calcChainId(chainName); +} diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index 5745fa7..958d5b2 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -22,84 +22,84 @@ */ -import debug from 'debug'; -import { MainnetChain, SChain } from '@skalenetwork/ima-js'; +// import debug from 'debug'; +// import { MainnetChain, SChain } from '@skalenetwork/ima-js'; -import { CommunityPoolData } from './interfaces'; -import { fromWei } from './convertation'; -import { - MAINNET_CHAIN_NAME, - DEFAULT_ERC20_DECIMALS, - RECHARGE_MULTIPLIER, - MINIMUM_RECHARGE_AMOUNT -} from './constants'; +// import { CommunityPoolData } from './interfaces'; +// import { fromWei } from './convertation'; +// import { +// MAINNET_CHAIN_NAME, +// DEFAULT_ERC20_DECIMALS, +// RECHARGE_MULTIPLIER, +// MINIMUM_RECHARGE_AMOUNT +// } from './constants'; -debug.enable('*'); -const log = debug('metaport:core:community_pool'); +// debug.enable('*'); +// const log = debug('metaport:core:community_pool'); -export function getEmptyCommunityPoolData(): CommunityPoolData { - return { - exitGasOk: null, - isActive: null, - balance: null, - accountBalance: null, - recommendedRechargeAmount: null, - originalRecommendedRechargeAmount: null - }; -} +// export function getEmptyCommunityPoolData(): CommunityPoolData { +// return { +// exitGasOk: null, +// isActive: null, +// balance: null, +// accountBalance: null, +// recommendedRechargeAmount: null, +// originalRecommendedRechargeAmount: null +// }; +// } -export async function getCommunityPoolData( - address: string, - chainName1: string, - chainName2: string, - mainnet: MainnetChain, - sChain: SChain -): Promise { +// export async function getCommunityPoolData( +// address: string, +// chainName1: string, +// chainName2: string, +// mainnet: MainnetChain, +// sChain: SChain +// ): Promise { - if (chainName2 !== MAINNET_CHAIN_NAME) { - log('not a S2M transfer, skipping community pool check'); - return { - exitGasOk: true, - isActive: null, - balance: null, - accountBalance: null, - recommendedRechargeAmount: null, - originalRecommendedRechargeAmount: null - } - } +// if (chainName2 !== MAINNET_CHAIN_NAME) { +// log('not a S2M transfer, skipping community pool check'); +// return { +// exitGasOk: true, +// isActive: null, +// balance: null, +// accountBalance: null, +// recommendedRechargeAmount: null, +// originalRecommendedRechargeAmount: null +// } +// } - log('Getting community pool data', address, chainName1); - const balanceWei = await mainnet.communityPool.balance(address, chainName1); - const accountBalanceWei = await mainnet.ethBalance(address); - const activeS = await sChain.communityLocker.contract.methods.activeUsers( - address - ).call(); - const chainHash = mainnet.web3.utils.soliditySha3(chainName1); - const activeM = await mainnet.communityPool.contract.methods.activeUsers( - address, - chainHash - ).call(); +// log('Getting community pool data', address, chainName1); +// const balanceWei = await mainnet.communityPool.balance(address, chainName1); +// const accountBalanceWei = await mainnet.ethBalance(address); +// const activeS = await sChain.communityLocker.contract.activeUsers( +// address +// ) +// const chainHash = mainnet.web3.utils.soliditySha3(chainName1); +// const activeM = await mainnet.communityPool.contract.activeUsers( +// address, +// chainHash +// ) - const rraWei = await mainnet.communityPool.contract.methods.getRecommendedRechargeAmount( - mainnet.web3.utils.soliditySha3(chainName1), - address - ).call(); - const rraEther = fromWei(rraWei as string, DEFAULT_ERC20_DECIMALS); +// const rraWei = await mainnet.communityPool.contract.getRecommendedRechargeAmount( +// mainnet.web3.utils.soliditySha3(chainName1), +// address +// ) +// const rraEther = fromWei(rraWei as string, DEFAULT_ERC20_DECIMALS); - let recommendedAmount = parseFloat(rraEther as string) * RECHARGE_MULTIPLIER; - if (recommendedAmount < MINIMUM_RECHARGE_AMOUNT) recommendedAmount = MINIMUM_RECHARGE_AMOUNT; +// let recommendedAmount = parseFloat(rraEther as string) * RECHARGE_MULTIPLIER; +// if (recommendedAmount < MINIMUM_RECHARGE_AMOUNT) recommendedAmount = MINIMUM_RECHARGE_AMOUNT; - const communityPoolData = { - exitGasOk: activeM && activeS && rraWei === '0', - isActive: activeM && activeS, - balance: balanceWei, - accountBalance: accountBalanceWei, - recommendedRechargeAmount: recommendedAmount.toString(), - originalRecommendedRechargeAmount: rraWei - } - log('communityPoolData:', communityPoolData); - return communityPoolData; -} \ No newline at end of file +// const communityPoolData = { +// exitGasOk: activeM && activeS && rraWei === '0', +// isActive: activeM && activeS, +// balance: balanceWei, +// accountBalance: accountBalanceWei, +// recommendedRechargeAmount: recommendedAmount.toString(), +// originalRecommendedRechargeAmount: rraWei +// } +// log('communityPoolData:', communityPoolData); +// return communityPoolData; +// } \ No newline at end of file diff --git a/src/core/constants.ts b/src/core/constants.ts index 89d3d89..80c77e8 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -47,13 +47,13 @@ export const HTTPS_PREFIX = 'https://'; export const MAINNET_EXPLORER_URLS: { [skaleNetwork: string]: string } = { mainnet: 'https://etherscan.io', - staging3: 'https://goerli.etherscan.io/', + staging: 'https://goerli.etherscan.io/', legacy: 'https://goerli.etherscan.io/' }; export const BASE_EXPLORER_URLS = { mainnet: "explorer.mainnet.skalenodes.com", - staging3: "explorer.staging-v3.skalenodes.com", + staging: "explorer.staging-v3.skalenodes.com", legacy: "explorer.staging-v3.skalenodes.com" }; @@ -100,4 +100,6 @@ export const COMMUNITY_POOL_WITHDRAW_GAS_LIMIT = '1500000'; export const BALANCE_UPDATE_INTERVAL_SECONDS = 10; -export const SFUEL_RESERVE_AMOUNT = 0.02; \ No newline at end of file +export const SFUEL_RESERVE_AMOUNT = 0.02; + +export const SUCCESS_EMOJIS = ['🎉', '👌', '✅', '🙌', '🎊']; \ No newline at end of file diff --git a/src/core/contracts.ts b/src/core/contracts.ts new file mode 100644 index 0000000..315400d --- /dev/null +++ b/src/core/contracts.ts @@ -0,0 +1,59 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file contracts.ts + * @copyright SKALE Labs 2023-Present + */ + +import { CustomAbiTokenType, TokenType } from './dataclasses'; + +import erc20Abi from '../metadata/erc20_abi.json'; +import erc721Abi from '../metadata/erc721_abi.json'; +import erc721MetaAbi from '../metadata/erc721meta_abi.json'; +import erc1155Abi from '../metadata/erc1155_abi.json'; +import erc20WrapperAbi from '../metadata/erc20_wrapper_abi.json'; +import sFuelWrapperAbi from '../metadata/sfuel_wrapper_abi.json'; + +import mainnetAddresses from '../metadata/addresses/mainnet.json'; +import stagingAddresses from '../metadata/addresses/staging.json'; +import legacyAddresses from '../metadata/addresses/legacy.json'; + +import sChainAbi from '../metadata/schainAbi.json'; +import mainnetAbi from '../metadata/mainnetAbi.json'; + +export const ERC_ABIS: { [tokenType in CustomAbiTokenType | TokenType]: { ['abi']: any } } = { + eth: null, + erc20: erc20Abi, + erc20wrap: erc20WrapperAbi, + sfuelwrap: sFuelWrapperAbi, + erc721: erc721Abi, + erc721meta: erc721MetaAbi, + erc1155: erc1155Abi +} + +export const IMA_ADDRESSES = { + mainnet: mainnetAddresses, + staging: stagingAddresses, + legacy: legacyAddresses +} + +export const IMA_ABIS = { + mainnet: mainnetAbi, + schain: sChainAbi +} \ No newline at end of file diff --git a/src/core/convertation.ts b/src/core/convertation.ts index 70d7a67..81ca100 100644 --- a/src/core/convertation.ts +++ b/src/core/convertation.ts @@ -21,18 +21,14 @@ * @copyright SKALE Labs 2022-Present */ -import { Unit, toWei as _toWei, fromWei as _fromWei, unitMap, toBN } from 'web3-utils'; +import { formatUnits, parseUnits, BigNumberish } from 'ethers'; -export function toWei(value: string, decimals: string): string { - return _toWei(value, decimalsToUnit(decimals)); +export function toWei(value: string, decimals: string): bigint { + return parseUnits(value, parseInt(decimals as string)); } -export function fromWei(value: string, decimals: string): string { - return _fromWei(value, decimalsToUnit(decimals)); -} -function decimalsToUnit(decimals: string): Unit { - return Object.keys(unitMap).find( - key => unitMap[key] === toBN(10).pow(toBN(decimals)).toString()) as Unit; +export function fromWei(value: BigNumberish, decimals: string): string { + return formatUnits(value, parseInt(decimals as string)); } \ No newline at end of file diff --git a/src/core/core.ts b/src/core/core.ts deleted file mode 100644 index a8e72b7..0000000 --- a/src/core/core.ts +++ /dev/null @@ -1,225 +0,0 @@ -import Web3 from 'web3'; -import { soliditySha3, AbiItem } from 'web3-utils'; - -import debug from 'debug'; - -import { SChain, MainnetChain } from '@skalenetwork/ima-js'; - -import sChainAbi from '../metadata/schainAbi.json'; -import mainnetAbi from '../metadata/mainnetAbi.json'; -import proxyEndpoints from '../metadata/proxy.json'; -import { - schainNetworkParams, - mainnetNetworkParams, - changeMetamaskNetwork, - CHAIN_IDS -} from '../components/WalletConnector'; - - -import erc20Abi from '../metadata/erc20_abi.json'; -import erc721Abi from '../metadata/erc721_abi.json'; -import erc721MetaAbi from '../metadata/erc721meta_abi.json'; -import erc1155Abi from '../metadata/erc1155_abi.json'; -import erc20WrapperAbi from '../metadata/erc20_wrapper_abi.json'; -import sFuelWrapperAbi from '../metadata/sfuel_wrapper_abi.json'; - -import mainnetAddresses from '../metadata/addresses/mainnet.json'; -import stagingAddresses from '../metadata/addresses/staging.json'; -import staging3Addresses from '../metadata/addresses/staging3.json'; -import legacyAddresses from '../metadata/addresses/legacy.json'; - -import { getChainName } from './helper'; -import { MAINNET_CHAIN_NAME } from './constants'; -import { MetaportConfig } from './interfaces'; - - -const ERC_ABIS = { - 'erc20': erc20Abi, - 'erc20wrap': erc20WrapperAbi, - 'sfuelwrap': sFuelWrapperAbi, - 'erc721': erc721Abi, - 'erc721meta': erc721MetaAbi, - 'erc1155': erc1155Abi -} - - -debug.enable('*'); -const log = debug('metaport:core:core'); - - -export function initContract(tokenType: string, tokenAddress: string, web3: Web3) { - return new web3.eth.Contract(ERC_ABIS[tokenType].abi as AbiItem[], tokenAddress); -} - - -export function initERC20(tokenAddress: string, web3: Web3) { - return new web3.eth.Contract(erc20Abi.abi as AbiItem[], tokenAddress); -} - - -export function initERC20Wrapper(tokenAddress: string, web3: Web3) { - return new web3.eth.Contract(erc20WrapperAbi.abi as AbiItem[], tokenAddress); -} - - -export function initSChain(network: string, schainName: string) { - const endpoint = getSChainEndpoint(network, schainName); - const sChainWeb3 = new Web3(endpoint); - return new SChain(sChainWeb3, sChainAbi); -} - - -export async function switchMetamaskNetwork( // TODO: use new function - network: string, - chainName: string, - mainnetEndpoint: string, - chainsMetadata: any -) { - if (chainName === MAINNET_CHAIN_NAME) { - return await initMainnetMetamask(network, mainnetEndpoint); - } else { - return await initSChainMetamask(network, chainName, chainsMetadata); - } -} - - -export function getChainId(network: string, chainName: string): string { // TODO: use new function - if (chainName === MAINNET_CHAIN_NAME) return CHAIN_IDS[network]; - return calcChainId(chainName); -} - - -export async function initSChainMetamask(network: string, schainName: string, chainsMetadata: any) { - const endpoint = getSChainEndpoint(network, schainName); - const chainId = calcChainId(schainName); - const chainName = getChainName(chainsMetadata, schainName, network); - const networkParams = schainNetworkParams(chainName, endpoint, chainId); - await changeMetamaskNetwork(networkParams); - const sChainWeb3 = new Web3(window.ethereum); - return new SChain(sChainWeb3, sChainAbi); -} - -export function updateWeb3SChain(schain: SChain, network: string, schainName: string) { - const endpoint = getSChainEndpoint(network, schainName); - const sChainWeb3 = new Web3(endpoint); - schain.updateWeb3(sChainWeb3); -} - -export async function updateWeb3SChainMetamask( - schain: SChain, - network: string, - schainName: string, - chainsMetadata: any -): Promise { - const endpoint = getSChainEndpoint(network, schainName); - const chainId = calcChainId(schainName); - const chainName = getChainName(chainsMetadata, schainName, network); - const networkParams = schainNetworkParams(chainName, endpoint, chainId); - await changeMetamaskNetwork(networkParams); - const sChainWeb3 = new Web3(window.ethereum); - schain.updateWeb3(sChainWeb3); -} - - -export function updateWeb3Mainnet(mainnet: MainnetChain, mainnetEndpoint: string) { - const web3 = new Web3(mainnetEndpoint); - mainnet.updateWeb3(web3); -} - - -export async function updateWeb3MainnetMetamask( - mainnet: MainnetChain, - network: string, - mainnetEndpoint: string -): Promise { - const networkParams = mainnetNetworkParams(network, mainnetEndpoint); - await changeMetamaskNetwork(networkParams); - const web3 = new Web3(window.ethereum); - mainnet.updateWeb3(web3); -} - - -function getMainnetAbi(network: string) { - if (network === 'staging') { - return { ...mainnetAbi, ...stagingAddresses } - } - if (network === 'staging3') { - return { ...mainnetAbi, ...staging3Addresses } - } - if (network === 'legacy') { - return { ...mainnetAbi, ...legacyAddresses } - } - return { ...mainnetAbi, ...mainnetAddresses } -} - - -export function initMainnet(network: string, mainnetEndpoint: string): MainnetChain { - const web3 = new Web3(mainnetEndpoint); - return new MainnetChain(web3, getMainnetAbi(network)); -} - - -export async function initMainnetMetamask( - network: string, - mainnetEndpoint: string -): Promise { - const networkParams = mainnetNetworkParams(network, mainnetEndpoint); - await changeMetamaskNetwork(networkParams); - const web3 = new Web3(window.ethereum); - return new MainnetChain(web3, getMainnetAbi(network)); -} - - -function getSChainEndpoint(network: string, sChainName: string): string { - return getProxyEndpoint(network) + '/v1/' + sChainName; -} - - -function getProxyEndpoint(network: string) { - // todo: add network validation - return proxyEndpoints[network]; -} - - -function calcChainId(sChainName) { - let h = soliditySha3(sChainName); - h = remove0x(h).toLowerCase(); - while (h.length < 64) - h = "0" + h; - h = h.substr(0, 13); - h = h.replace(/^0+/, ''); - return "0x" + h; -} - - -export function remove0x(s: any) { - if (!s.startsWith('0x')) return s; - return s.slice(2); -} - - -// - -export function initChainWeb3(config: MetaportConfig, chainName: string): Web3 { - log(`Initializing web3 instance for ${chainName}`); - const endpoint = getChainEndpoint(chainName, config.mainnetEndpoint, config.skaleNetwork); - return initWeb3(endpoint); -} - - -export function initWeb3(endpoint: string) { - const provider = new Web3.providers.HttpProvider(endpoint); - return new Web3(provider); -} - - -export function getChainEndpoint( - chainName: string, - mainnetEndpoint: string, - skaleNetwork: string -): string { - if (chainName === MAINNET_CHAIN_NAME) { - return mainnetEndpoint; - } - return getProxyEndpoint(skaleNetwork) + '/v1/' + chainName; -} \ No newline at end of file diff --git a/src/core/dataclasses/ErrorMessage.ts b/src/core/dataclasses/ErrorMessage.ts new file mode 100644 index 0000000..3ecaff5 --- /dev/null +++ b/src/core/dataclasses/ErrorMessage.ts @@ -0,0 +1,74 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file ErrorMessage.ts + * @copyright SKALE Labs 2022-Present + */ + + + +export class ErrorMessage { + + icon: string + text: string + btnText?: string + fallback?: Function + + constructor(fallback?: Function) { + this.fallback = fallback + } +} + + +export class NoTokenPairsMessage extends ErrorMessage { + constructor() { + super() + this.icon = 'link-off' + this.text = 'No token pairs for these chains' + } +} + + +export class WrongNetworkMessage extends ErrorMessage { + constructor(fallback: Function) { + super(fallback) + this.icon = 'public-off' + this.text = 'Looks like you are connected to the wrong network' + this.btnText = 'Switch network' + } +} + + +export class TransactionErrorMessage extends ErrorMessage { + constructor(text: string, fallback: Function) { + super(fallback) + this.icon = 'sentiment' + this.text = text + this.btnText = 'Try again' + } +} + + +export class CustomErrorMessage extends ErrorMessage { + constructor(text: string) { + super(undefined) + this.icon = 'error' + this.text = text + } +} diff --git a/src/core/dataclasses/EthTokenData.ts b/src/core/dataclasses/EthTokenData.ts index 664bff3..0b85bde 100644 --- a/src/core/dataclasses/EthTokenData.ts +++ b/src/core/dataclasses/EthTokenData.ts @@ -21,26 +21,26 @@ * @copyright SKALE Labs 2022-Present */ -import { ETH_ERC20_ADDRESS } from '../constants'; -import { TokenType } from './TokenType'; -import TokenData from './TokenData'; +// import { ETH_ERC20_ADDRESS } from '../constants'; +// import { TokenType } from './TokenType'; +import { TokenData } from './TokenData'; export default class EthTokenData extends TokenData { - constructor(clone: boolean) { - super( - ETH_ERC20_ADDRESS, - null, - TokenType.eth, - TokenType.eth, - TokenType.eth, - clone, - null, - null, - TokenType.eth, - null, - null, - null - ); - } + // constructor(clone: boolean) { + // super( + // ETH_ERC20_ADDRESS, + // null, + // TokenType.eth, + // TokenType.eth, + // TokenType.eth, + // clone, + // null, + // null, + // TokenType.eth, + // null, + // null, + // null + // ); + // } } \ No newline at end of file diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts new file mode 100644 index 0000000..641cb3a --- /dev/null +++ b/src/core/dataclasses/StepMetadata.ts @@ -0,0 +1,107 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file StepMetadata.ts + * @copyright SKALE Labs 2022-Present + */ + +import debug from 'debug'; + +import { TokenType } from './TokenType'; +import { isMainnet } from '../helper'; +import { + S2S_POSTFIX, + M2S_POSTFIX, + S2M_POSTFIX, +} from '../constants'; + + +debug.enable('*'); +const log = debug('metaport:actions'); + + +export enum ActionType { + erc20_m2s = 'erc20_m2s', + erc20_s2m = 'erc20_s2m', + erc20_s2s = 'erc20_s2s', + wrap = 'wrap', + unwrap = 'unwrap' +} + + +export function getActionType( + chainName1: string, + chainName2: string, + tokenType: TokenType +): ActionType { + if (!chainName1 || !chainName2 || !tokenType) return; + let postfix = S2S_POSTFIX; + if (isMainnet(chainName1)) { postfix = M2S_POSTFIX; }; + if (isMainnet(chainName2)) { postfix = S2M_POSTFIX; }; + const actionName = tokenType + '_' + postfix; + log('Action name: ' + actionName); + return actionName as ActionType; +} + + +export abstract class StepMetadata { + headline: string = ''; + text: string = ''; + btnText: string = ''; + btnLoadingText: string = ''; + + onSource: boolean = true; + + constructor(public type: ActionType, public from: string, public to: string) { } +} + + +export class TransferStepMetadata extends StepMetadata { + headline: string = 'Transfer to'; + text: string = 'You may need to approve first.'; + btnText: string = 'Transfer'; + btnLoadingText: string = 'Transferring'; + + onSource: boolean = false; +} + + +export class WrapStepMetadata extends StepMetadata { + headline: string = 'Wrap on'; + text: string = 'Tokens should be wrapped before transferring. Approval may be required.'; + btnText: string = 'Wrap'; + btnLoadingText: string = 'Wrapping'; + + constructor(public from: string, public to: string) { + super(ActionType.wrap, from, to) + } +} + + +export class UnwrapStepMetadata extends StepMetadata { + headline: string = 'Unwrap on'; + text: string = 'Tokens should be unwrapped after transferring.'; + btnText: string = 'Unwrap'; + btnLoadingText: string = 'Unwrapping'; + onSource: boolean = false; + + constructor(public from: string, public to: string) { + super(ActionType.unwrap, from, to) + } +} \ No newline at end of file diff --git a/src/core/dataclasses/TokenData.ts b/src/core/dataclasses/TokenData.ts index 27a7cc5..4408b47 100644 --- a/src/core/dataclasses/TokenData.ts +++ b/src/core/dataclasses/TokenData.ts @@ -22,68 +22,44 @@ */ import { DEFAULT_ERC20_DECIMALS } from '../constants'; +import { TokenMetadata, ConnectedChainMap } from '../interfaces'; import { TokenType } from './TokenType'; -export default class TokenData { - originAddress: string - cloneAddress: string - cloneSymbol: string - - name: string - symbol: string - keyname: string - - clone: boolean - type: TokenType - - balance: string - - iconUrl: string - decimals: string - - unwrappedSymbol: string - unwrappedAddress: string - unwrappedIconUrl: string - unwrappedBalance: string - - wrapsSFuel: boolean +export class TokenData { + address: string; + keyname: string; + type: TokenType; + meta: TokenMetadata; + connections: ConnectedChainMap; + chain: string; constructor( - cloneAddress: string, - originAddress: string, - name: string, - symbol: string, - cloneSymbol: string, - clone: boolean, - iconUrl: string, - decimals: string, + address: string, type: TokenType, - unwrappedSymbol: string, - unwrappedAddress: string, - unwrappedIconUrl: string, - wrapsSFuel: boolean = false + tokenKeyname: string, + metadata: TokenMetadata, + connections: ConnectedChainMap, + chain: string ) { - this.cloneAddress = cloneAddress; - this.cloneSymbol = cloneSymbol ? cloneSymbol : symbol; - this.originAddress = originAddress; - this.name = name; - this.symbol = symbol; - this.clone = clone; - this.iconUrl = iconUrl; - this.decimals = decimals ? decimals : DEFAULT_ERC20_DECIMALS; + this.address = address; + this.meta = metadata; + this.meta.decimals = this.meta.decimals ? this.meta.decimals : DEFAULT_ERC20_DECIMALS; + this.connections = connections; this.type = type; + this.keyname = tokenKeyname; + this.chain = chain; + } - this.keyname = getTokenKeyname(symbol, originAddress); - - this.unwrappedSymbol = unwrappedSymbol; - this.unwrappedAddress = unwrappedAddress; - this.unwrappedIconUrl = unwrappedIconUrl; - this.wrapsSFuel = wrapsSFuel; + wrapper(destChain: string): string | undefined { + return this.connections[destChain].wrapper } -} + isClone(destChain: string): boolean | undefined { + return this.connections[destChain].clone + } -export function getTokenKeyname(symbol: string, originAddress: string): string { - return `_${symbol}_${originAddress}`; -} + wrapsSFuel(destChain: string): boolean | undefined { + return this.connections[destChain].wrapsSFuel + } +} \ No newline at end of file diff --git a/src/core/dataclasses/TokenType.ts b/src/core/dataclasses/TokenType.ts index 9920285..31acdb5 100644 --- a/src/core/dataclasses/TokenType.ts +++ b/src/core/dataclasses/TokenType.ts @@ -28,4 +28,10 @@ export enum TokenType { erc721 = 'erc721', erc721meta = 'erc721meta', erc1155 = 'erc1155' +} + + +export enum CustomAbiTokenType { + erc20wrap = 'erc20wrap', + sfuelwrap = 'sfuelwrap' } \ No newline at end of file diff --git a/src/core/dataclasses/index.ts b/src/core/dataclasses/index.ts index 774734c..16b8139 100644 --- a/src/core/dataclasses/index.ts +++ b/src/core/dataclasses/index.ts @@ -22,5 +22,8 @@ */ export * from "./TokenType"; +export * from "./TokenData"; export * from "./Position"; export * from "./TransferRequestStatus"; +export * from "./StepMetadata"; +export * from "./ErrorMessage"; diff --git a/src/core/ethers.ts b/src/core/ethers.ts new file mode 100644 index 0000000..8460931 --- /dev/null +++ b/src/core/ethers.ts @@ -0,0 +1,47 @@ +import * as React from 'react' +import { type WalletClient, useWalletClient } from 'wagmi' +import { BrowserProvider, JsonRpcSigner, Eip1193Provider } from 'ethers' + +import { type PublicClient, usePublicClient } from 'wagmi' +import { FallbackProvider, JsonRpcProvider } from 'ethers' +import { type HttpTransport } from 'viem' + + +export function walletClientToSigner(walletClient: WalletClient) { + const { account, transport } = walletClient + const provider = new BrowserProvider(transport as Eip1193Provider, "any") + const signer = new JsonRpcSigner(provider, account.address) + return signer +} + +/** Hook to convert a viem Wallet Client to an ethers.js Signer. */ +export function useEthersSigner({ chainId }: { chainId?: number } = {}) { + const { data: walletClient } = useWalletClient({ chainId }) + return React.useMemo( + () => (walletClient ? walletClientToSigner(walletClient) : undefined), + [walletClient], + ) +} + +export function publicClientToProvider(publicClient: PublicClient) { + const { chain, transport } = publicClient + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') { + const providers = (transport.transports as ReturnType[]).map( + ({ value }) => new JsonRpcProvider(value?.url, network), + ) + if (providers.length === 1) return providers[0] + return new FallbackProvider(providers) + } + return new JsonRpcProvider(transport.url, network) +} + +/** Hook to convert a viem Public Client to an ethers.js Provider. */ +export function useEthersProvider({ chainId }: { chainId?: number } = {}) { + const publicClient = usePublicClient({ chainId }) + return React.useMemo(() => publicClientToProvider(publicClient), [publicClient]) +} \ No newline at end of file diff --git a/src/core/events.ts b/src/core/events.ts index f3a9920..eff409f 100644 --- a/src/core/events.ts +++ b/src/core/events.ts @@ -95,7 +95,7 @@ export namespace externalEvents { chainName2: string, address: string, amount: string, - amountWei: string, + amountWei: bigint, tokenId: number }, transactionHash?: string, diff --git a/src/core/explorer.ts b/src/core/explorer.ts index 711f291..7e663ce 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -27,6 +27,7 @@ import { MAINNET_EXPLORER_URLS, BASE_EXPLORER_URLS } from './constants'; +import { SkaleNetwork } from './interfaces'; function getMainnetExplorerUrl(skaleNetwork: string) { @@ -37,13 +38,12 @@ function getSChainExplorerUrl(skaleNetwork: string) { return BASE_EXPLORER_URLS[skaleNetwork]; } -export function getExplorerUrl(chainName: string, skaleNetwork: string): string { +export function getExplorerUrl(skaleNetwork: SkaleNetwork, chainName: string): string { if (chainName === MAINNET_CHAIN_NAME) return getMainnetExplorerUrl(skaleNetwork); return HTTPS_PREFIX + chainName + '.' + getSChainExplorerUrl(skaleNetwork); } - -export function getTxUrl(chainName: string, skaleNetwork: string, txHash: string): string { - const explorerUrl = getExplorerUrl(chainName, skaleNetwork); +export function getTxUrl(chainName: string, skaleNetwork: SkaleNetwork, txHash: string): string { + const explorerUrl = getExplorerUrl(skaleNetwork, chainName); return `${explorerUrl}/tx/${txHash}`; } diff --git a/src/core/faucet.ts b/src/core/faucet.ts index 6d7b30d..738a246 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -21,31 +21,33 @@ * @copyright SKALE Labs 2023-Present */ -import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants'; -import Web3 from 'web3'; - - -function getAddress(chainName: string, skaleNetwork: string) { - if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS; - const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; - return faucet[chainName].address; -} - -function getFunc(chainName: string, skaleNetwork: string) { - if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_FUNCSIG; - const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; - return faucet[chainName].func; -} - -export function isFaucetAvailable(chainName: string, skaleNetwork: string) { - if (!FAUCET_DATA[skaleNetwork]) return false; - const keys = Object.keys(FAUCET_DATA[skaleNetwork]); - return keys.includes(chainName); -} - -export function getFuncData(web3: Web3, chainName: string, address: string, skaleNetwork: string) { - const faucetAddress = getAddress(chainName, skaleNetwork); - const functionSig = getFunc(chainName, skaleNetwork); - const functionParam = web3.eth.abi.encodeParameter('address', address); - return { to: faucetAddress, data: functionSig + functionParam.slice(2) }; -} \ No newline at end of file + +// TODO! + +// import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants'; + + +// function getAddress(chainName: string, skaleNetwork: string) { +// if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS; +// const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; +// return faucet[chainName].address; +// } + +// function getFunc(chainName: string, skaleNetwork: string) { +// if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_FUNCSIG; +// const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; +// return faucet[chainName].func; +// } + +// export function isFaucetAvailable(chainName: string, skaleNetwork: string) { +// if (!FAUCET_DATA[skaleNetwork]) return false; +// const keys = Object.keys(FAUCET_DATA[skaleNetwork]); +// return keys.includes(chainName); +// } + +// export function getFuncData(web3: Web3, chainName: string, address: string, skaleNetwork: string) { +// const faucetAddress = getAddress(chainName, skaleNetwork); +// const functionSig = getFunc(chainName, skaleNetwork); +// const functionParam = web3.eth.abi.encodeParameter('address', address); +// return { to: faucetAddress, data: functionSig + functionParam.slice(2) }; +// } \ No newline at end of file diff --git a/src/core/helper.ts b/src/core/helper.ts index 602ea6f..9336f41 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -21,10 +21,12 @@ * @copyright SKALE Labs 2022-Present */ +import { getAddress } from 'ethers'; import { MAINNET_CHAIN_NAME } from './constants'; -import utils from 'web3-utils'; +// import utils from 'web3-utils'; import { TransferRequestStatus } from './dataclasses'; +import { SkaleNetwork } from './interfaces'; import mainnetMeta from '../meta/mainnet/chains.json'; import stagingMeta from '../meta/staging/chains.json'; @@ -33,12 +35,12 @@ import legacyMeta from '../meta/legacy/chains.json'; export const CHAINS_META = { 'mainnet': mainnetMeta, - 'staging3': stagingMeta, + 'staging': stagingMeta, 'legacy': legacyMeta } -export function clsNames(...args: any): string { +export function cls(...args: any): string { const filteredArgs = args.map((clsName: any) => { if (typeof clsName === 'string') return clsName; if (Array.isArray(clsName) && clsName.length === 2 && clsName[1]) return clsName[0]; @@ -58,11 +60,11 @@ export function isMainnet(chainName: string): boolean { export function addressesEqual(address1: string, address2: string): boolean { - return utils.toChecksumAddress(address1) === utils.toChecksumAddress(address2); + return getAddress(address1) === getAddress(address2); } -export function isTransferRequestActive(transferRequestStatus: TransferRequestStatus) { +export default function isTransferRequestActive(transferRequestStatus: TransferRequestStatus) { return transferRequestStatus === TransferRequestStatus.IN_PROGRESS || transferRequestStatus === TransferRequestStatus.IN_PROGRESS_HUB; } @@ -71,27 +73,15 @@ export function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } -export function getChainName( - chainsMetadata: any, - chainName: string, - skaleNetwork: string, - app?: string -): string { + +export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { if (chainName === MAINNET_CHAIN_NAME) { - return 'Mainnet'; - } - if (chainsMetadata && chainsMetadata[chainName]) { - if (app && chainsMetadata[chainName].apps[app]) { - return chainsMetadata[chainName].apps[app].alias; + if (skaleNetwork != MAINNET_CHAIN_NAME) { + const network = skaleNetwork === 'staging' ? 'Goerli' : skaleNetwork; + return `Ethereum (${network})`; } - return chainsMetadata[chainName].alias; - } else { - return getChainNameMeta(chainName, skaleNetwork, app); + return 'Ethereum'; } -} - - -function getChainNameMeta(chainName: string, skaleNetwork: string, app?: string): string { if (CHAINS_META[skaleNetwork] && CHAINS_META[skaleNetwork][chainName]) { if (app && CHAINS_META[skaleNetwork][chainName].apps && CHAINS_META[skaleNetwork][chainName].apps[app]) { @@ -102,8 +92,12 @@ function getChainNameMeta(chainName: string, skaleNetwork: string, app?: string) return chainName; } -export function getChainAppsMeta(chainName: string, skaleNetwork: string) { +export function getChainAppsMeta(chainName: string, skaleNetwork: SkaleNetwork) { if (CHAINS_META[skaleNetwork][chainName] && CHAINS_META[skaleNetwork][chainName].apps) { return CHAINS_META[skaleNetwork][chainName].apps; } +} + +export function getRandom(list: Array) { + return list[Math.floor((Math.random() * list.length))]; } \ No newline at end of file diff --git a/src/core/index.ts b/src/core/index.ts deleted file mode 100644 index 8d119de..0000000 --- a/src/core/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./core"; diff --git a/src/core/interfaces/Config.ts b/src/core/interfaces/Config.ts index fed8d46..fc784c4 100644 --- a/src/core/interfaces/Config.ts +++ b/src/core/interfaces/Config.ts @@ -21,10 +21,9 @@ * @copyright SKALE Labs 2022-Present */ -import { MetaportTheme } from './Theme'; -import { ChainsMetadataMap } from './ChainsMetadata'; -import { TokensMap } from './Tokens'; +import { TokenConnectionsMap, TokenMetadataMap, MetaportTheme } from '.'; +export type SkaleNetwork = 'mainnet' | 'staging' | 'legacy' | 'regression'; export interface MetaportConfig { openOnLoad?: boolean; @@ -32,12 +31,12 @@ export interface MetaportConfig { autoLookup?: boolean; debug?: boolean; - skaleNetwork?: string; + skaleNetwork: SkaleNetwork; mainnetEndpoint?: string; chains?: string[]; - chainsMetadata?: ChainsMetadataMap; - tokens?: TokensMap; + tokens: TokenMetadataMap; + connections: TokenConnectionsMap; theme?: MetaportTheme; } diff --git a/src/core/interfaces/TokenDataMap.ts b/src/core/interfaces/TokenDataMap.ts index 3abcb56..693b85a 100644 --- a/src/core/interfaces/TokenDataMap.ts +++ b/src/core/interfaces/TokenDataMap.ts @@ -21,9 +21,10 @@ * @copyright SKALE Labs 2022-Present */ -import TokenData from '../../core/dataclasses/TokenData'; +import { TokenData } from '../../core/dataclasses/TokenData'; import EthTokenData from '../../core/dataclasses/EthTokenData'; import { TokenType } from '../../core/dataclasses/TokenType'; +import { Contract } from "ethers"; export interface TokenDataMap { [tokenSymbol: string]: TokenData; } @@ -37,3 +38,6 @@ export type TokenDataTypesMap = { [TokenType.erc721meta]: TokenDataMap [TokenType.erc1155]: TokenDataMap } + +export interface TokenContractsMap { [tokenKeyname: string]: Contract; }; +export interface TokenBalancesMap { [tokenKeyname: string]: bigint; }; \ No newline at end of file diff --git a/src/core/interfaces/TokenMetadata.ts b/src/core/interfaces/TokenMetadata.ts new file mode 100644 index 0000000..f9cdb74 --- /dev/null +++ b/src/core/interfaces/TokenMetadata.ts @@ -0,0 +1,31 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file MetaportConfig.ts + * @copyright SKALE Labs 2022-Present + */ + +export interface TokenMetadata { + symbol: string, + name?: string, + iconUrl?: string, + decimals?: string +} + +export interface TokenMetadataMap { [tokenName: string]: TokenMetadata; } \ No newline at end of file diff --git a/src/core/interfaces/Tokens.ts b/src/core/interfaces/Tokens.ts index c8951f2..3695d2e 100644 --- a/src/core/interfaces/Tokens.ts +++ b/src/core/interfaces/Tokens.ts @@ -22,27 +22,23 @@ */ export interface EthToken { - chains: string[]; + chains: ConnectedChainMap } export interface Token { - symbol: string, - cloneSymbol?: string, - address: string, - name?: string, - iconUrl?: string, - decimals?: string, - wrapsSFuel?: boolean, - wraps?: WrapsData, + address?: string, + chains: ConnectedChainMap } -interface WrapsData { - symbol: string, - address: string, - iconUrl?: string +export interface ConnectedChain { + hub?: string + wrapper?: string + wrapsSFuel?: boolean + clone?: boolean } - +export interface ConnectedChainMap { [chainName: string]: ConnectedChain; } export interface ChainTokensMap { [tokenSymbol: string]: Token; } -export interface TokenTypeMap { [tokenType: string]: EthToken | ChainTokensMap; } -export interface TokensMap { [chainName: string]: TokenTypeMap; } +// export interface TokenTypeMap { [tokenType: string]: EthToken | ChainTokensMap; } +export interface TokenTypeMap { [tokenType: string]: ChainTokensMap; } +export interface TokenConnectionsMap { [chainName: string]: TokenTypeMap; } \ No newline at end of file diff --git a/src/core/interfaces/index.ts b/src/core/interfaces/index.ts index 2dcb5f1..945ebdf 100644 --- a/src/core/interfaces/index.ts +++ b/src/core/interfaces/index.ts @@ -30,3 +30,4 @@ export * from "./TransferParams"; export * from "./CheckRes"; export * from "./TransactionHistory"; export * from "./CommunityPoolData"; +export * from "./TokenMetadata"; diff --git a/src/components/TokenList/helper.ts b/src/core/metadata.ts similarity index 55% rename from src/components/TokenList/helper.ts rename to src/core/metadata.ts index 8904bfa..4a03908 100644 --- a/src/components/TokenList/helper.ts +++ b/src/core/metadata.ts @@ -17,24 +17,13 @@ */ /** - * @file helper.ts - * @copyright SKALE Labs 2022-Present + * @file metadata.ts + * @copyright SKALE Labs 2023-Present */ -import { MAINNET_CHAIN_NAME } from '../../core/constants'; -import TokenData from '../../core/dataclasses/TokenData'; - - -export function getTokenSymbol(token: TokenData): string { - let symbol = token.unwrappedSymbol ? token.unwrappedSymbol : token.symbol; - if (token.clone) symbol = token.cloneSymbol ? token.cloneSymbol : symbol; - return symbol; -} - - -export function getTokenName(token: TokenData): string { - return token.name ? token.name : getTokenSymbol(token); -} +import { TokenData } from './dataclasses'; +import { SkaleNetwork } from './interfaces'; +import { MAINNET_CHAIN_NAME } from './constants'; function importAll(r) { @@ -43,26 +32,16 @@ function importAll(r) { return images; } -const icons = importAll(require.context('../../icons', false, /\.(png|jpe?g|svg)$/)); -const CHAIN_ICONS = { - 'mainnet': importAll(require.context('../../meta/mainnet/icons', false, /\.(png|jpe?g|svg)$/)), - 'staging3': importAll(require.context('../../meta/staging/icons', false, /\.(png|jpe?g|svg)$/)), - 'legacy': importAll(require.context('../../meta/legacy/icons', false, /\.(png|jpe?g|svg)$/)) -} - -export function iconPath(name) { - if (!name) return; - const key = name.toLowerCase() + '.svg'; - if (icons[key]) { - return icons[key]; - } else { - return icons['eth.svg']; - } +const icons = importAll(require.context('../icons', false, /\.(png|jpe?g|svg)$/)); +const CHAIN_ICONS = { + 'mainnet': importAll(require.context('../meta/mainnet/icons', false, /\.(png|jpe?g|svg)$/)), + 'staging': importAll(require.context('../meta/staging/icons', false, /\.(png|jpe?g|svg)$/)), + 'legacy': importAll(require.context('../meta/legacy/icons', false, /\.(png|jpe?g|svg)$/)) } -export function chainIconPath(skaleNetwork: string, name: string, app?: string) { +export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: string) { if (!name) return; let filename = name.toLowerCase(); if (app) @@ -77,7 +56,22 @@ export function chainIconPath(skaleNetwork: string, name: string, app?: string) } -export function getIconSrc(token: TokenData): string { - if (token.unwrappedIconUrl) return token.unwrappedIconUrl; - return token.iconUrl ? token.iconUrl : iconPath(token.symbol); +export function tokenIcon(name: string): string { + if (!name) return; + const key = name.toLowerCase() + '.svg'; + if (icons[key]) { + return icons[key]; + } else { + return icons['eth.svg']; + } +} + + +export function tokenIconPath(token: TokenData): string { + return token.meta.iconUrl ?? tokenIcon(token.meta.symbol); } + + +export function getTokenName(token: TokenData): string { + return token.meta.name ?? token.meta.symbol; +} \ No newline at end of file diff --git a/src/core/metaport.ts b/src/core/metaport.ts new file mode 100644 index 0000000..8da24ec --- /dev/null +++ b/src/core/metaport.ts @@ -0,0 +1,237 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file metaport.ts + * @copyright SKALE Labs 2023-Present + */ + +import { Provider, JsonRpcProvider, Contract } from "ethers"; + +import { MetaportConfig, TokenDataTypesMap, Token, TokenContractsMap, TokenBalancesMap } from './interfaces'; +import { TokenType, TokenData, CustomAbiTokenType } from './dataclasses'; + +import { getEmptyTokenDataMap } from './tokens/helper'; +import { getChainEndpoint, initIMA, initMainnet, initSChain } from './network'; +import { ERC_ABIS } from './contracts'; + + +import debug from 'debug'; +import { MainnetChain, SChain } from "@skalenetwork/ima-js"; + + + +const log = debug('ima:test:MainnetChain'); + + +export const createTokenData = ( + tokenKeyname: string, + chainName: string, + tokenType: TokenType, + config: MetaportConfig +): TokenData => { + const configToken: Token = config.connections[chainName][tokenType][tokenKeyname]; + return new TokenData( + configToken.address, + tokenType, + tokenKeyname, + config.tokens[tokenKeyname], + configToken.chains, + chainName + ); +} + + +export const addTokenData = ( + tokenKeyname: string, + chainName: string, + tokenType: TokenType, + config: MetaportConfig, + tokens: TokenDataTypesMap +) => { + tokens[tokenType][tokenKeyname] = createTokenData( + tokenKeyname, + chainName, + tokenType, + config + ) +} + +export const createTokensMap = ( + chainName1: string, + chainName2: string | null | undefined, + config: MetaportConfig +): TokenDataTypesMap => { + const tokens = getEmptyTokenDataMap(); + log(`updating tokens map for ${chainName1} -> ${chainName2}`); + if (chainName1) { + Object.values(TokenType).forEach(tokenType => { + if (config.connections[chainName1][tokenType]) { + Object.keys(config.connections[chainName1][tokenType]).forEach(tokenKeyname => { + const tokenInfo = config.connections[chainName1][tokenType][tokenKeyname]; + if (!chainName2 || (chainName2 && tokenInfo.chains.hasOwnProperty(chainName2))) { + addTokenData(tokenKeyname, chainName1, tokenType as TokenType, config, tokens); + } + }); + } + }); + } + return tokens; +} + + +export default class MetaportCore { + + private _config: MetaportConfig + + constructor(config: MetaportConfig) { + this._config = config; + } + + get config(): MetaportConfig { + return this._config; + } + + /** + * Generates available tokens for a given chain or a pair of the chains. + * + * @param {string} from - Source chain name. + * @param {string | null} [to] - Destination chain name. + * + * @returns {TokenDataTypesMap} - Returns a map of token data types for the given chains. + * + * @example + * + * // To get tokens for 'a' -> 'b' + * const tokens = mpc.tokens('a', 'b'); + * + * // To get all tokens from 'a' + * const tokens = mpc.tokens('a'); + */ + tokens( + from: string, + to?: string | null, + ): TokenDataTypesMap { + if (from === undefined || from === null || from === '') return getEmptyTokenDataMap(); + return createTokensMap(from, to, this._config); + } + + async tokenBalance( + tokenContract: Contract, + address: string + ): Promise { + return await tokenContract.balanceOf(address); + } + + async tokenBalances( + tokenContracts: TokenContractsMap, + address: string + ): Promise { + const balances: TokenBalancesMap = {}; + const tokenKeynames = Object.keys(tokenContracts); + for (const tokenKeyname of tokenKeynames) { + balances[tokenKeyname] = await tokenContracts[tokenKeyname].balanceOf(address); + } + return balances; + } + + tokenContracts( + tokens: TokenDataTypesMap, + tokenType: TokenType, + chainName: string, + provider: Provider + ): TokenContractsMap { + const contracts: TokenContractsMap = {}; + if (tokens[tokenType]) { + Object.keys(tokens[tokenType]).forEach(tokenKeyname => { + contracts[tokenKeyname] = this.tokenContract( + chainName, + tokenKeyname, + tokenType, + provider + ) + }); + } + return contracts; + } + + tokenContract( + chainName: string, + tokenKeyname: string, + tokenType: TokenType, + provider: Provider, + customAbiTokenType?: CustomAbiTokenType, + destChainName?: string + ): Contract { + const token = this._config.connections[chainName][tokenType][tokenKeyname]; + const abi = customAbiTokenType ? ERC_ABIS[customAbiTokenType].abi : ERC_ABIS[tokenType].abi; + const address = customAbiTokenType ? token.chains[destChainName].wrapper : token.address; + // TODO: add sFUEL address support! + return new Contract(address, abi, provider); + } + + originAddress( + chainName1: string, + chainName2: string, + tokenKeyname: string, + tokenType: TokenType + ) { + let token = this._config.connections[chainName1][tokenType][tokenKeyname]; + const isClone = token.chains[chainName2].clone; + if (isClone) { + token = this._config.connections[chainName2][tokenType][tokenKeyname]; + } + return token.chains[isClone ? chainName1 : chainName2].wrapper ?? token.address; + } + + endpoint( + chainName: string + ): string { + return getChainEndpoint( + this._config.mainnetEndpoint, + this._config.skaleNetwork, + chainName + ); + } + + ima(chainName: string): MainnetChain | SChain { + return initIMA( + this._config.mainnetEndpoint, + this._config.skaleNetwork, + chainName + ); + } + + mainnet(): MainnetChain { + return initMainnet( + this._config.mainnetEndpoint, + this._config.skaleNetwork, + ) + } + + schain(chainName: string): SChain { + return initSChain( + this._config.skaleNetwork, + chainName + ) + } + + provider(chainName: string): Provider { + return new JsonRpcProvider(this.endpoint(chainName)); + } +} diff --git a/src/core/network.ts b/src/core/network.ts new file mode 100644 index 0000000..e587145 --- /dev/null +++ b/src/core/network.ts @@ -0,0 +1,104 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file network.ts + * @copyright SKALE Labs 2023-Present + */ + +import { MainnetChain, SChain } from '@skalenetwork/ima-js'; +import { JsonRpcProvider } from "ethers"; + + +import proxyEndpoints from '../metadata/proxy.json'; +import { MAINNET_CHAIN_NAME } from './constants'; +import { IMA_ADDRESSES, IMA_ABIS } from './contracts'; +import { SkaleNetwork } from './interfaces'; + + +const PROTOCOL: { [protocol in 'http' | 'ws']: string } = { + 'http': 'https://', + 'ws': 'wss://' +} + +export const CHAIN_IDS: { [network in SkaleNetwork]: number } = { + 'staging': 5, + 'legacy': 5, + 'regression': 5, + 'mainnet': 5 +} + +export function isMainnetChainId(chainId: number | BigInt, skaleNetwork: SkaleNetwork): boolean { + return Number(chainId) === CHAIN_IDS[skaleNetwork]; +} + +export function getChainEndpoint( + mainnetEndpoint: string, + network: SkaleNetwork, + chainName: string +): string { + if (chainName === MAINNET_CHAIN_NAME) return mainnetEndpoint; + return getSChainEndpoint(network, chainName); +} + +export function getSChainEndpoint( + network: SkaleNetwork, + sChainName: string, + protocol: 'http' | 'ws' = 'http' +): string { + return PROTOCOL[protocol] + getProxyEndpoint(network) + '/v1/' + (protocol === 'ws' ? 'ws/' : '') + sChainName; +} + +function getProxyEndpoint(network: SkaleNetwork) { + return proxyEndpoints[network]; +} + +export function getMainnetAbi(network: string) { + if (network === 'staging') { + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.staging } + } + if (network === 'legacy') { + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.legacy } + } + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.mainnet } +} + + +export function initIMA( + mainnetEndpoint: string, + network: SkaleNetwork, + chainName: string +): MainnetChain | SChain { + if (chainName === MAINNET_CHAIN_NAME) return initMainnet(mainnetEndpoint, network); + return initSChain(network, chainName); +} + +export function initMainnet(mainnetEndpoint: string, network: string): MainnetChain { + const provider = new JsonRpcProvider(mainnetEndpoint); + return new MainnetChain(provider, getMainnetAbi(network)); +} + +export function initSChain(network: SkaleNetwork, chainName: string): SChain { + const endpoint = getChainEndpoint( + null, + network, + chainName + ); + const provider = new JsonRpcProvider(endpoint); + return new SChain(provider, IMA_ABIS.schain); +} diff --git a/src/core/sfuel.ts b/src/core/sfuel.ts index 9c61ef3..c2c6f84 100644 --- a/src/core/sfuel.ts +++ b/src/core/sfuel.ts @@ -22,116 +22,118 @@ * @copyright SKALE Labs 2022-Present */ -import Web3 from 'web3'; -import debug from 'debug'; -import { AnonymousPoW } from "@skaleproject/pow-ethers"; - -import { getChainEndpoint, initWeb3 } from '../core/core'; -import { getFuncData, isFaucetAvailable } from '../core/faucet'; -import { DEFAULT_MIN_SFUEL_WEI, DEFAULT_FAUCET_URL, MAINNET_CHAIN_NAME } from '../core/constants'; - - -debug.enable('*'); -const log = debug('metaport:Widget'); - - -function getFaucetUrl(chainsMetadata: object, chainName: string): string { - if (chainsMetadata && chainsMetadata[chainName]) return chainsMetadata[chainName].faucetUrl; - return DEFAULT_FAUCET_URL; -} - - -function getMinSfuelWei(chainName: string, chainsMetadata?: object): string { - if (chainsMetadata && chainsMetadata[chainName] && chainsMetadata[chainName].minSfuelWei) { - return chainsMetadata[chainName].minSfuelWei; - } else { - return DEFAULT_MIN_SFUEL_WEI; - } -} - - -async function getSfuelBalance(web3: Web3, address: string): Promise { - return await web3.eth.getBalance(address); -} - - -export interface StationData { - faucetUrl: string; - minSfuelWei: string; - balance: string; - ok: boolean; -} - - -export interface StationPowRes { - message: string; - ok: boolean; -} - - -export class Station { - - endpoint: string; - web3: Web3; - - constructor( - public chainName: string, - public skaleNetwork: string, - public mainnetEndpoint?: string, - public chainsMetadata?: object - ) { - this.chainName = chainName; - this.skaleNetwork = skaleNetwork; - - this.endpoint = getChainEndpoint(chainName, mainnetEndpoint, skaleNetwork); - - this.web3 = initWeb3(this.endpoint); - this.chainsMetadata = chainsMetadata; - } - - async getData(address: string): Promise { - try { - const minSfuelWei = getMinSfuelWei(this.chainName, this.chainsMetadata); - const balance = await getSfuelBalance(this.web3, address); - return { - faucetUrl: getFaucetUrl(this.chainsMetadata, this.chainName), - minSfuelWei, - balance, - ok: Number(balance) >= Number(minSfuelWei) - } - } catch (e) { - log(`ERROR: getSFuelData for ${this.chainName} failed!`); - log(e); - return { - faucetUrl: undefined, minSfuelWei: undefined, balance: undefined, ok: undefined - }; - } - } - - async doPoW(address: string): Promise { - if (!this.chainName || !isFaucetAvailable(this.chainName, this.skaleNetwork)) { - log('WARNING: PoW is not available for this chain'); - if (this.chainName === MAINNET_CHAIN_NAME) { - return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; - } - return { ok: false, message: 'PoW is not available for this chain' }; - } - log('Mining sFUEL for ' + address + ' on ' + this.chainName + '...'); - try { - const endpoint = getChainEndpoint(this.chainName, undefined, this.skaleNetwork); - const web3 = initWeb3(endpoint); - const anon = new AnonymousPoW({ rpcUrl: endpoint }); - await (await anon.send(getFuncData( - web3, - this.chainName, - address, - this.skaleNetwork - ))).wait(); - return { ok: true, message: 'PoW finished successfully' } - } catch (e) { - log('ERROR: PoW failed!'); - log(e); - return { ok: false, message: e.message }; - } - } -} +// import debug from 'debug'; +// import { AnonymousPoW } from "@skaleproject/pow-ethers"; + +// import { getChainEndpoint, initWeb3 } from '../core/core'; +// import { getFuncData, isFaucetAvailable } from '../core/faucet'; +// import { DEFAULT_MIN_SFUEL_WEI, DEFAULT_FAUCET_URL, MAINNET_CHAIN_NAME } from '../core/constants'; + + +// debug.enable('*'); +// const log = debug('metaport:Widget'); + + +// function getFaucetUrl(chainsMetadata: object, chainName: string): string { +// if (chainsMetadata && chainsMetadata[chainName]) return chainsMetadata[chainName].faucetUrl; +// return DEFAULT_FAUCET_URL; +// } + + +// function getMinSfuelWei(chainName: string, chainsMetadata?: object): string { +// if (chainsMetadata && chainsMetadata[chainName] && chainsMetadata[chainName].minSfuelWei) { +// return chainsMetadata[chainName].minSfuelWei; +// } else { +// return DEFAULT_MIN_SFUEL_WEI; +// } +// } + + +// async function getSfuelBalance(web3: any, address: string): Promise { +// //return await provider.getBalance(address); +// // TODO! +// console.log(web3, address); +// return ''; +// } + + +// export interface StationData { +// faucetUrl: string; +// minSfuelWei: string; +// balance: string; +// ok: boolean; +// } + + +// export interface StationPowRes { +// message: string; +// ok: boolean; +// } + + +// export class Station { + +// endpoint: string; +// web3: any; // todo! + +// constructor( +// public chainName: string, +// public skaleNetwork: string, +// public mainnetEndpoint?: string, +// public chainsMetadata?: object +// ) { +// this.chainName = chainName; +// this.skaleNetwork = skaleNetwork; + +// this.endpoint = getChainEndpoint(chainName, mainnetEndpoint, skaleNetwork); + +// this.web3 = initWeb3(this.endpoint); +// this.chainsMetadata = chainsMetadata; +// } + +// async getData(address: string): Promise { +// try { +// const minSfuelWei = getMinSfuelWei(this.chainName, this.chainsMetadata); +// const balance = await getSfuelBalance(this.web3, address); +// return { +// faucetUrl: getFaucetUrl(this.chainsMetadata, this.chainName), +// minSfuelWei, +// balance, +// ok: Number(balance) >= Number(minSfuelWei) +// } +// } catch (e) { +// log(`ERROR: getSFuelData for ${this.chainName} failed!`); +// log(e); +// return { +// faucetUrl: undefined, minSfuelWei: undefined, balance: undefined, ok: undefined +// }; +// } +// } + +// async doPoW(address: string): Promise { +// if (!this.chainName || !isFaucetAvailable(this.chainName, this.skaleNetwork)) { +// log('WARNING: PoW is not available for this chain'); +// if (this.chainName === MAINNET_CHAIN_NAME) { +// return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; +// } +// return { ok: false, message: 'PoW is not available for this chain' }; +// } +// log('Mining sFUEL for ' + address + ' on ' + this.chainName + '...'); +// try { +// const endpoint = getChainEndpoint(this.chainName, undefined, this.skaleNetwork); +// const web3 = initWeb3(endpoint); +// const anon = new AnonymousPoW({ rpcUrl: endpoint }); +// await (await anon.send(getFuncData( +// web3, +// this.chainName, +// address, +// this.skaleNetwork +// ))).wait(); +// return { ok: true, message: 'PoW finished successfully' } +// } catch (e) { +// log('ERROR: PoW failed!'); +// log(e); +// return { ok: false, message: e.message }; +// } +// } +// } diff --git a/src/components/WidgetUI/Themes.ts b/src/core/themes.ts similarity index 85% rename from src/components/WidgetUI/Themes.ts rename to src/core/themes.ts index 7829906..729de92 100644 --- a/src/components/WidgetUI/Themes.ts +++ b/src/core/themes.ts @@ -17,26 +17,26 @@ */ /** - * @file Themes.ts + * @file themes.ts * @copyright SKALE Labs 2022-Present */ -import { Positions } from '../../core/dataclasses/Position'; -import { MetaportTheme } from '../../core/interfaces/Theme'; -import { DEFAULT_MP_Z_INDEX } from '../../core/constants'; +import { Positions } from './dataclasses/Position'; +import { MetaportTheme } from './interfaces/Theme'; +import { DEFAULT_MP_Z_INDEX } from './constants'; const defaultThemes = { 'dark': { - primary: 'rgb(217, 224, 33)', - background: '#0e0e0e', + primary: '#29FF94', + background: '#000000', mode: 'dark', position: Positions.bottomRight, zIndex: DEFAULT_MP_Z_INDEX }, 'light': { - primary: '#309676', - background: '#fbfbfb', + primary: '#173CFF', + background: '#EFEFEF', mode: 'light', position: Positions.bottomRight, zIndex: DEFAULT_MP_Z_INDEX diff --git a/src/core/tokens/erc20.ts b/src/core/tokens/erc20.ts deleted file mode 100644 index 1ef5cef..0000000 --- a/src/core/tokens/erc20.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file erc20.ts - * @copyright SKALE Labs 2022-Present - */ - -import debug from 'debug'; - -import { SChain, MainnetChain } from '@skalenetwork/ima-js'; - -import { initContract } from '../core'; -import { getEmptyTokenDataMap } from './helper'; -import { externalEvents } from '../events'; -import { MAINNET_CHAIN_NAME } from '../constants'; -import TokenData, { getTokenKeyname } from '../dataclasses/TokenData'; -import { TokenType } from '../dataclasses/TokenType'; -import * as interfaces from '../interfaces/index'; -import { fromWei } from '../convertation'; - - -debug.enable('*'); -const log = debug('metaport:tokens:erc20'); - - -export async function updateERC20TokenBalances( - availableTokens: interfaces.TokenDataTypesMap, - chainName: string, - mainnet: MainnetChain, - sChain1: SChain, - address: string -): Promise { - log('Getting ERC20 token balances...'); - for (const [symbol, tokenData] of Object.entries(availableTokens.erc20)) { - if (chainName === MAINNET_CHAIN_NAME) { - const balance = await getTokenBalance( - chainName, - mainnet, - symbol, - tokenData.decimals, - address - ); - availableTokens.erc20[symbol].balance = balance; - } else { - const balance = tokenData.wrapsSFuel && !tokenData.clone ? await getSFuelBalance( - chainName, - sChain1, - tokenData.decimals, - address - ) : await getTokenBalance( - chainName, - sChain1, - symbol, - tokenData.decimals, - address - ); - availableTokens.erc20[symbol].balance = balance; - if (availableTokens.erc20[symbol].unwrappedSymbol && - !availableTokens.erc20[symbol].clone) { - const wBalance = await getTokenBalance( - chainName, - sChain1, - availableTokens.erc20[symbol].unwrappedSymbol, - tokenData.decimals, - address - ); - availableTokens.erc20[symbol].unwrappedBalance = wBalance; - } - } - } -} - - -export async function getTokenBalance( - chainName: string, - chain: any, - tokenSymbol: string, - decimals: string, - address: string -): Promise { - const tokenContract = chain.erc20.tokens[tokenSymbol]; - const balance = await chain.getERC20Balance(tokenContract, address); - externalEvents.balance(tokenSymbol, chainName, balance); - return fromWei(balance, decimals); -} - - -export async function getSFuelBalance( - chainName: string, - chain: any, - decimals: string, - address: string -): Promise { - const balance = await chain.web3.eth.getBalance(address); - externalEvents.balance('sfuel', chainName, balance); - return fromWei(balance, decimals); -} - -export async function getWrappedTokens( - sChain: SChain, - chainName: string, - configTokens: interfaces.TokensMap, - address: string -): Promise { - log('Checking wrapped tokens...'); - const wrappedTokens: interfaces.TokenDataTypesMap = getEmptyTokenDataMap(); - if (configTokens && configTokens[chainName] && configTokens[chainName].erc20) { - for (const [_symbol, configToken] of Object.entries(configTokens[chainName].erc20)) { - if (!configToken.wraps && !configToken.wrapsSFuel) continue; - log(`getting wrapped token info configToken: ${JSON.stringify(configToken)}`) - const tokenKeyname = getTokenKeyname(configToken.symbol, configToken.address); - const tokenAbiType = configToken.wrapsSFuel ? 'sfuelwrap' : 'erc20wrap'; - const tokenContract = initContract(tokenAbiType, configToken.address, sChain.web3); - sChain.erc20.addToken( - tokenKeyname, - tokenContract - ); - const balance = await sChain.getERC20Balance( - tokenContract, - address - ); - log(`token ${tokenKeyname}, address: ${address}, balance: ${balance}`); - if (balance !== '0') { - let wrapsSymbol; - let wrapsAddress; - let wrapsIconUrl; - if (configToken.wraps) { - wrapsSymbol = configToken.wraps.symbol; - wrapsAddress = configToken.wraps.address; - wrapsIconUrl = configToken.wraps.iconUrl; - } - wrappedTokens.erc20[tokenKeyname] = new TokenData( - null, - configToken.address, - configToken.name, - configToken.symbol, - configToken.cloneSymbol, - false, - configToken.iconUrl, - configToken.decimals, - TokenType.erc20, - wrapsSymbol, - wrapsAddress, - wrapsIconUrl, - configToken.wrapsSFuel - ); - wrappedTokens.erc20[tokenKeyname].balance = fromWei( - balance, - wrappedTokens.erc20[tokenKeyname].decimals - ); - } - } - } - log('wrappedTokens'); - log(wrappedTokens); - return wrappedTokens; -} \ No newline at end of file diff --git a/src/core/tokens/eth.ts b/src/core/tokens/eth.ts deleted file mode 100644 index 61246d7..0000000 --- a/src/core/tokens/eth.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file eth.ts - * @copyright SKALE Labs 2022-Present - */ - - -import debug from 'debug'; - -import { SChain, MainnetChain } from '@skalenetwork/ima-js'; - -import { isMainnet } from '../helper'; -import { externalEvents } from '../events'; - -import EthTokenData from '../dataclasses/EthTokenData'; -import { TokenType } from '../dataclasses/TokenType'; -import * as interfaces from '../interfaces/index'; -import { MAINNET_CHAIN_NAME } from '../constants'; - - -debug.enable('*'); -const log = debug('metaport:tokens:eth'); - - -function ethInConfig(configTokens: interfaces.TokensMap): boolean { - return configTokens[MAINNET_CHAIN_NAME] !== undefined && - configTokens[MAINNET_CHAIN_NAME].eth !== undefined; -} - - -export async function addETHToken( - chainName1: string, - chainName2: string, - configTokens: interfaces.TokensMap, - availableTokens: interfaces.TokenDataTypesMap -): Promise { - - log('Checking ETH in the configTokens'); - if (!ethInConfig(configTokens)) return; - log('Adding ETH to token list'); - - let chains: string[] = []; - - if (configTokens[MAINNET_CHAIN_NAME][TokenType.eth]) { - chains = (configTokens[MAINNET_CHAIN_NAME][TokenType.eth].chains as string[]); - } - - if (chainName1 === MAINNET_CHAIN_NAME) { - if (chains.includes(chainName2)) { - availableTokens[TokenType.eth][TokenType.eth] = new EthTokenData(false); - } - } - - if (chainName2 === MAINNET_CHAIN_NAME) { - if (chains.includes(chainName1)) { - availableTokens[TokenType.eth][TokenType.eth] = new EthTokenData(true); - } - } -} - - -export async function getEthBalance( - mainnet: MainnetChain, - sChain: SChain, - chainName: string, - address: string -) { - log(`Getting ETH balance for ${address} on ${chainName}`); - const ethBalance = isMainnet(chainName) ? await mainnet.ethBalance(address) : - await sChain.ethBalance(address); - log('ETH balance for ' + address + ': ' + ethBalance + ' wei'); - externalEvents.balance('eth', chainName, ethBalance); - return mainnet ? mainnet.web3.utils.fromWei(ethBalance) : sChain.web3.utils.fromWei(ethBalance); -} diff --git a/src/core/tokens/helper.ts b/src/core/tokens/helper.ts index 4a95a11..e10604a 100644 --- a/src/core/tokens/helper.ts +++ b/src/core/tokens/helper.ts @@ -21,7 +21,7 @@ * @copyright SKALE Labs 2022-Present */ -import TokenData from '../dataclasses/TokenData'; +import { TokenData } from '../dataclasses/TokenData'; import * as interfaces from '../interfaces/index'; import { eqArrays } from '../helper'; @@ -45,7 +45,7 @@ export function getAvailableTokensTotal(availableTokens): number { export function getDefaultToken(availableTokens: interfaces.TokenDataTypesMap): TokenData { if (availableTokens === undefined) return; const availableTokenNumers = getAvailableTokenNumers(availableTokens); - if (eqArrays(availableTokenNumers, [1, 0, 0, 0, 0])) return availableTokens.eth.eth; + // if (eqArrays(availableTokenNumers, [1, 0, 0, 0, 0])) return availableTokens.eth.eth; if (eqArrays(availableTokenNumers, [0, 1, 0, 0, 0])) { return Object.values(availableTokens.erc20)[0]; } diff --git a/src/core/tokens/index.ts b/src/core/tokens/index.ts deleted file mode 100644 index 4681419..0000000 --- a/src/core/tokens/index.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file index.ts - * @copyright SKALE Labs 2022-Present - */ - -import debug from 'debug'; - -import { SChain, MainnetChain } from '@skalenetwork/ima-js'; - -import { addETHToken, getEthBalance } from './eth'; -import { updateERC20TokenBalances } from './erc20'; -import { addM2STokens } from './m2s'; -import { addS2STokens } from './s2s'; - -import { isMainnet } from '../helper'; -import { getEmptyTokenDataMap } from './helper'; - -import * as interfaces from '../interfaces/index'; - - -debug.enable('*'); -const log = debug('metaport:tokens'); - - -export async function getAvailableTokens( - mainnet: MainnetChain, - sChain1: SChain, - sChain2: SChain, - chainName1: string, - chainName2: string, - configTokens: interfaces.TokensMap, - autoLookup: boolean -): Promise { - log('Collecting available tokens for ' + chainName1 + ' → ' + chainName2); - const availableTokens = getEmptyTokenDataMap(); - try { - log('Adding ETH to availableTokens'); - await addETHToken( - chainName1, - chainName2, - configTokens, - availableTokens - ); - if (isMainnet(chainName1) || isMainnet(chainName2)) { - log('Going to add M2S ERC20 tokens') - const sChain = isMainnet(chainName1) ? sChain2 : sChain1; - const schainName = isMainnet(chainName1) ? chainName2 : chainName1; - await addM2STokens( - mainnet, - sChain, - schainName, - configTokens, - availableTokens, - autoLookup - ); - } else { - await addS2STokens( - sChain1, - sChain2, - chainName1, - chainName2, - configTokens, - availableTokens - ); - } - log('availableTokens'); - log(availableTokens); - } catch (e: unknown) { - log('ERROR: Something went wrong during getAvailableTokens procedure'); - if (typeof e === "string") { - log(e.toUpperCase()); - } else if (e instanceof Error) { - log(e.message); - } - } - return availableTokens; -} - - -export async function getTokenBalances( - availableTokens: interfaces.TokenDataTypesMap, - chainName: string, - mainnet: MainnetChain, - sChain1: SChain, - address: string -) { - log('Getting token balances...'); - if (availableTokens.eth && availableTokens.eth.eth) { - availableTokens.eth.eth.balance = await getEthBalance( - mainnet, - sChain1, - chainName, - address - ); - }; - await updateERC20TokenBalances( - availableTokens, - chainName, - mainnet, - sChain1, - address - ); -} \ No newline at end of file diff --git a/src/core/tokens/m2s.ts b/src/core/tokens/m2s.ts deleted file mode 100644 index 49c7eee..0000000 --- a/src/core/tokens/m2s.ts +++ /dev/null @@ -1,210 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file m2s.ts - * @copyright SKALE Labs 2022-Present - */ - - -import debug from 'debug'; - -import { SChain, MainnetChain } from '@skalenetwork/ima-js'; - -import { initContract } from '../core'; - -import * as interfaces from '../interfaces/index'; -import { TokenType } from '../dataclasses/TokenType'; -import TokenData, { getTokenKeyname } from '../dataclasses/TokenData'; -import { MAINNET_CHAIN_NAME, ZERO_ADDRESS } from '../constants'; -import { isMainnet } from '../helper'; - - -debug.enable('*'); -const log = debug('metaport:tokens:m2s'); - - -export async function addM2STokens( - mainnet: MainnetChain, - sChain: SChain, - schainName: string, - configTokens: interfaces.TokensMap, - availableTokens: interfaces.TokenDataTypesMap, - autoLookup: boolean -): Promise { - autoLookup ? await getM2STokensAutomatic( - mainnet, - sChain, - schainName, - configTokens, - availableTokens - ) : await getM2STokensManual( - mainnet, - sChain, - configTokens, - availableTokens - ); -} - - -async function getM2STokensAutomatic( - mainnet: MainnetChain, - sChain: SChain, - schainName: string, - configTokens: interfaces.TokensMap, - availableTokens: interfaces.TokenDataTypesMap -): Promise { - log('Starting automatic lookup for M2S tokens...'); - for (const tokenType in TokenType) { - await addM2STokensAutomatic( - tokenType, - mainnet, - sChain, - schainName, - configTokens, - availableTokens - ) - } -} - - -async function addM2STokensAutomatic( - tokenType: string, - mainnet: MainnetChain, - sChain: SChain, - destChainName: string, - configTokens: interfaces.TokensMap, - availableTokens: interfaces.TokenDataTypesMap -): Promise { - if (tokenType === TokenType.eth) return; - log(`Getting token pairs: ${tokenType}`); - const len = await mainnet[tokenType].getTokenMappingsLength(destChainName); - log(`Number of ${tokenType} token pairs: ${len}`); - if (len === '0') { - log('No linked tokens, exiting.') - return; - } - const tokenAddresses = await mainnet[tokenType].getTokenMappings( - destChainName, - 0, - len - ); // todo: optimize - for (const address of tokenAddresses) { - log(`Adding contract: ${address}`); - const contract = initContract(tokenType, address, mainnet.web3); - - const symbol = await contract.methods.symbol().call(); - const isClone = isMainnet(destChainName); - const name = await contract.methods.name().call(); - - let decimals: string; - - if (tokenType === TokenType.erc20) { - decimals = await contract.methods.decimals().call(); - } - - const cloneAddress = await sChain[tokenType].getTokenCloneAddress(address); - - const key = getTokenKeyname(symbol, address); - log('Adding token: ' + key); - - const tokenData = new TokenData( - cloneAddress, - address, - name, - symbol, - symbol, - isClone, - null, - decimals, - tokenType as TokenType, - null, - null, - null - ); - availableTokens[tokenType][key] = overrideTokenDataFromConfig( - configTokens, - tokenData, - tokenType - ); - mainnet[tokenType].addToken(key, contract); - sChain[tokenType].addToken(key, initContract(tokenType, cloneAddress, sChain.web3)); - } -} - -function overrideTokenDataFromConfig( - configTokens: interfaces.TokensMap, - tokenData: TokenData, - tokenType: string -): TokenData { - if (!configTokens[MAINNET_CHAIN_NAME]) return tokenData; - if (!configTokens[MAINNET_CHAIN_NAME][tokenType]) return tokenData; - if (!configTokens[MAINNET_CHAIN_NAME][tokenType][tokenData.keyname]) return tokenData; - - const configTokenData = configTokens[MAINNET_CHAIN_NAME][tokenType][tokenData.keyname]; - log(`Overriding token data from config ${tokenData.keyname}: ${configTokenData}`); - tokenData.iconUrl = configTokenData.iconUrl ? configTokenData.iconUrl : tokenData.iconUrl; - tokenData.decimals = configTokenData.decimals ? configTokenData.decimals : tokenData.decimals; - tokenData.name = configTokenData.name ? configTokenData.name : tokenData.name; - tokenData.symbol = configTokenData.symbol ? configTokenData.symbol : tokenData.symbol; - return tokenData; -} - - -async function getM2STokensManual( - mainnet: MainnetChain, - sChain: SChain, - configTokens: interfaces.TokensMap, - availableTokens: interfaces.TokenDataTypesMap -): Promise { - log('Starting manual lookup for M2S tokens...'); - if (!configTokens[MAINNET_CHAIN_NAME]) return; - for (const tokenType in configTokens[MAINNET_CHAIN_NAME]) { - if (tokenType === TokenType.eth) continue; - log(`Adding tokens for tokenType ${tokenType}`); - for (const tokenSymbol in configTokens[MAINNET_CHAIN_NAME][tokenType]) { - const tokenInfo = configTokens[MAINNET_CHAIN_NAME][tokenType][tokenSymbol]; - const cloneAddress = await sChain[tokenType].getTokenCloneAddress(tokenInfo.address); - const tokenKeyname = getTokenKeyname(tokenInfo.symbol, tokenInfo.address); - if (cloneAddress === ZERO_ADDRESS) { - log(`No token clone for ${tokenInfo.address}, skipping`); - continue; - } - - log(`Adding token: ${tokenKeyname}`); - availableTokens[tokenType][tokenKeyname] = new TokenData( - cloneAddress, - tokenInfo.address, - tokenInfo.name, - tokenInfo.symbol, - tokenInfo.cloneSymbol, - false, - tokenInfo.iconUrl, - tokenInfo.decimals, - tokenType as TokenType, - null, - null, - null - ); - mainnet[tokenType].addToken( - tokenKeyname, initContract(tokenType, tokenInfo.address, mainnet.web3)); - sChain[tokenType].addToken( - tokenKeyname, initContract(tokenType, cloneAddress, sChain.web3)); - } - } -} \ No newline at end of file diff --git a/src/core/tokens/s2s.ts b/src/core/tokens/s2s.ts deleted file mode 100644 index 7ac5bf9..0000000 --- a/src/core/tokens/s2s.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file s2s.ts - * @copyright SKALE Labs 2022-Present - */ - - -import debug from 'debug'; - -import { SChain } from '@skalenetwork/ima-js'; - -import { initContract } from '../core'; -import * as interfaces from '../interfaces/index'; -import { TokenType } from '../dataclasses/TokenType'; -import TokenData, { getTokenKeyname } from '../dataclasses/TokenData'; -import { ZERO_ADDRESS } from '../constants'; - - -debug.enable('*'); -const log = debug('metaport:tokens:s2s'); - - -export async function addS2STokens( - sChain1: SChain, - sChain2: SChain, - chainName1: string, - chainName2: string, - configTokens: interfaces.TokensMap, - availableTokens: interfaces.TokenDataTypesMap, -): Promise { - log('Add S2S Tokens'); - await collectS2STokens( - sChain1, - sChain2, - chainName1, - configTokens, - availableTokens, - false - ); - await collectS2STokens( - sChain1, - sChain2, - chainName2, - configTokens, - availableTokens, - true - ); -} - - -async function collectS2STokens( - sChain1: SChain, - sChain2: SChain, - chainName: string, - configTokens: interfaces.TokensMap, - availableTokens: interfaces.TokenDataTypesMap, - isClone: boolean -): Promise { - if (!configTokens[chainName]) return; - for (const tokenType in configTokens[chainName]) { - log(`Adding tokens for tokenType ${tokenType}`); - for (const tokenKeyname in configTokens[chainName][tokenType]) { - await addTokenData( - sChain1, - sChain2, - chainName, - configTokens[chainName][tokenType][tokenKeyname], - availableTokens, - isClone, - tokenType as TokenType - ); - } - } -} - - -async function addTokenData( - sChain1: SChain, - sChain2: SChain, - sChainName: string, - configToken: interfaces.Token, - availableTokens: interfaces.TokenDataTypesMap, - isClone: boolean, - tokenType: TokenType -): Promise { - const cloneAddress = await getCloneAddress( - isClone ? sChain1 : sChain2, - configToken.address, - sChainName, - tokenType - ); - if (!cloneAddress) { - log(`No token clone for ${configToken.address}, skipping`); - return; - } - let unwrappedSymbol; - let unwrappedAddress; - let unwrappedIconUrl; - if (configToken.wraps) { - unwrappedSymbol = configToken.wraps.symbol; - unwrappedAddress = configToken.wraps.address; - unwrappedIconUrl = configToken.wraps.iconUrl; - } - - const tokenKeyname = getTokenKeyname(configToken.symbol, configToken.address); - availableTokens[tokenType][tokenKeyname] = new TokenData( - cloneAddress, - configToken.address, - configToken.name, - configToken.symbol, - configToken.cloneSymbol, - isClone, - configToken.iconUrl, - configToken.decimals, - tokenType, - unwrappedSymbol, - unwrappedAddress, - unwrappedIconUrl, - configToken.wrapsSFuel - ); - addToken(sChain1, availableTokens[tokenType][tokenKeyname], true); - addToken(sChain2, availableTokens[tokenType][tokenKeyname], false); -} - - -async function getCloneAddress( - sChain: SChain, - originTokenAddress: string, - originChainName: string, - tokenType: TokenType -): Promise { - log(`Getting clone address for ${originTokenAddress} on a chain`); - try { - const tokenCloneAddress = await sChain[tokenType].getTokenCloneAddress( - originTokenAddress, - originChainName - ); - if (tokenCloneAddress === ZERO_ADDRESS) return; - return tokenCloneAddress; - } catch (e) { - log(`getCloneAddress for ${originTokenAddress} - ${originChainName} failed`); - log(e); - } -} - - -function addToken(sChain: SChain, token: TokenData, fromChain: boolean): void { - log(`Adding token to sChain object - ${token.keyname}`); - const isCloneAddress = (fromChain && token.clone) || (!fromChain && !token.clone); - const address = isCloneAddress ? token.cloneAddress : token.originAddress; - if (token.unwrappedSymbol) { - sChain[token.type].addToken( - token.keyname, - initContract('erc20wrap', address, sChain.web3) - ); - sChain[token.type].addToken( - token.unwrappedSymbol, - initContract(token.type, token.unwrappedAddress, sChain.web3) - ); - } else { - if (token.wrapsSFuel && !token.clone) { - sChain[token.type].addToken( - token.keyname, - initContract('sfuelwrap', address, sChain.web3) - ); - return; - } - sChain[token.type].addToken(token.keyname, initContract(token.type, address, sChain.web3)); - } -} diff --git a/src/core/transfer_steps.ts b/src/core/transfer_steps.ts index 73efb21..3e35b60 100644 --- a/src/core/transfer_steps.ts +++ b/src/core/transfer_steps.ts @@ -1,13 +1,40 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file transfer_steps.ts + * @copyright SKALE Labs 2023-Present + */ import debug from 'debug'; -import TokenData from './dataclasses/TokenData'; -import * as interfaces from './interfaces/index'; +import { + TokenData, + WrapStepMetadata, + UnwrapStepMetadata, + TransferStepMetadata, + StepMetadata, + getActionType +} from './dataclasses'; + import { MetaportConfig } from './interfaces/index'; -import { getChainIcon } from '../components/ChainsList/helper'; -import { getChainName } from './helper'; import { MAINNET_CHAIN_NAME } from './constants'; @@ -15,187 +42,51 @@ debug.enable('*'); const log = debug('metaport:core:transferSteps'); -export function getTransferSteps( - trReq: interfaces.TransferParams, +export function getStepsMetadata( config: MetaportConfig, - theme: any, - token: TokenData -) { - - // TODO: refactor this function - - const steps = []; - - const toChain = trReq.route ? trReq.route.hub : trReq.chains[1]; - const toApp = trReq.route ? null : trReq.toApp; - - log('adding transfer step'); - steps.push(getTransferStep( - trReq.chains[0], - toChain, - trReq.fromApp, - toApp, - token.symbol, - config, - theme - )); - - if (trReq.route) { - if (!token.clone) { - log('adding wrap+transfer steps'); - steps.push(getWrapStep( - trReq.route.hub, - null, - config, - theme - )); - steps.push(getTransferStep( - trReq.route.hub, - trReq.chains[1], - null, - trReq.toApp, - token.symbol, - config, - theme - )); - - } else { - log('adding unwrap+transfer steps'); - steps.push(getUnwrapStep( - trReq.route.hub, - null, - config, - theme - )); - steps.push(getTransferStep( - trReq.route.hub, - trReq.chains[1], - null, - toApp, - token.symbol, - config, - theme - )); - } - } else { - if (token.unwrappedSymbol || token.wrapsSFuel) { - if (!token.clone) { - log('adding wrap step'); - steps.unshift(getWrapStep( - trReq.chains[0], - trReq.fromApp, - config, - theme - )); - } else { - log('adding unwrap step'); - if (!token.wrapsSFuel) { - steps.push(getUnwrapStep( - trReq.chains[1], - trReq.toApp, - config, - theme - )); - } - } - } - }; - if (trReq.chains[1] === MAINNET_CHAIN_NAME && - (trReq.tokenKeyname === 'eth' || (trReq.route && trReq.route.tokenKeyname === 'eth')) - ) { - log('adding unlock step'); - steps.push(getUnlockStep( - trReq.chains[1], - null, - config, - theme + token: TokenData, + to: string +): StepMetadata[] { + const steps: StepMetadata[] = []; + if (token === undefined || token === null || to === null || to === '') return steps; + + const toChain = token.connections[to].hub ?? to; + const hubTokenOptions = config.connections[toChain][token.type][token.keyname].chains[token.chain]; + const destTokenOptions = config.connections[to][token.type][token.keyname].chains[token.chain]; + const isCloneToClone = token.isClone(to) && destTokenOptions.clone; + + log(`Setting toChain: ${toChain}`); + + if (token.connections[toChain].wrapper) { + steps.push(new WrapStepMetadata( + token.chain, + to )) } - return steps; -} - - -function getWrapStep( - chain: string, - app: string, - config: MetaportConfig, - theme: any -) { - const chainName = getChainName(config.chainsMetadata, chain, config.skaleNetwork, app); - const chainIcon = getChainIcon(config.skaleNetwork, chain, theme.dark, app); - return { - chainName, - chainIcon, - headline: 'Wrap on', - text: `Wrap on ${chainName}. You may need to approve first.`, - btnText: 'Wrap', - btnLoadingText: 'Wrapping' + steps.push(new TransferStepMetadata( + getActionType(token.chain, toChain, token.type), + token.chain, + toChain + )); + if (hubTokenOptions.wrapper && !isCloneToClone) { + steps.push(new UnwrapStepMetadata(token.chain, toChain)); } -} - - -function getUnwrapStep( - chain: string, - app: string, - config: MetaportConfig, - theme: any -) { - const chainName = getChainName(config.chainsMetadata, chain, config.skaleNetwork, app); - const chainIcon = getChainIcon(config.skaleNetwork, chain, theme.dark, app); - return { - chainName, - chainIcon, - headline: 'Unwrap on', - text: `Unwrap on ${chainName}.`, - btnText: 'Unwrap', - btnLoadingText: 'Unwrapping' + if (token.connections[to].hub) { + const tokenOptionsHub = config.connections[toChain][token.type][token.keyname].chains[to]; + if (tokenOptionsHub.wrapper && !isCloneToClone) { + steps.push(new WrapStepMetadata(toChain, to)); + } + steps.push(new TransferStepMetadata( + getActionType(toChain, to, token.type), + toChain, + to + )); } -} - - -function getUnlockStep( - chain: string, - app: string, - config: MetaportConfig, - theme: any -) { - const chainName = getChainName(config.chainsMetadata, chain, config.skaleNetwork, app); - const chainIcon = getChainIcon(config.skaleNetwork, chain, theme.dark, app); - return { - chainName, - chainIcon, - headline: 'Unlock on', - text: `You have to unlock your assets to be able to use it on ${chainName}.`, - btnText: 'Unlock', - btnLoadingText: 'Unlocking' + if (to === MAINNET_CHAIN_NAME && token.keyname === 'eth') { + // todo: add unlock step! } -} - -function getTransferStep( - fromChain: string, - toChain: string, - fromApp: string, - toApp: string, - tokenSymbol: string, - config: MetaportConfig, - theme: any -) { - const fromChainName = getChainName( - config.chainsMetadata, - fromChain, - config.skaleNetwork, - fromApp - ); - const toChainName = getChainName(config.chainsMetadata, toChain, config.skaleNetwork, toApp); - const toChainIcon = getChainIcon(config.skaleNetwork, toChain, theme.dark, toApp); - return { - chainName: toChainName, - chainIcon: toChainIcon, - headline: 'Transfer to', - text: `Transfer ${tokenSymbol.toUpperCase()} from ${fromChainName} to ${toChainName}. - You may need to approve first.`, - btnText: 'Transfer', - btnLoadingText: 'Transferring' - } + log(`Action steps metadata:`); + log(steps); + return steps; } \ No newline at end of file diff --git a/src/core/views.ts b/src/core/views.ts index 407f32b..aa33581 100644 --- a/src/core/views.ts +++ b/src/core/views.ts @@ -35,6 +35,6 @@ export function isTransferRequestSummary(view: View) { } -export function isTransferRequestSteps(view: View) { +export function isStepsMetadata(view: View) { return view === View.TRANSFER_REQUEST_STEPS } diff --git a/src/core/wagmi_network.ts b/src/core/wagmi_network.ts new file mode 100644 index 0000000..5bbaab2 --- /dev/null +++ b/src/core/wagmi_network.ts @@ -0,0 +1,65 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file wagmi_network.ts + * @copyright SKALE Labs 2023-Present + */ + +import { Chain } from 'wagmi' + +import { getSChainEndpoint } from './network'; +import { getExplorerUrl } from './explorer'; +import { getChainAlias } from './helper'; +import { getChainId } from './chain_id'; + +import { SkaleNetwork } from './interfaces'; + + +export function constructWagmiChain(network: SkaleNetwork, chainName: string): Chain { + const endpointHttp = getSChainEndpoint(network, chainName); + const endpointWs = getSChainEndpoint(network, chainName, 'ws'); + const explorerUrl = getExplorerUrl(network, chainName); + const name = getChainAlias(network, chainName); + const chainId = getChainId(chainName); + return { + id: chainId, + name: name, + network: `skale-${chainName}`, + nativeCurrency: { + decimals: 18, + name: 'sFUEL', + symbol: 'sFUEL', + }, + rpcUrls: { + public: { http: [endpointHttp], webSocket: [endpointWs] }, + default: { http: [endpointHttp], webSocket: [endpointWs] }, + }, + blockExplorers: { + etherscan: { name: 'SKALE Explorer', url: explorerUrl }, + default: { name: 'SKALE Explorer', url: explorerUrl }, + }, + contracts: {} + } as const satisfies Chain +} + + +export function getWebSocketUrl(chain: Chain): string { + // return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : ''; + return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : ''; // TODO - IP! +} \ No newline at end of file diff --git a/src/metadata/addresses/staging.json b/src/metadata/addresses/staging.json index 8192894..ef115e5 100644 --- a/src/metadata/addresses/staging.json +++ b/src/metadata/addresses/staging.json @@ -1,10 +1,10 @@ { - "message_proxy_mainnet_address": "0x656fb12abab353FB1875a4e3Dc4D70179CB85BA4", - "linker_address": "0xEa870bEF8cc1Ca6871AE960266ea0fDbCF06371d", - "community_pool_address": "0xb2BadB7f28075CB2C8BDBd730204750Db4C03f98", - "deposit_box_eth_address": "0x9910cF6ba22915C5afCe8b682f7c09d1c001FA59", - "deposit_box_erc20_address": "0xb3bf0c62f0924e5C8fdae9815355eA98Fba33C8E", - "deposit_box_erc721_address": "0x98937f91885dcCfF8082623a157296AA161a9917", - "deposit_box_erc1155_address": "0xa0EF1521f56641F9E0E43c46E0F6B20715E454c8", - "deposit_box_erc721_with_metadata_address": "0x4B85DD7d995D6ae445292939d7ebfabD7Cd088dA" + "message_proxy_mainnet_address": "0x08913E0DC2BA60A1626655581f701bCa84f42324", + "linker_address": "0xd081AC47D26baE9c07320AdB83867da28678959E", + "community_pool_address": "0x4957cF98336C0911E42100C8839dCd65DdDe88C9", + "deposit_box_eth_address": "0xD0C9019c517A6CEbb86527fd52F2bDD4Dc6A94Dd", + "deposit_box_erc20_address": "0x2F4B31e661955d41bd6ab5530b117758C26C8159", + "deposit_box_erc721_address": "0x3B1425c6EfD383BAA53F607DD43e5593c4DeBf8f", + "deposit_box_erc1155_address": "0x29DF2117459Dd2c692A1E86DE90371fBc0E3EC76", + "deposit_box_erc721_with_metadata_address": "0x01dd5b9a147c03336F37b7857248d9CDF27661e8" } \ No newline at end of file diff --git a/src/metadata/addresses/staging3.json b/src/metadata/addresses/staging3.json deleted file mode 100644 index ef115e5..0000000 --- a/src/metadata/addresses/staging3.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "message_proxy_mainnet_address": "0x08913E0DC2BA60A1626655581f701bCa84f42324", - "linker_address": "0xd081AC47D26baE9c07320AdB83867da28678959E", - "community_pool_address": "0x4957cF98336C0911E42100C8839dCd65DdDe88C9", - "deposit_box_eth_address": "0xD0C9019c517A6CEbb86527fd52F2bDD4Dc6A94Dd", - "deposit_box_erc20_address": "0x2F4B31e661955d41bd6ab5530b117758C26C8159", - "deposit_box_erc721_address": "0x3B1425c6EfD383BAA53F607DD43e5593c4DeBf8f", - "deposit_box_erc1155_address": "0x29DF2117459Dd2c692A1E86DE90371fBc0E3EC76", - "deposit_box_erc721_with_metadata_address": "0x01dd5b9a147c03336F37b7857248d9CDF27661e8" -} \ No newline at end of file diff --git a/src/metadata/faucet.json b/src/metadata/faucet.json index 8248998..d69a401 100644 --- a/src/metadata/faucet.json +++ b/src/metadata/faucet.json @@ -25,7 +25,7 @@ "func": "0x0c11dedd" } }, - "staging3": { + "staging": { "staging-perfect-parallel-gacrux": { "address": "0x4576d1B9eeaE16d6Ca643e55D21E0Dc00e8A7b6D", "func": "0x0c11dedd" diff --git a/src/metadata/metaportConfigStaging.json b/src/metadata/metaportConfigStaging.json new file mode 100644 index 0000000..db445c7 --- /dev/null +++ b/src/metadata/metaportConfigStaging.json @@ -0,0 +1,314 @@ +{ + "skaleNetwork": "staging", + "openOnLoad": true, + "openButton": true, + "debug": false, + "chains": [ + "mainnet", + "staging-legal-crazy-castor", + "staging-utter-unripe-menkar", + "staging-faint-slimy-achird", + "staging-perfect-parallel-gacrux", + "staging-severe-violet-wezen", + "staging-weepy-fitting-caph" + ], + "tokens": { + "eth": { + "symbol": "eth" + }, + "skl": { + "decimals": "18", + "name": "SKALE", + "symbol": "SKL" + }, + "usdc": { + "decimals": "6", + "symbol": "USDC", + "name": "USD Coin" + }, + "usdt": { + "decimals": "6", + "symbol": "USDT", + "name": "Tether USD" + }, + "wbtc": { + "decimals": "18", + "symbol": "WBTC", + "name": "WBTC" + }, + "_SPACE_1": { + "name": "SKALE Space", + "symbol": "SPACE", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png" + }, + "_SKALIENS_1": { + "name": "SKALIENS Collection", + "symbol": "SKALIENS", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png" + }, + "ruby": { + "name": "Ruby Token", + "iconUrl": "https://ruby.exchange/images/tokens/ruby-square.png", + "symbol": "RUBY" + }, + "dai": { + "name": "DAI Stablecoin", + "symbol": "DAI" + }, + "usdp": { + "name": "Pax Dollar", + "symbol": "USDP", + "iconUrl": "https://ruby.exchange/images/tokens/usdp-square.png" + }, + "hmt": { + "name": "Human Token", + "symbol": "HMT", + "iconUrl": "https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png" + } + }, + "connections": { + "mainnet": { + "erc20": { + "skl": { + "address": "0x493D4442013717189C9963a2e275Ad33bfAFcE11", + "chains": { + "staging-legal-crazy-castor": {}, + "staging-utter-unripe-menkar": { + "hub": "staging-legal-crazy-castor" + }, + "staging-faint-slimy-achird": { + "hub": "staging-legal-crazy-castor" + } + } + }, + "ruby": { + "address": "0xd66641E25E9D36A995682572eaD74E24C11Bb422", + "chains": { + "staging-legal-crazy-castor": {} + } + }, + "dai": { + "address": "0x83B38f79cFFB47CF74f7eC8a5F8D7DD69349fBf7", + "chains": { + "staging-legal-crazy-castor": {} + } + }, + "usdp": { + "address": "0x66259E472f8d09083ecB51D42F9F872A61001426", + "chains": { + "staging-legal-crazy-castor": {} + } + }, + "usdt": { + "address": "0xD1E44e3afd6d3F155e7704c67705E3bAC2e491b6", + "chains": { + "staging-legal-crazy-castor": {} + } + }, + "usdc": { + "address": "0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9", + "chains": { + "staging-legal-crazy-castor": {} + } + }, + "wbtc": { + "address": "0xd80BC0126A38c9F7b915e1B2B9f78280639cadb3", + "chains": { + "staging-legal-crazy-castor": {} + } + }, + "hmt": { + "address": "0x4058d058ff62ED347dB8a69c43Ae9C67268B50b0", + "chains": {} + } + }, + "erc721meta": { + "_SPACE_1": { + "address": "0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6", + "chains": {} + } + }, + "erc1155": { + "_SKALIENS_1": { + "address": "0x6cb73D413970ae9379560aA45c769b417Fbf33D6", + "chains": {} + } + } + }, + "staging-utter-unripe-menkar": { + "erc20": { + "skl": { + "address": "0x7E1B8750C21AebC3bb2a0bDf40be104C609a9852", + "chains": { + "staging-legal-crazy-castor": { + "clone": true + }, + "staging-faint-slimy-achird": { + "hub": "staging-legal-crazy-castor", + "clone": true + }, + "mainnet": { + "hub": "staging-legal-crazy-castor", + "clone": true + } + } + } + } + }, + "staging-faint-slimy-achird": { + "erc20": { + "skl": { + "address": "0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d", + "chains": { + "staging-legal-crazy-castor": { + "clone": true + }, + "mainnet": { + "hub": "staging-legal-crazy-castor", + "clone": true + }, + "staging-utter-unripe-menkar": { + "hub": "staging-legal-crazy-castor", + "clone": true + } + } + } + } + }, + "staging-legal-crazy-castor": { + "erc20": { + "skl": { + "address": "0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6", + "chains": { + "mainnet": { + "clone": true + }, + "staging-utter-unripe-menkar": { + "wrapper": "0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6" + }, + "staging-faint-slimy-achird": { + "wrapper": "0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6" + } + } + }, + "ruby": { + "address": "0xf06De9214B1Db39fFE9db2AebFA74E52f1e46e39", + "chains": { + "mainnet": { + "clone": true + } + } + }, + "dai": { + "address": "0x3595E2f313780cb2f23e197B8e297066fd410d30", + "chains": { + "mainnet": { + "clone": true + } + } + }, + "usdp": { + "address": "0xe0E2cb3A5d6f94a5bc2D00FAa3e64460A9D241E1", + "chains": { + "mainnet": { + "clone": true + } + } + }, + "usdt": { + "address": "0xa388F9783d8E5B0502548061c3b06bf4300Fc0E1", + "chains": { + "mainnet": { + "clone": true + } + } + }, + "usdc": { + "address": "0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33", + "chains": { + "mainnet": { + "clone": true + } + } + }, + "wbtc": { + "address": "0xf5E880E1066DDc90471B9BAE6f183D5344fd289F", + "chains": { + "mainnet": { + "clone": true + } + } + } + } + }, + "staging-perfect-parallel-gacrux": { + "erc20": { + "_ETH_0xBA3f8192e28224790978794102C0D7aaa65B7d70": { + "address": "0xBA3f8192e28224790978794102C0D7aaa65B7d70", + "name": "ETH", + "symbol": "ETH", + "cloneSymbol": "ETH", + "wraps": { + "address": "0xD2Aaa00700000000000000000000000000000000", + "symbol": "ETH", + "name": "aaaa" + } + }, + "_SKILL_0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA": { + "address": "0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA", + "name": "SKILL Token", + "symbol": "SKILL", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Compass/3D/compass_3d.png" + }, + "DMT": { + "address": "0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA", + "name": "DMT", + "symbol": "DMT", + "cloneSymbol": "DMTC", + "wraps": { + "address": "0x688f6d050B935BF06531b51B5e598318788fA7a5", + "symbol": "DMT", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Compass/3D/compass_3d.png" + } + }, + "_SKL_0x099A46F35b627CABee27dc917eDA253fFbC55Be6": { + "address": "0x099A46F35b627CABee27dc917eDA253fFbC55Be6", + "decimals": "18", + "name": "SKL S2S", + "symbol": "SKL" + } + }, + "erc721": { + "_SPS_0x30216880A73B67133F37de35e769b8e1A943D35c": { + "address": "0x30216880A73B67133F37de35e769b8e1A943D35c", + "name": "SKALE Space S2S", + "symbol": "SPS", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Glowing%20star/3D/glowing_star_3d.png" + } + }, + "erc1155": { + "skaliens": { + "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", + "name": "SKALIENS Collection", + "symbol": "SKALIENS2S", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png" + }, + "medals": { + "address": "0x5D8bD602dC5468B3998e8514A1851bd5888E9639", + "name": "Medals", + "symbol": "MEDALS2S", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/1st%20place%20medal/3D/1st_place_medal_3d.png" + }, + "_ANIMALS_0xDf87EEF0977148129969b01b329379b17756cdDE": { + "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", + "name": "Funny Animals", + "symbol": "ANIMALS", + "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Frog/3D/frog_3d.png" + } + } + } + }, + "theme": { + "mode": "dark" + } +} \ No newline at end of file diff --git a/src/metadata/proxy.json b/src/metadata/proxy.json index 3f749d1..a54c3bb 100644 --- a/src/metadata/proxy.json +++ b/src/metadata/proxy.json @@ -1,7 +1,6 @@ { - "mainnet": "https://mainnet.skalenodes.com", - "staging": "https://staging-v2.skalenodes.com", - "staging3": "https://staging-v3.skalenodes.com", - "legacy": "https://legacy-proxy.skalenodes.com/", - "qatestnet": "https://new-testnet-proxy.skalenodes.com" + "mainnet": "mainnet.skalenodes.com", + "staging": "staging-v3.skalenodes.com", + "legacy": "legacy-proxy.skalenodes.com/", + "qatestnet": "new-testnet-proxy.skalenodes.com" } \ No newline at end of file diff --git a/src/metadata/schainAbi.json b/src/metadata/schainAbi.json index 9e18540..cf9d056 100644 --- a/src/metadata/schainAbi.json +++ b/src/metadata/schainAbi.json @@ -1 +1 @@ -{"ERC1155OnChain_abi":[{"inputs":[{"internalType":"string","name":"uri","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"value","type":"string"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"URI","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"burnBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mintBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"uri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}],"ERC20OnChain_abi":[{"inputs":[{"internalType":"string","name":"contractName","type":"string"},{"internalType":"string","name":"contractSymbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"ERC721OnChain_abi":[{"inputs":[{"internalType":"string","name":"contractName","type":"string"},{"internalType":"string","name":"contractSymbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"tokenUri","type":"string"}],"name":"setTokenURI","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}],"community_locker_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"ActivateUser","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"constantHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ConstantUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"LockUser","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"CONSTANT_SETTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"activeUsers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"checkAllowedToSendMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"communityPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasPriceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newTokenManagerLinker","type":"address"},{"internalType":"address","name":"newCommunityPool","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastMessageTimeStamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mainnetGasPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"components":[{"internalType":"uint256[2]","name":"blsSignature","type":"uint256[2]"},{"internalType":"uint256","name":"hashA","type":"uint256"},{"internalType":"uint256","name":"hashB","type":"uint256"},{"internalType":"uint256","name":"counter","type":"uint256"}],"internalType":"struct IMessageProxy.Signature","name":"","type":"tuple"}],"name":"setGasPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newTimeLimitPerMessage","type":"uint256"}],"name":"setTimeLimitPerMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"timeLimitPerMessage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"community_locker_address":"0xD2aaa00300000000000000000000000000000000","eth_erc20_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"BURNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"forceBurn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenManagerEthAddress","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"eth_erc20_address":"0xD2Aaa00700000000000000000000000000000000","key_storage_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FN_NUM_GET_CONFIG_VARIABLE_UINT256","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FN_NUM_GET_CURRENT_BLS_PUBLIC_KEY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FREE_MEM_PTR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBlsCommonPublicKey","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"internalType":"struct IFieldOperations.Fp2Point","name":"x","type":"tuple"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"internalType":"struct IFieldOperations.Fp2Point","name":"y","type":"tuple"}],"internalType":"struct IFieldOperations.G2Point","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"key_storage_address":"0xd2aaa00200000000000000000000000000000000","message_proxy_chain_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"contractAddress","type":"address"}],"name":"ExtraContractRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"contractAddress","type":"address"}],"name":"ExtraContractRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"GasLimitWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"dstChainHash","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"msgCounter","type":"uint256"},{"indexed":true,"internalType":"address","name":"srcContract","type":"address"},{"indexed":false,"internalType":"address","name":"dstContract","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"OutgoingMessage","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"msgCounter","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"}],"name":"PostMessageError","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"oldVersion","type":"string"},{"indexed":false,"internalType":"string","name":"newVersion","type":"string"}],"name":"VersionUpdated","type":"event"},{"inputs":[],"name":"CHAIN_CONNECTOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONSTANT_SETTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ETHERBASE","outputs":[{"internalType":"contract IEtherbaseUpgradeable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_CONTRACT_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MESSAGES_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_BALANCE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REVERT_REASON_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"}],"name":"addConnectedChain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"connectedChains","outputs":[{"internalType":"uint256","name":"incomingMessageCounter","type":"uint256"},{"internalType":"uint256","name":"outgoingMessageCounter","type":"uint256"},{"internalType":"bool","name":"inited","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"schainHash","type":"bytes32"}],"name":"getContractRegisteredLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"internalType":"uint256","name":"from","type":"uint256"},{"internalType":"uint256","name":"to","type":"uint256"}],"name":"getContractRegisteredRange","outputs":[{"internalType":"address[]","name":"contractsInRange","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"fromSchainName","type":"string"}],"name":"getIncomingMessagesCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"}],"name":"getOutgoingMessagesCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IKeyStorage","name":"blsKeyStorage","type":"address"},{"internalType":"string","name":"schainName","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllRegisteredContracts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newGasLimit","type":"uint256"}],"name":"initializeMessageProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"isConnectedChain","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"internalType":"address","name":"contractAddress","type":"address"}],"name":"isContractRegistered","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"keyStorage","outputs":[{"internalType":"contract IKeyStorage","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageInProgress","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"fromChainName","type":"string"},{"internalType":"uint256","name":"startingCounter","type":"uint256"},{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"destinationContract","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IMessageProxy.Message[]","name":"messages","type":"tuple[]"},{"components":[{"internalType":"uint256[2]","name":"blsSignature","type":"uint256[2]"},{"internalType":"uint256","name":"hashA","type":"uint256"},{"internalType":"uint256","name":"hashB","type":"uint256"},{"internalType":"uint256","name":"counter","type":"uint256"}],"internalType":"struct IMessageProxy.Signature","name":"signature","type":"tuple"}],"name":"postIncomingMessages","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"targetChainHash","type":"bytes32"},{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postOutgoingMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"},{"internalType":"address","name":"extraContract","type":"address"}],"name":"registerExtraContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"extraContract","type":"address"}],"name":"registerExtraContractForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"}],"name":"removeConnectedChain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"},{"internalType":"address","name":"extraContract","type":"address"}],"name":"removeExtraContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"extraContract","type":"address"}],"name":"removeExtraContractForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newGasLimit","type":"uint256"}],"name":"setNewGasLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newVersion","type":"string"}],"name":"setVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"dstChainHash","type":"bytes32"},{"internalType":"uint256","name":"msgCounter","type":"uint256"},{"internalType":"address","name":"srcContract","type":"address"},{"internalType":"address","name":"dstContract","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IMessageProxyForSchain.OutgoingMessageData","name":"message","type":"tuple"}],"name":"verifyOutgoingMessageData","outputs":[{"internalType":"bool","name":"isValidMessage","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hashedMessage","type":"bytes32"},{"components":[{"internalType":"uint256[2]","name":"blsSignature","type":"uint256[2]"},{"internalType":"uint256","name":"hashA","type":"uint256"},{"internalType":"uint256","name":"hashB","type":"uint256"},{"internalType":"uint256","name":"counter","type":"uint256"}],"internalType":"struct IMessageProxy.Signature","name":"signature","type":"tuple"}],"name":"verifySignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}],"message_proxy_chain_address":"0xd2AAa00100000000000000000000000000000000","proxy_admin_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"},{"internalType":"address","name":"newAdmin","type":"address"}],"name":"changeProxyAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"}],"name":"getProxyAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"}],"name":"getProxyImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"},{"internalType":"address","name":"implementation","type":"address"}],"name":"upgrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"},{"internalType":"address","name":"implementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeAndCall","outputs":[],"stateMutability":"payable","type":"function"}],"proxy_admin_address":"0xd2aAa00000000000000000000000000000000000","token_manager_erc1155_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"indexed":true,"internalType":"address","name":"erc1155OnSchain","type":"address"}],"name":"ERC1155TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"indexed":true,"internalType":"address","name":"erc1155OnSchain","type":"address"}],"name":"ERC1155TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"ERC1155TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"indexed":true,"internalType":"address","name":"erc1155OnSchain","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"ERC1155TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainName","type":"string"},{"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"internalType":"address","name":"erc1155OnSchain","type":"address"}],"name":"addERC1155TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC1155OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc1155","outputs":[{"internalType":"contract ERC1155OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc1155","outputs":[{"internalType":"contract ERC1155OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exitToMainERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"exitToMainERC1155Batch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferToSchainERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"transferToSchainERC1155Batch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferredAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"token_manager_erc1155_address":"0xD2aaA00900000000000000000000000000000000","token_manager_erc20_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc20OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc20OnSchain","type":"address"}],"name":"ERC20TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc20OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc20OnSchain","type":"address"}],"name":"ERC20TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc20OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc20OnSchain","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainName","type":"string"},{"internalType":"address","name":"erc20OnMainChain","type":"address"},{"internalType":"address","name":"erc20OnSchain","type":"address"}],"name":"addERC20TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc20","outputs":[{"internalType":"contract ERC20OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc20","outputs":[{"internalType":"contract ERC20OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exitToMainERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"","type":"address"}],"name":"totalSupplyOnMainnet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferToSchainERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"transferredAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"token_manager_erc20_address":"0xD2aAA00500000000000000000000000000000000","token_manager_erc721_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainName","type":"string"},{"internalType":"address","name":"erc721OnMainChain","type":"address"},{"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"addERC721TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"exitToMainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferToSchainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferredAmount","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}],"token_manager_erc721_address":"0xD2aaa00600000000000000000000000000000000","token_manager_erc721_with_metadata_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainName","type":"string"},{"internalType":"address","name":"erc721OnMainChain","type":"address"},{"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"addERC721TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"exitToMainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferToSchainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferredAmount","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}],"token_manager_erc721_with_metadata_address":"0xd2AaA00a00000000000000000000000000000000","token_manager_eth_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ethErc20","outputs":[{"internalType":"contract IEthErc20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exitToMain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"},{"internalType":"contract IEthErc20","name":"ethErc20Address","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IEthErc20","name":"newEthErc20Address","type":"address"}],"name":"setEthErc20Address","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"token_manager_eth_address":"0xd2AaA00400000000000000000000000000000000","token_manager_linker_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"connectSchain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"disconnectSchain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasSchain","outputs":[{"internalType":"bool","name":"connected","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ITokenManager","name":"tokenManager","type":"address"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxyAddress","type":"address"},{"internalType":"address","name":"linker","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"linkerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ITokenManager","name":"newTokenManager","type":"address"}],"name":"registerTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ITokenManager","name":"tokenManagerAddress","type":"address"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenManagers","outputs":[{"internalType":"contract ITokenManager","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"token_manager_linker_address":"0xD2aAA00800000000000000000000000000000000"} \ No newline at end of file +{"ERC1155OnChain_abi":[{"inputs":[{"internalType":"string","name":"uri","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"TransferBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"TransferSingle","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"value","type":"string"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"URI","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"accounts","type":"address[]"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"balanceOfBatch","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"burnBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mintBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeBatchTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"uri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}],"ERC20OnChain_abi":[{"inputs":[{"internalType":"string","name":"contractName","type":"string"},{"internalType":"string","name":"contractSymbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"ERC721OnChain_abi":[{"inputs":[{"internalType":"string","name":"contractName","type":"string"},{"internalType":"string","name":"contractSymbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"tokenUri","type":"string"}],"name":"setTokenURI","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}],"community_locker_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"ActivateUser","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"constantHash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"previousValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"ConstantUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"LockUser","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"CONSTANT_SETTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"activeUsers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"checkAllowedToSendMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"communityPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasPriceTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newTokenManagerLinker","type":"address"},{"internalType":"address","name":"newCommunityPool","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastMessageTimeStamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mainnetGasPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"gasPrice","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"components":[{"internalType":"uint256[2]","name":"blsSignature","type":"uint256[2]"},{"internalType":"uint256","name":"hashA","type":"uint256"},{"internalType":"uint256","name":"hashB","type":"uint256"},{"internalType":"uint256","name":"counter","type":"uint256"}],"internalType":"struct IMessageProxy.Signature","name":"","type":"tuple"}],"name":"setGasPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newTimeLimitPerMessage","type":"uint256"}],"name":"setTimeLimitPerMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"timeLimitPerMessage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"community_locker_address":"0xD2aaa00300000000000000000000000000000000","eth_erc20_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"BURNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"forceBurn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenManagerEthAddress","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"eth_erc20_address":"0xD2Aaa00700000000000000000000000000000000","key_storage_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FN_NUM_GET_CONFIG_VARIABLE_UINT256","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FN_NUM_GET_CURRENT_BLS_PUBLIC_KEY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FREE_MEM_PTR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBlsCommonPublicKey","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"internalType":"struct IFieldOperations.Fp2Point","name":"x","type":"tuple"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"internalType":"struct IFieldOperations.Fp2Point","name":"y","type":"tuple"}],"internalType":"struct IFieldOperations.G2Point","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"key_storage_address":"0xd2aaa00200000000000000000000000000000000","message_proxy_chain_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"contractAddress","type":"address"}],"name":"ExtraContractRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"contractAddress","type":"address"}],"name":"ExtraContractRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldValue","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newValue","type":"uint256"}],"name":"GasLimitWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"dstChainHash","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"msgCounter","type":"uint256"},{"indexed":true,"internalType":"address","name":"srcContract","type":"address"},{"indexed":false,"internalType":"address","name":"dstContract","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"OutgoingMessage","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"msgCounter","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"}],"name":"PostMessageError","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"oldVersion","type":"string"},{"indexed":false,"internalType":"string","name":"newVersion","type":"string"}],"name":"VersionUpdated","type":"event"},{"inputs":[],"name":"CHAIN_CONNECTOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CONSTANT_SETTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ETHERBASE","outputs":[{"internalType":"contract IEtherbaseUpgradeable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_CONTRACT_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MESSAGES_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_BALANCE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REVERT_REASON_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"}],"name":"addConnectedChain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"connectedChains","outputs":[{"internalType":"uint256","name":"incomingMessageCounter","type":"uint256"},{"internalType":"uint256","name":"outgoingMessageCounter","type":"uint256"},{"internalType":"bool","name":"inited","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"schainHash","type":"bytes32"}],"name":"getContractRegisteredLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"internalType":"uint256","name":"from","type":"uint256"},{"internalType":"uint256","name":"to","type":"uint256"}],"name":"getContractRegisteredRange","outputs":[{"internalType":"address[]","name":"contractsInRange","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"fromSchainName","type":"string"}],"name":"getIncomingMessagesCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"}],"name":"getOutgoingMessagesCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IKeyStorage","name":"blsKeyStorage","type":"address"},{"internalType":"string","name":"schainName","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllRegisteredContracts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newGasLimit","type":"uint256"}],"name":"initializeMessageProxy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"isConnectedChain","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"schainHash","type":"bytes32"},{"internalType":"address","name":"contractAddress","type":"address"}],"name":"isContractRegistered","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"keyStorage","outputs":[{"internalType":"contract IKeyStorage","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageInProgress","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"fromChainName","type":"string"},{"internalType":"uint256","name":"startingCounter","type":"uint256"},{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"destinationContract","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IMessageProxy.Message[]","name":"messages","type":"tuple[]"},{"components":[{"internalType":"uint256[2]","name":"blsSignature","type":"uint256[2]"},{"internalType":"uint256","name":"hashA","type":"uint256"},{"internalType":"uint256","name":"hashB","type":"uint256"},{"internalType":"uint256","name":"counter","type":"uint256"}],"internalType":"struct IMessageProxy.Signature","name":"signature","type":"tuple"}],"name":"postIncomingMessages","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"targetChainHash","type":"bytes32"},{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postOutgoingMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"},{"internalType":"address","name":"extraContract","type":"address"}],"name":"registerExtraContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"extraContract","type":"address"}],"name":"registerExtraContractForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"}],"name":"removeConnectedChain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"chainName","type":"string"},{"internalType":"address","name":"extraContract","type":"address"}],"name":"removeExtraContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"extraContract","type":"address"}],"name":"removeExtraContractForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newGasLimit","type":"uint256"}],"name":"setNewGasLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newVersion","type":"string"}],"name":"setVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"dstChainHash","type":"bytes32"},{"internalType":"uint256","name":"msgCounter","type":"uint256"},{"internalType":"address","name":"srcContract","type":"address"},{"internalType":"address","name":"dstContract","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct IMessageProxyForSchain.OutgoingMessageData","name":"message","type":"tuple"}],"name":"verifyOutgoingMessageData","outputs":[{"internalType":"bool","name":"isValidMessage","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hashedMessage","type":"bytes32"},{"components":[{"internalType":"uint256[2]","name":"blsSignature","type":"uint256[2]"},{"internalType":"uint256","name":"hashA","type":"uint256"},{"internalType":"uint256","name":"hashB","type":"uint256"},{"internalType":"uint256","name":"counter","type":"uint256"}],"internalType":"struct IMessageProxy.Signature","name":"signature","type":"tuple"}],"name":"verifySignature","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}],"message_proxy_chain_address":"0xd2AAa00100000000000000000000000000000000","proxy_admin_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"},{"internalType":"address","name":"newAdmin","type":"address"}],"name":"changeProxyAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"}],"name":"getProxyAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"}],"name":"getProxyImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"},{"internalType":"address","name":"implementation","type":"address"}],"name":"upgrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract TransparentUpgradeableProxy","name":"proxy","type":"address"},{"internalType":"address","name":"implementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeAndCall","outputs":[],"stateMutability":"payable","type":"function"}],"proxy_admin_address":"0xd2aAa00000000000000000000000000000000000","token_manager_erc1155_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"indexed":true,"internalType":"address","name":"erc1155OnSchain","type":"address"}],"name":"ERC1155TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"indexed":true,"internalType":"address","name":"erc1155OnSchain","type":"address"}],"name":"ERC1155TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"ERC1155TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"indexed":true,"internalType":"address","name":"erc1155OnSchain","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"ERC1155TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainAlias","type":"string"},{"internalType":"address","name":"erc1155OnMainnet","type":"address"},{"internalType":"address","name":"erc1155OnSchain","type":"address"}],"name":"addERC1155TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC1155OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc1155","outputs":[{"internalType":"contract ERC1155OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc1155","outputs":[{"internalType":"contract ERC1155OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exitToMainERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"exitToMainERC1155Batch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferToSchainERC1155","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"transferToSchainERC1155Batch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferredAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"token_manager_erc1155_address":"0xD2aaA00900000000000000000000000000000000","token_manager_erc20_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc20OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc20OnSchain","type":"address"}],"name":"ERC20TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc20OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc20OnSchain","type":"address"}],"name":"ERC20TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc20OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc20OnSchain","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainAlias","type":"string"},{"internalType":"address","name":"erc20OnMainChain","type":"address"},{"internalType":"address","name":"erc20OnSchain","type":"address"}],"name":"addERC20TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc20","outputs":[{"internalType":"contract ERC20OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc20","outputs":[{"internalType":"contract ERC20OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exitToMainERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Upgradeable","name":"","type":"address"}],"name":"totalSupplyOnMainnet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferToSchainERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"transferredAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],"token_manager_erc20_address":"0xD2aAA00500000000000000000000000000000000","token_manager_erc721_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainAlias","type":"string"},{"internalType":"address","name":"erc721OnMainChain","type":"address"},{"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"addERC721TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"exitToMainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferToSchainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferredAmount","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}],"token_manager_erc721_address":"0xD2aaa00600000000000000000000000000000000","token_manager_erc721_with_metadata_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"ERC721TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"contractOnMainnet","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReady","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"chainHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"erc721OnMainChain","type":"address"},{"indexed":true,"internalType":"address","name":"erc721OnSchain","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721TokenReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetChainAlias","type":"string"},{"internalType":"address","name":"erc721OnMainChain","type":"address"},{"internalType":"address","name":"erc721OnSchain","type":"address"}],"name":"addERC721TokenByOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"name":"addedClones","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"clonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"deprecatedClonesErc721","outputs":[{"internalType":"contract ERC721OnChain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"exitToMainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"contracts","type":"address[]"}],"name":"initializeAllClonesERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"targetSchainName","type":"string"},{"internalType":"address","name":"contractOnMainnet","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferToSchainERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferredAmount","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}],"token_manager_erc721_with_metadata_address":"0xd2AaA00a00000000000000000000000000000000","token_manager_eth_abi":[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldValue","type":"address"},{"indexed":false,"internalType":"address","name":"newValue","type":"address"}],"name":"DepositBoxWasChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"AUTOMATIC_DEPLOY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"},{"internalType":"address","name":"newTokenManager","type":"address"}],"name":"addTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"automaticDeploy","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"changeDepositBoxAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"communityLocker","outputs":[{"internalType":"contract ICommunityLocker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositBox","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableAutomaticDeploy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ethErc20","outputs":[{"internalType":"contract IEthErc20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"exitToMain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newChainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"},{"internalType":"contract IEthErc20","name":"ethErc20Address","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newSchainName","type":"string"},{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxy","type":"address"},{"internalType":"contract ITokenManagerLinker","name":"newIMALinker","type":"address"},{"internalType":"contract ICommunityLocker","name":"newCommunityLocker","type":"address"},{"internalType":"address","name":"newDepositBox","type":"address"}],"name":"initializeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"fromChainHash","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"postMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IEthErc20","name":"newEthErc20Address","type":"address"}],"name":"setEthErc20Address","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenManagerLinker","outputs":[{"internalType":"contract ITokenManagerLinker","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenManagers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"token_manager_eth_address":"0xd2AaA00400000000000000000000000000000000","token_manager_linker_abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_HASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_NAME","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REGISTRAR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"connectSchain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"disconnectSchain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"schainName","type":"string"}],"name":"hasSchain","outputs":[{"internalType":"bool","name":"connected","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ITokenManager","name":"tokenManager","type":"address"}],"name":"hasTokenManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IMessageProxyForSchain","name":"newMessageProxyAddress","type":"address"},{"internalType":"address","name":"linker","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"linkerAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"messageProxy","outputs":[{"internalType":"contract IMessageProxyForSchain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ITokenManager","name":"newTokenManager","type":"address"}],"name":"registerTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ITokenManager","name":"tokenManagerAddress","type":"address"}],"name":"removeTokenManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenManagers","outputs":[{"internalType":"contract ITokenManager","name":"","type":"address"}],"stateMutability":"view","type":"function"}],"token_manager_linker_address":"0xD2aAA00800000000000000000000000000000000"} \ No newline at end of file diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts new file mode 100644 index 0000000..a2bbc3a --- /dev/null +++ b/src/store/MetaportState.ts @@ -0,0 +1,326 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file MetaportState.ts + * @copyright SKALE Labs 2023-Present + */ + +import debug from 'debug'; + +import { MainnetChain, SChain } from '@skalenetwork/ima-js' +import { create } from 'zustand' + +import MetaportCore from '../core/metaport' +import * as interfaces from '../core/interfaces'; +import * as dataclasses from '../core/dataclasses'; +import { getEmptyTokenDataMap } from '../core/tokens/helper'; +import { MAINNET_CHAIN_NAME, DEFAULT_ERROR_MSG } from '../core/constants'; +import { getStepsMetadata } from '../core/transfer_steps'; +import { ACTIONS } from '../core/actions'; +import { WalletClient } from 'viem'; + + +debug.enable('*'); +const log = debug('metaport:state'); + + +interface MetaportState { + mainnetChain: MainnetChain + setMainnetChain: (mainnet: MainnetChain) => void + sChain1: SChain + setSChain1: (schain: SChain) => void + sChain2: SChain + setSChain2: (schain: SChain) => void + + mpc: MetaportCore, + setMpc: (mpc: MetaportCore) => void + + amount: string + setAmount: (amount: string, address: `0x${string}`) => void + + tokenId: number + setTokenId: (tokenId: number) => void + + execute: ( + address: string, + switchNetwork: (chainId: number) => void, + walletClient: WalletClient + ) => void + check: (amount: string, address: `0x${string}`) => void + + currentStep: number + setCurrentStep: (currentStep: number) => void + + stepsMetadata: dataclasses.StepMetadata[] + setStepsMetadata: (steps: dataclasses.StepMetadata[]) => void + + chainName1: string, + chainName2: string, + + setChainName1: (name: string) => void + setChainName2: (name: string) => void + + tokens: interfaces.TokenDataTypesMap + + token: dataclasses.TokenData + setToken: (token: dataclasses.TokenData) => void + + tokenContracts: interfaces.TokenContractsMap + tokenBalances: interfaces.TokenBalancesMap + updateTokenBalances: (address: string) => Promise + + amountErrorMessage: string + setAmountErrorMessage: (amountErrorMessage: string) => void + + errorMessage: dataclasses.ErrorMessage + setErrorMessage: (amountErrorMessage: dataclasses.ErrorMessage) => void + + actionBtnDisabled: boolean + setActionBtnDisabled: (actionBtnDisabled: boolean) => void + + loading: boolean + setLoading: (loading: boolean) => void + + transferInProgress: boolean + setTransferInProgress: (loading: boolean) => void + + btnText: string + setBtnText: (btnText: string) => void + + errorMessageClosedFallback: () => void + startOver: () => void +} + + +export const useMetaportStore = create()((set, get) => ({ + mainnetChain: null, + setMainnetChain: (mainnet: MainnetChain) => set(() => ({ mainnetChain: mainnet })), + + sChain1: null, + setSChain1: (schain: SChain) => set(() => ({ sChain1: schain })), + + sChain2: null, + setSChain2: (schain: SChain) => set(() => ({ sChain2: schain })), + + mpc: null, + setMpc: (mpc: MetaportCore) => set(() => ({ mpc: mpc })), + + tokenId: null, + setTokenId: (tokenId: number) => set(() => { + return { + tokenId: tokenId + } + }), + + amount: '', + setAmount: (amount: string, address: `0x${string}`) => set((state) => { + state.check(amount, address); + return { + amount: amount + } + }), + + + execute: async (address: string, switchNetwork: any, walletClient: WalletClient) => { + log('Running execute'); + if (get().stepsMetadata[get().currentStep]) { + set({ + loading: true, + transferInProgress: true + }) + try { + const stepMetadata = get().stepsMetadata[get().currentStep]; + const actionClass = ACTIONS[stepMetadata.type]; + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + get().amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + switchNetwork, + walletClient + ).execute(); + } catch (err) { + console.error(err); + const msg = err.message ? err.message : DEFAULT_ERROR_MSG; + set({ + errorMessage: new dataclasses.TransactionErrorMessage( + msg, + get().errorMessageClosedFallback + ) + }); + return; + } finally { + set({ loading: false }); + } + set({ + transferInProgress: get().currentStep + 1 !== get().stepsMetadata.length, + currentStep: get().currentStep + 1, + + }); + } + }, + + errorMessageClosedFallback() { + set({ + loading: false, + errorMessage: undefined, + transferInProgress: get().currentStep !== 0 + }); + }, + + startOver() { + set({ + loading: false, + errorMessage: undefined, + amount: '', + tokenId: null, + currentStep: 0, + transferInProgress: false + }); + }, + + check: async (amount: string, address: string) => { + if (get().stepsMetadata[get().currentStep]) { + set({ + loading: true, + btnText: 'Checking balance...' + }); + const stepMetadata = get().stepsMetadata[get().currentStep]; + const actionClass = ACTIONS[stepMetadata.type]; + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + null, + null + ).preAction(); + // console.log(); + // console.log('going to check amount!!!'); + } + set({ loading: false }); + }, + + currentStep: 0, + setCurrentStep: (currentStep: number) => set(() => ({ currentStep: currentStep })), + + stepsMetadata: [], + setStepsMetadata: (steps: dataclasses.StepMetadata[]) => set(() => ({ stepsMetadata: steps })), + + chainName1: '', + chainName2: '', + + setChainName1: (name: string) => set((state) => { + // updateState( + // name, + // state.chainName2 + // ) + + const updState = {}; + if (name === MAINNET_CHAIN_NAME) { + updState['mainnetChain'] = state.mpc.mainnet(); + } else { + updState['sChain1'] = state.mpc.schain(name); + } + const provider = updState['mainnetChain'] ? updState['mainnetChain'].provider : updState['sChain1'].provider; + const tokens = state.mpc.tokens(name); + const tokenContracts = state.mpc.tokenContracts( + tokens, + dataclasses.TokenType.erc20, + name, + provider + ); + return { + currentStep: 0, + token: null, + chainName1: name, + tokens: tokens, + tokenContracts: tokenContracts, + ...updState + } + }), + setChainName2: (name: string) => set((state) => { + const updState = {}; + if (name === MAINNET_CHAIN_NAME) { + updState['mainnetChain'] = state.mpc.mainnet(); + } else { + updState['sChain2'] = state.mpc.schain(name); + } + return { + currentStep: 0, + token: null, + chainName2: name, + tokens: state.mpc.tokens(state.chainName1, name), + stepsMetadata: getStepsMetadata( + get().mpc.config, + get().token, + name + ), + ...updState + } + }), + + tokens: getEmptyTokenDataMap(), + + token: null, + setToken: (token: dataclasses.TokenData) => set(() => ({ + token: token, + stepsMetadata: getStepsMetadata( + get().mpc.config, + token, + get().chainName2 + ) + })), + + tokenContracts: {}, + tokenBalances: {}, + + updateTokenBalances: async (address: string) => { + const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address); + set({ tokenBalances: tokenBalances }); + }, + + amountErrorMessage: null, + setAmountErrorMessage: (em: string) => set(() => ({ amountErrorMessage: em })), + + errorMessage: null, + setErrorMessage: (em: dataclasses.ErrorMessage) => set(() => ({ errorMessage: em })), + + actionBtnDisabled: false, + setActionBtnDisabled: (disabled: boolean) => set(() => ({ actionBtnDisabled: disabled })), + + loading: false, + setLoading: (loading: boolean) => set(() => ({ loading: loading })), + + transferInProgress: false, + setTransferInProgress: (inProgress: boolean) => set(() => ({ transferInProgress: inProgress })), + + btnText: null, + setBtnText: (btnText: string) => set(() => ({ btnText: btnText })) +})); diff --git a/src/store/Store.ts b/src/store/Store.ts new file mode 100644 index 0000000..59e4aa2 --- /dev/null +++ b/src/store/Store.ts @@ -0,0 +1,75 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file Store.ts + * @copyright SKALE Labs 2023-Present + */ + +import { create } from 'zustand' + +import * as interfaces from '../core/interfaces'; + +interface UIState { + theme: interfaces.MetaportTheme + setTheme: (theme: interfaces.MetaportTheme) => void + open: boolean, + setOpen: (isOpen: boolean) => void +} + + +export const useUIStore = create()((set) => ({ + theme: null, + setTheme: (theme: interfaces.MetaportTheme) => set(() => ({ theme: theme })), + open: false, + setOpen: (isOpen: boolean) => set(() => ({ open: isOpen })), +})); + + +interface CollapseState { + expandedFrom: string | false, + setExpandedFrom: (expanded: string | false) => void, + expandedTo: string | false, + setExpandedTo: (expanded: string | false) => void + + expandedTokens: string | false, + setExpandedTokens: (expanded: string | false) => void +} + + +export const useCollapseStore = create()((set) => ({ + expandedFrom: false, + setExpandedFrom: (expanded: string | false) => set(() => ({ + expandedFrom: expanded, + expandedTo: false, + expandedTokens: false + })), + expandedTo: false, + setExpandedTo: (expanded: string | false) => set(() => ({ + expandedTo: expanded, + expandedFrom: false, + expandedTokens: false + })), + expandedTokens: false, + setExpandedTokens: (expanded: string | false) => set(() => ({ + expandedTokens: expanded, + expandedFrom: false, + expandedTo: false + })) +})); + diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss new file mode 100644 index 0000000..24f2eb1 --- /dev/null +++ b/src/styles/_variables.scss @@ -0,0 +1,14 @@ +$sk-border-radius-outter: 30px; +$sk-border-radius: 25px; + +$sk-paper-color: rgb(136 135 135 / 15%); +$sk-gray-background-color: rgba(161, 161, 161, 0.2); + + +$sk-btn-height: 47px; + +$sk-disabled-dark: rgba(255, 255, 255, 0.5); +$sk-disabled-light: rgba(0, 0, 0, 0.38); + +$sk-secondary-dark: rgb(255 255 255 / 65%); +$sk-secondary-light: rgb(0 0 0 / 65%); \ No newline at end of file diff --git a/src/styles/common.scss b/src/styles/common.scss new file mode 100644 index 0000000..e01c124 --- /dev/null +++ b/src/styles/common.scss @@ -0,0 +1,233 @@ +@import './variables'; + +.flex { + display: flex; + vertical-align: middle; +} + +.flexRight { + justify-content: end; +} + +.flexCentered { + align-items: center; + justify-content: center; +} + +.flexCenteredVert { + align-items: center; +} + +.flexGrow { + flex-grow: 1; +} + +.flexRow { + flex-direction: row; + flex-wrap: wrap; +} + +.marg10 { + margin: 10px !important; +} + +.padd10 { + padding: 10px !important; +} + + +.margTop10 { + margin-top: 10px !important; +} + +.margTop20 { + margin-top: 20px !important; +} + +.margTop40 { + margin-top: 40px !important; +} + + +.margLeft5 { + margin-left: 5px !important; +} + +.margLeft10 { + margin-left: 10px !important; +} + +.margLeft20 { + margin-left: 20px !important; +} + +.margRi20 { + margin-right: 20px !important; +} + +.margRi10 { + margin-right: 10px !important; +} + +.margRi5 { + margin-right: 5px !important; +} + +.margBottMin10 { + margin-bottom: -10px !important; +} + +.margBottMin15 { + margin-bottom: -15px !important; +} + +.marg-top-20 { + margin-top: 20px !important; +} + +.margTop20Pt { + margin-top: 20pt !important; +} + +.marg-top-30 { + margin-top: 30px !important; +} + +.margBott20 { + margin-bottom: 20px !important; +} + +.margBott40 { + margin-bottom: 40px !important; +} + +.margBott15 { + margin-bottom: 15px !important; +} + +.margBott10 { + margin-bottom: 10px !important; +} + +.marg-bott-40 { + margin-bottom: 40px !important; +} + +.margBott5 { + margin-bottom: 5px !important; +} + +.margTop5 { + margin-top: 5px !important; +} + +.noMargTop { + margin-top: 0 !important; +} + +.noMargBott { + margin-bottom: 0 !important; +} + +.noMarg { + margin: 0 !important; +} + +.paddTop10 { + padding-top: 10px !important; +} + +.paddBott10 { + padding-bottom: 10px !important; +} + +.noPadd { + padding: 0 !important; +} + +.capitalize { + text-transform: capitalize !important; +} + +// fonts + + +.p { + margin: 0; + text-transform: none; + line-height: 1.6 !important; + font-weight: 500; +} + +.p500 { + font-weight: 500 !important; +} + +.p600 { + font-weight: 600 !important; +} + +.uppercase { + text-transform: uppercase !important; +} + +.p1 { + font-size: 1.3rem !important; + letter-spacing: 0.03857em !important; +} + +.p2 { + font-size: 0.9025rem !important; + letter-spacing: 0.03857em !important; +} + +.p3 { + font-size: 0.8025rem !important; + letter-spacing: 0.02857em !important; +} + +.p4 { + font-size: 0.7025rem !important; + letter-spacing: 0.04857em !important; +} + +.darkTheme { + .pMain { + color: white !important; + } + + .pSecondary { + color: $sk-secondary-dark !important; + } + + .pDisabled { + color: $sk-disabled-dark !important; + } +} + +.lightTheme { + .pMain { + color: black !important; + } + + .pSecondary { + color: $sk-secondary-light !important; + } + + .pDisabled { + color: $sk-disabled-light !important; + } +} + +.fullWidth { + width: 100% !important; +} + +.textCentered { + text-align: center; +} + + +:global([rk-chain-button]) { + display: none !important; +} \ No newline at end of file diff --git a/src/styles/styles.scss b/src/styles/styles.scss new file mode 100644 index 0000000..f949f04 --- /dev/null +++ b/src/styles/styles.scss @@ -0,0 +1,262 @@ +@import './variables'; + +.paper { + padding: 10px; + border-radius: $sk-border-radius; +} + +.paperGrey { + // background-color: $sk-gray-background-color !important; + background-color: $sk-paper-color !important; +} + +.fullHeight { + height: 100%; +} + +.popper { + width: 390px; + max-height: calc(100vh - 110pt); + padding: 10pt; + border-radius: $sk-border-radius-outter; + border: 1px rgba(127, 127, 127, .2705882353) solid; + + :global(.Mui-disabled) { + :global(.MuiAccordionSummary-expandIconWrapper) { + display: none !important; + } + opacity: 1 !important; + } +} + +.darkTheme { + .skaleLogoSm { + filter: invert(1); + } + + .defaultChainIcon { + color: 'hsl(120deg 2% 88%)'; + } + + .sk__btnSwitch { + :global(.Mui-disabled) { + background-color: $sk-paper-color !important; + } + } + + :global .MuiIconButton-root { + svg { + color: #000000; + } + } + + :global(.MuiIconButton-root.Mui-disabled) { + svg { + color: rgba(255, 255, 255, 0.3); + } + } + + :global(.Mui-completed) { + color: $sk-disabled-dark; + } + +} + +.lightTheme { + .skaleLogoLg { + filter: invert(1); + } + + .defaultChainIcon { + color: 'hsl(120deg 2% 88%)'; + } + + .sk__btnSwitch { + :global(.Mui-disabled) { + background-color: rgb(224 224 224) !important; + } + } + + :global(.MuiIconButton-root) { + svg { + color: #ffffff; + } + } + + :global(.MuiIconButton-root.Mui-disabled) { + svg { + color: rgba(0, 0, 0, 0.26); + } + } + + :global(.Mui-completed) { + color: $sk-disabled-light; + } +} + +.skaleBtnHidden { + display: none !important; +} + +.skaleBtn { + border: 1px #7f7f7f45 solid; + box-shadow: none !important; +} + + +.skaleLogoSm { + width: 18pt; +} + + +button { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif !important; + -webkit-font-smoothing: antialiased; +} + + +.imaWidgetBody { + border-radius: $sk-border-radius-outter !important; + position: fixed; + + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif !important; + -webkit-font-smoothing: antialiased; + + :global(.MuiAccordion-root) { + background-color: transparent !important; + } + + :global(.MuiAccordionDetails-root) { + padding: 0 !important; + } + + :global(.MuiButton-root) { + font-weight: 600 !important; + box-shadow: none !important; + border-radius: $sk-border-radius !important; + } +} + +.chainIconxs { + width: 17px; + height: 17px; + + svg { + width: 17px; + height: 17px; + } +} + +.chainIconsm { + width: 26px; + height: 26px; + + svg { + width: 26px; + height: 26px; + } +} + +.chainIconmd { + width: 35px; + height: 35px; + + svg { + width: 35px; + height: 35px; + } +} + +.chainIconlg { + width: 45px; + height: 45px; +} + +.sk__chainApps { + + border-left: 1px #bdbdbd solid; + margin-left: 30px !important; + + // background-color: $sk-paper-color; + // border-radius: $sk-border-radius !important; + // padding: 5px; +} + + + +.sk__btnSwitch { + width: 100%; + text-align: center; + margin-top: -13pt; + margin-bottom: -13pt; + + button { + border: 13px #1a1a1a solid; + } + + :global(svg) { + width: 14pt; + height: 14pt; + } +} + +.btnSwitchAnimation { + transition: all 0.2s ease-in-out; +} + +.btnAction { + width: 100%; + text-transform: none !important; + font-size: 0.8025rem !important; + line-height: 1.6 !important; + letter-spacing: 0.02857em !important; + font-weight: 600 !important; + padding-top: 0.8em !important; + padding-bottom: 0.8em !important; + min-height: $sk-btn-height !important; +} + +.btnChain { + width: 100%; + text-align: left; + text-transform: none !important; + font-size: 0.6525rem !important; + line-height: 1.6; + letter-spacing: 0.02857em; +} + + +@keyframes resize { + + 0%, + 100% { + transform: scale(1); + /* Initial and final size: 100% (no change) */ + } + + 50% { + transform: scale(1.1); + /* At half-time, size becomes 110% (10% larger) */ + } +} + +.skMovingDiv { + animation: resize 3s infinite ease-in-out; +} + +.infoIcon { + svg { + height: 45pt; + width: 100%; + } +} + +:global(.spin) { + transform: rotate(360deg); +} + + + +.accordionSummary { + padding: 10px 26px !important; +} \ No newline at end of file diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 3e6090d..9c9c4e8 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -13,4 +13,25 @@ declare module "*.png" { export default content; } -declare module '*.scss'; \ No newline at end of file +declare module '*.scss'; + +declare module 'node' { + interface NodeRequire { + context: ( + directory: string, + useSubdirectories: boolean, + regExp: RegExp, + mode?: string + ) => any; + } +} + +declare namespace NodeJS { + interface Global { + require: NodeRequire; + } +} + +interface NodeRequire { + context: (directory: string, useSubdirectories: boolean, regExp: RegExp, mode?: string) => any; +} diff --git a/test/TestTest.ts b/test/TestTest.ts new file mode 100644 index 0000000..5f6f3e9 --- /dev/null +++ b/test/TestTest.ts @@ -0,0 +1,105 @@ + +import { Wallet } from "ethers"; + +import MetaportCore from '../src/core/metaport' +import { TokenType } from "../src/core/dataclasses"; +import { MainnetChain } from "@skalenetwork/ima-js"; + + + +const METAPORT_CONFIG = require('../src/metadata/metaportConfigStaging.json'); +METAPORT_CONFIG.mainnetEndpoint = 'https://cloudflare-eth.com/'; + + +describe("BASE LIB Test", () => { + let wallet: Wallet; + + before(async () => { + + + }); + + it("Requests ETH balance for Mainnet chain", async () => { + const mpc = new MetaportCore(METAPORT_CONFIG); + + const chain1 = 'mainnet'; + const chain2 = 'elated-tan-skat'; + const token = '_SKL_1'; + const tokenType = TokenType.erc20; + + const address = ''; + + const tokens = mpc.tokens(chain1, chain2); + + console.log(tokens); + + // const provider1 = mpc.provider(chain1); + // const provider2 = mpc.provider(chain2); + + const endp1 = mpc.endpoint(chain1); + const endp2 = mpc.endpoint(chain2); + + const mainnetChain = mpc.mainnet(); + const sChain = mpc.schain(chain2); + + console.log('======'); + console.log(endp1); + console.log(endp2); + console.log('======'); + + const bnr1 = await mainnetChain.provider.getBlockNumber(); + const bnr2 = await sChain.provider.getBlockNumber(); + + // const contract2 = mpc.tokenContract( + // token.erc20[tokenKeyname], + // provider2 + // ); + + const contract2 = mpc.tokenContract( + chain2, + token, + tokenType, + sChain.provider + ); + + console.log('-----'); + console.log(bnr1); + console.log(bnr2); + console.log('-------') + console.log(await contract2.balanceOf(address)); + console.log('-----'); + + console.log('-----'); + console.log(tokens.erc20); + + const tokenContracts = mpc.tokenContracts( + tokens, + TokenType.erc20, + chain1, + mainnetChain.provider + ); + const tokenBalances = await mpc.tokenBalances( + tokenContracts, + address + ); + + const tokenContractsDest = mpc.tokenContracts( + tokens, + TokenType.erc20, + chain2, + sChain.provider + ); + const tokenBalancesDest = await mpc.tokenBalances( + tokenContractsDest, + address + ); + + console.log('BALANCES:'); + console.log(tokenBalances); + console.log(tokenBalancesDest); + + // console.log(await sChain.ethBalance(address)); + // console.log(await mainnetChain.ethBalance(address)); + }); + +}); \ No newline at end of file diff --git a/test/core/tokensTest.ts b/test/core/tokensTest.ts deleted file mode 100644 index 5fcbb3d..0000000 --- a/test/core/tokensTest.ts +++ /dev/null @@ -1,52 +0,0 @@ -import 'mocha'; -import { SChain, MainnetChain } from '@skalenetwork/ima-js'; - -import { initSChain } from '../../src/core/core'; -import { CHAIN_NAME_SCHAIN, CHAIN_NAME_SCHAIN_2, NETWORK_NAME } from '../test_utils'; - -import { getAvailableTokens } from '../../src/core/tokens'; - - -describe("Test for tokens core module", () => { - let sChainName1: string; - let sChainName2: string; - - let sChain1: SChain; - let sChain2: SChain; - let mainnet: MainnetChain; - - let tokens: Object; - - before(async () => { - sChain1 = initSChain(NETWORK_NAME, CHAIN_NAME_SCHAIN); - sChain2 = initSChain(NETWORK_NAME, CHAIN_NAME_SCHAIN_2); - - tokens = { - 'rapping-zuben-elakrab': { - 'erc20': { - 'skEth': { - 'address': '0xD8AA84EbC1CfafFa4968cDd493235A0ae0872b73', - 'name': 'skETH', - 'wraps': { - 'address': '0xD2Aaa00700000000000000000000000000000000', - 'symbol': 'ETHC' - } - } - } - } - } - }); - - it.only("Test getAvailableTokens", async () => { - const availableTokens = await getAvailableTokens( - mainnet, - sChain1, - sChain2, - CHAIN_NAME_SCHAIN, - CHAIN_NAME_SCHAIN_2, - tokens, - true - ); - console.log(availableTokens); - }) -}) \ No newline at end of file diff --git a/test/test_utils.ts b/test/test_utils.ts index a4f66d1..e69de29 100644 --- a/test/test_utils.ts +++ b/test/test_utils.ts @@ -1,14 +0,0 @@ -import Web3 from 'web3'; -import { SChain, MainnetChain } from '@skalenetwork/ima-js'; - -import * as dotenv from "dotenv"; - - -dotenv.config(); - -export const CHAIN_NAME_SCHAIN = (process.env["CHAIN_NAME_SCHAIN"] as string); -export const CHAIN_NAME_SCHAIN_2 = (process.env["CHAIN_NAME_SCHAIN_2"] as string); - -export const MAINNET_CHAIN_NAME = 'Mainnet'; - -export const NETWORK_NAME = (process.env["NETWORK_NAME"] as string); diff --git a/tsconfig.json b/tsconfig.json index 0cdd4da..65c9c45 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "esModuleInterop": true, "baseUrl": "src", "types": ["node", "webpack-env", "mocha"], - "typeRoots": ["./src/types"], + "typeRoots": ["./src/types", "./node_modules/@types"], "noUnusedLocals": true, /* Report errors on unused locals. */ "noUnusedParameters": true /* Report errors on unused parameters. */ }, diff --git a/webpack.config.js b/webpack.config.js index 5ed46f0..a3417b5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -54,31 +54,51 @@ module.exports = { // }, // ], // }, + { - test: /\.s[ac]ss$/i, - use: [ - - // Creates `style` nodes from JS strings - { - loader: 'style-loader', - options: { - esModule: false, - }, - }, - // Translates CSS into CommonJS - { - loader: 'css-loader', - options: { - modules: true, - sourceMap: true, - // importLoaders: 2, - // esModule: false - } - }, - // Compiles Sass to CSS - "sass-loader", - ], - } + test: /\.css$/, + use: ['style-loader', 'css-loader', 'postcss-loader'], + }, + { + test: /\.scss$/, + use: ["style-loader", { + loader: 'css-loader', + options: { + modules: true, + sourceMap: true, + importLoaders: 2, + esModule: false + } + }, "sass-loader"], + include: path.resolve(__dirname, "../") + }, + + + // { + // test: /\.s[ac]ss$/i, + // use: [ + + // // Creates `style` nodes from JS strings + // { + // loader: 'style-loader', + // options: { + // esModule: false, + // }, + // }, + // // Translates CSS into CommonJS + // { + // loader: 'css-loader', + // options: { + // modules: true, + // sourceMap: true, + // // importLoaders: 2, + // // esModule: false + // } + // }, + // // Compiles Sass to CSS + // "sass-loader", + // ], + // } ] }, resolve: { From 6e646b1b94f2616b29a1c9aaf7a2af6ad896020e Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 7 Aug 2023 19:51:08 +0100 Subject: [PATCH 004/110] Update skale-network --- skale-network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale-network b/skale-network index 84e54b4..5b00cae 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 84e54b423f920c42efaca79274e16789fc8cf0a7 +Subproject commit 5b00cae6736322f302c3a75331b19ee02b0a3596 From 517e45182ba94a0d7880e44fab4f02c721c6b784 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 7 Aug 2023 19:55:45 +0100 Subject: [PATCH 005/110] Fix path to regression icons --- src/core/metadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/metadata.ts b/src/core/metadata.ts index c43cf25..ca8d80d 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -39,7 +39,7 @@ const CHAIN_ICONS = { 'staging': importAll(require.context('../meta/staging/icons', false, /\.(png|jpe?g|svg)$/)), 'legacy': importAll(require.context('../meta/legacy/icons', false, /\.(png|jpe?g|svg)$/)), 'regression': importAll( - require.context('../../meta/regression/icons', false, /\.(png|jpe?g|svg)$/)) + require.context('../meta/regression/icons', false, /\.(png|jpe?g|svg)$/)) } From 186ba3b2d9b352b0554461031bac936a6616b2ee Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 7 Aug 2023 20:06:39 +0100 Subject: [PATCH 006/110] Comment out metamask wallet --- src/components/Widget/Widget.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Widget/Widget.tsx b/src/components/Widget/Widget.tsx index e2df2f7..ee7fd86 100644 --- a/src/components/Widget/Widget.tsx +++ b/src/components/Widget/Widget.tsx @@ -36,8 +36,7 @@ import { connectorsForWallets } from '@rainbow-me/rainbowkit'; import { injectedWallet, - coinbaseWallet, - metaMaskWallet + coinbaseWallet } from '@rainbow-me/rainbowkit/wallets'; import { MetaportConfig } from "core/interfaces" @@ -84,7 +83,7 @@ const connectors = connectorsForWallets([ { groupName: 'Supported Wallets', wallets: [ - metaMaskWallet({ chains, projectId: '' }), + // metaMaskWallet({ chains, projectId: '' }), injectedWallet({ chains }), coinbaseWallet({ chains, appName: 'TEST' }) ], From e3e00253666641555411dc8f535a25b2ec6fff8d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 8 Aug 2023 20:11:54 +0100 Subject: [PATCH 007/110] Update webpack-cli, move packages to dependencies, add regression --- package.json | 34 ++++++------ src/components/Widget/Widget.tsx | 8 ++- src/core/contracts.ts | 4 +- src/core/network.ts | 3 ++ src/core/wagmi_network.ts | 2 +- src/metadata/faucet.json | 1 - webpack.config.js | 93 +++++++++++--------------------- 7 files changed, 59 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index 01786cf..fb39f2e 100644 --- a/package.json +++ b/package.json @@ -34,19 +34,10 @@ "@babel/preset-env": "^7.21.4", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.4", - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@fontsource/roboto": "^4.5.7", - "@mui/icons-material": "^5.8.0", - "@mui/lab": "^5.0.0-alpha.88", - "@mui/material": "^5.8.1", - "@rainbow-me/rainbowkit": "^1.0.6", "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-image": "^2.1.1", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^11.2.1", - "@skalenetwork/ima-js": "2.0.0-custom.5", - "@skaleproject/pow-ethers": "0.3.2", "@storybook/addon-essentials": "^7.1.0", "@storybook/addon-interactions": "^7.1.0", "@storybook/addon-links": "^7.1.0", @@ -75,6 +66,7 @@ "html-webpack-plugin": "^5.5.0", "https-browserify": "^1.0.0", "identity-obj-proxy": "^3.0.0", + "mini-css-extract-plugin": "^2.7.6", "mocha": "^9.2.2", "node-polyfill-webpack-plugin": "^1.1.4", "postcss": "^8.4.14", @@ -103,12 +95,22 @@ "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", "typescript": "^5.1.6", - "viem": "^1.3.0", - "wagmi": "^1.3.9", - "webpack": "^5.73.0", - "webpack-cli": "^4.10.0", - "zustand": "^4.3.9" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4" }, "peerDependencies": {}, - "dependencies": {} -} + "dependencies": { + "@rainbow-me/rainbowkit": "^1.0.8", + "viem": "^1.5.3", + "wagmi": "^1.3.9", + "zustand": "^4.4.1", + "@skalenetwork/ima-js": "2.0.0-custom.5", + "@skaleproject/pow-ethers": "0.3.2", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@fontsource/roboto": "^4.5.7", + "@mui/icons-material": "^5.8.0", + "@mui/lab": "^5.0.0-alpha.88", + "@mui/material": "^5.8.1" + } +} \ No newline at end of file diff --git a/src/components/Widget/Widget.tsx b/src/components/Widget/Widget.tsx index ee7fd86..609180a 100644 --- a/src/components/Widget/Widget.tsx +++ b/src/components/Widget/Widget.tsx @@ -36,7 +36,8 @@ import { connectorsForWallets } from '@rainbow-me/rainbowkit'; import { injectedWallet, - coinbaseWallet + coinbaseWallet, + metaMaskWallet } from '@rainbow-me/rainbowkit/wallets'; import { MetaportConfig } from "core/interfaces" @@ -56,7 +57,6 @@ const { chains, webSocketPublicClient } = configureChains( [ mainnet, goerli, - constructWagmiChain('staging', "staging-legal-crazy-castor"), constructWagmiChain('staging', "staging-utter-unripe-menkar"), constructWagmiChain('staging', "staging-faint-slimy-achird"), @@ -83,7 +83,7 @@ const connectors = connectorsForWallets([ { groupName: 'Supported Wallets', wallets: [ - // metaMaskWallet({ chains, projectId: '' }), + metaMaskWallet({ chains, projectId: '' }), injectedWallet({ chains }), coinbaseWallet({ chains, appName: 'TEST' }) ], @@ -98,8 +98,6 @@ const wagmiConfig = createConfig({ }); - - export default function Widget(props: { config: MetaportConfig }) { diff --git a/src/core/contracts.ts b/src/core/contracts.ts index 315400d..f9e7b2c 100644 --- a/src/core/contracts.ts +++ b/src/core/contracts.ts @@ -33,6 +33,7 @@ import sFuelWrapperAbi from '../metadata/sfuel_wrapper_abi.json'; import mainnetAddresses from '../metadata/addresses/mainnet.json'; import stagingAddresses from '../metadata/addresses/staging.json'; import legacyAddresses from '../metadata/addresses/legacy.json'; +import regressionAddresses from '../metadata/addresses/regression.json'; import sChainAbi from '../metadata/schainAbi.json'; import mainnetAbi from '../metadata/mainnetAbi.json'; @@ -50,7 +51,8 @@ export const ERC_ABIS: { [tokenType in CustomAbiTokenType | TokenType]: { ['abi' export const IMA_ADDRESSES = { mainnet: mainnetAddresses, staging: stagingAddresses, - legacy: legacyAddresses + legacy: legacyAddresses, + regression: regressionAddresses } export const IMA_ABIS = { diff --git a/src/core/network.ts b/src/core/network.ts index e587145..b9a9363 100644 --- a/src/core/network.ts +++ b/src/core/network.ts @@ -75,6 +75,9 @@ export function getMainnetAbi(network: string) { if (network === 'legacy') { return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.legacy } } + if (network === 'regression') { + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.regression } + } return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.mainnet } } diff --git a/src/core/wagmi_network.ts b/src/core/wagmi_network.ts index 5bbaab2..fef4d33 100644 --- a/src/core/wagmi_network.ts +++ b/src/core/wagmi_network.ts @@ -61,5 +61,5 @@ export function constructWagmiChain(network: SkaleNetwork, chainName: string): C export function getWebSocketUrl(chain: Chain): string { // return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : ''; - return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : ''; // TODO - IP! + return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : 'wss://goerli-light.eth.linkpool.io/ws'; // TODO - IP! } \ No newline at end of file diff --git a/src/metadata/faucet.json b/src/metadata/faucet.json index f5b34bd..d40e6e5 100644 --- a/src/metadata/faucet.json +++ b/src/metadata/faucet.json @@ -35,7 +35,6 @@ "func": "0x0c11dedd" } }, - "staging": null, "legacy": null, "regression": null } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a3417b5..0db5dad 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,6 +13,8 @@ module.exports = { }, module: { rules: [ + { test: /\.m?js$/, type: 'javascript/auto' }, + { test: /\.m?js$/, resolve: { fullySpecified: false } }, { test: /\.(ts|tsx)$/, loader: require.resolve("babel-loader"), @@ -40,65 +42,38 @@ module.exports = { }, ], }, - // { - // test: /\.png$/, - // use: [ - // { - // loader: 'file-loader', - // options: { - // limit: 10000, - // outputPath: 'icons', - // publicPath: 'icons', - // name: '[name].[ext]', - // }, - // }, - // ], - // }, - { test: /\.css$/, - use: ['style-loader', 'css-loader', 'postcss-loader'], - }, - { - test: /\.scss$/, - use: ["style-loader", { - loader: 'css-loader', - options: { - modules: true, - sourceMap: true, - importLoaders: 2, - esModule: false + sideEffects: true, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 1 + } } - }, "sass-loader"], - include: path.resolve(__dirname, "../") + ] }, - - - // { - // test: /\.s[ac]ss$/i, - // use: [ - - // // Creates `style` nodes from JS strings - // { - // loader: 'style-loader', - // options: { - // esModule: false, - // }, - // }, - // // Translates CSS into CommonJS - // { - // loader: 'css-loader', - // options: { - // modules: true, - // sourceMap: true, - // // importLoaders: 2, - // // esModule: false - // } - // }, - // // Compiles Sass to CSS - // "sass-loader", - // ], - // } + { + test: /\.s[ac]ss$/i, + use: [ + { + loader: 'style-loader', + options: { + esModule: false, + }, + }, + { + loader: 'css-loader', + options: { + modules: true, + sourceMap: true + } + }, + "sass-loader", + ], + } ] }, resolve: { @@ -122,11 +97,5 @@ module.exports = { new webpack.ProvidePlugin({ process: 'process/browser', }), - ], - // optimization: { - // splitChunks: { - // chunks: 'all', - // } - // } - + ] }; \ No newline at end of file From c467f563a2708bea1a9131aa31017c4ce30882db Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 11 Aug 2023 11:45:13 +0100 Subject: [PATCH 008/110] Update webpack config --- package.json | 30 ++++++----------- rollup.config.js | 55 -------------------------------- src/Metaport.tsx | 9 ++++-- src/components/Widget/Widget.tsx | 2 +- webpack.config.js | 37 ++++++++++++++++----- 5 files changed, 47 insertions(+), 86 deletions(-) delete mode 100644 rollup.config.js diff --git a/package.json b/package.json index fb39f2e..d2496fe 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,11 @@ "author": "SKALE Labs", "license": "LGPL-3.0-only", "main": "build/index.js", + "types": "build/index.d.ts", + "source": "src/index.ts", "files": [ "build" ], - "types": "build/index.d.ts", "scripts": { "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", @@ -34,10 +35,6 @@ "@babel/preset-env": "^7.21.4", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.21.4", - "@rollup/plugin-commonjs": "^17.1.0", - "@rollup/plugin-image": "^2.1.1", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^11.2.1", "@storybook/addon-essentials": "^7.1.0", "@storybook/addon-interactions": "^7.1.0", "@storybook/addon-links": "^7.1.0", @@ -63,25 +60,18 @@ "esm": "^3.2.25", "fast-glob": "^3.2.11", "html-webpack-inline-svg-plugin": "^2.3.0", - "html-webpack-plugin": "^5.5.0", "https-browserify": "^1.0.0", "identity-obj-proxy": "^3.0.0", "mini-css-extract-plugin": "^2.7.6", "mocha": "^9.2.2", "node-polyfill-webpack-plugin": "^1.1.4", - "postcss": "^8.4.14", + "postcss": "^8.4.27", "postcss-loader": "^7.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-jazzicon": "^1.0.4", "react-script": "^2.0.5", "react-svg-loader": "^3.0.3", - "rollup": "^2.56.3", - "rollup-plugin-copy": "^3.4.0", - "rollup-plugin-peer-deps-external": "^2.2.4", - "rollup-plugin-postcss": "^4.0.2", - "rollup-plugin-svg-import": "^1.6.0", - "rollup-plugin-typescript2": "^0.29.0", "sass": "^1.54.0", "sass-loader": "^13.0.0", "storybook": "^7.1.0", @@ -100,17 +90,17 @@ }, "peerDependencies": {}, "dependencies": { - "@rainbow-me/rainbowkit": "^1.0.8", - "viem": "^1.5.3", - "wagmi": "^1.3.9", - "zustand": "^4.4.1", - "@skalenetwork/ima-js": "2.0.0-custom.5", - "@skaleproject/pow-ethers": "0.3.2", "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@fontsource/roboto": "^4.5.7", "@mui/icons-material": "^5.8.0", "@mui/lab": "^5.0.0-alpha.88", - "@mui/material": "^5.8.1" + "@mui/material": "^5.8.1", + "@rainbow-me/rainbowkit": "^1.0.8", + "@skalenetwork/ima-js": "2.0.0-custom.5", + "@skaleproject/pow-ethers": "0.3.2", + "viem": "^1.5.3", + "wagmi": "^1.3.9", + "zustand": "^4.4.1" } } \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js deleted file mode 100644 index 371514c..0000000 --- a/rollup.config.js +++ /dev/null @@ -1,55 +0,0 @@ -import peerDepsExternal from "rollup-plugin-peer-deps-external"; -import resolve from "@rollup/plugin-node-resolve"; -import commonjs from "@rollup/plugin-commonjs"; -import typescript from "rollup-plugin-typescript2"; -import postcss from "rollup-plugin-postcss"; -import copy from "rollup-plugin-copy"; -import image from '@rollup/plugin-image'; -import json from '@rollup/plugin-json'; - - -const packageJson = require("./package.json"); - -export default { - input: "src/index.ts", - output: [ - { - file: packageJson.main, - format: "cjs", - sourcemap: true - }, - { - file: packageJson.module, - format: "esm", - sourcemap: true - } - ], - plugins: [ - json(), - image(), - peerDepsExternal(), - resolve(), - commonjs(), - typescript({ useTsconfigDeclarationDir: true }), - postcss(), - copy({ - targets: [ - { - src: "src/variables.scss", - dest: "build", - rename: "variables.scss" - }, - { - src: "src/typography.scss", - dest: "build", - rename: "typography.scss" - }, - { - src: "src/icons", - dest: "build", - rename: "icons" - } - ] - }) - ] -}; diff --git a/src/Metaport.tsx b/src/Metaport.tsx index 5fc667e..d6c16b0 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -41,7 +41,12 @@ export class Metaport { if (config.autoLookup === undefined) config.autoLookup = true; if (config.skaleNetwork === undefined) config.skaleNetwork = 'mainnet'; if (config.debug === undefined) config.debug = false; - createRoot(document.getElementById('metaport')).render(); + const el = document.getElementById('metaport'); + if (el) { + createRoot(el).render(); + } else { + console.log('div with id="metaport" does not exist') + } } transfer(params: interfaces.TransferParams): void { @@ -53,7 +58,7 @@ export class Metaport { // updateParams(params) { internalEvents.updateParams(params) } // requestBalance(params) { internalEvents.requestBalance(params) } - setTheme(theme) { internalEvents.setTheme(theme) } + setTheme(theme: any) { internalEvents.setTheme(theme) } close() { internalEvents.close() } open() { internalEvents.open() } reset() { internalEvents.reset() } diff --git a/src/components/Widget/Widget.tsx b/src/components/Widget/Widget.tsx index 609180a..5d35b9b 100644 --- a/src/components/Widget/Widget.tsx +++ b/src/components/Widget/Widget.tsx @@ -40,7 +40,7 @@ import { metaMaskWallet } from '@rainbow-me/rainbowkit/wallets'; -import { MetaportConfig } from "core/interfaces" +import { MetaportConfig } from "../../core/interfaces" import WidgetUI from '../WidgetUI' import { useUIStore } from '../../store/Store' diff --git a/webpack.config.js b/webpack.config.js index 0db5dad..edbe161 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,22 +1,26 @@ const path = require('path'); const webpack = require("webpack"); + module.exports = { entry: path.join(__dirname, '/src/index.ts'), mode: 'production', output: { filename: 'index.js', - publicPath: '', + // publicPath: '', path: path.join(__dirname, 'build'), - libraryTarget: 'commonjs', - //chunkFilename: '[id].[chunkhash].js' + library: { + type: 'commonjs' + }, + // chunkFilename: '[id].[chunkhash].js' }, module: { rules: [ - { test: /\.m?js$/, type: 'javascript/auto' }, - { test: /\.m?js$/, resolve: { fullySpecified: false } }, + // { test: /\.m?js$/, type: 'javascript/auto' }, + // { test: /\.m?js$/, resolve: { fullySpecified: false } }, { - test: /\.(ts|tsx)$/, + test: /\.(js|ts|tsx)$/, + exclude: /node_modules/, // excludes node_modules directory loader: require.resolve("babel-loader"), options: { presets: [["react-app", { @@ -96,6 +100,23 @@ module.exports = { }), new webpack.ProvidePlugin({ process: 'process/browser', - }), - ] + }) + ], + optimization: { + splitChunks: { + cacheGroups: { + default: false, + vendors: false, + // Merge all the chunks into one. + single: { + name: 'main', + chunks: 'all', + minChunks: 1, + reuseExistingChunk: true, + enforce: true + } + } + }, + runtimeChunk: false + } }; \ No newline at end of file From 1a28ee3c72a0b966f19853c39c9fc9bbb71a377b Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 11 Aug 2023 13:28:33 +0100 Subject: [PATCH 009/110] Update React imports --- src/Metaport.tsx | 2 ++ src/components/AmountErrorMessage/AmountErrorMessage.tsx | 1 + src/components/ChainApps/ChainApps.tsx | 1 + src/components/ChainIcon/ChainIcon.tsx | 1 + src/components/ErrorMessage/ErrorMessage.tsx | 1 + src/components/SkConnect/SkConnect.tsx | 1 + src/components/SkPaper/SkPaper.tsx | 2 +- src/components/SkeletonLoader/SkeletonLoader.tsx | 1 + src/components/Stepper/SkStepper.tsx | 2 +- src/components/SwitchDirection/SwitchDirection.tsx | 2 +- src/components/TokenIcon/TokenIcon.tsx | 1 + src/components/TokenList/TokenBalance.tsx | 1 + src/components/TokenListSection/TokenListSection.tsx | 1 + src/components/Widget/Widget.tsx | 2 +- src/components/WidgetBody/WidgetBody.tsx | 1 + src/index.ts | 2 +- tsconfig.json | 2 +- 17 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Metaport.tsx b/src/Metaport.tsx index d6c16b0..7d4c0ea 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -32,6 +32,8 @@ import * as interfaces from './core/interfaces/index'; export * as dataclasses from './core/dataclasses/index'; export * as interfaces from './core/interfaces/index'; +export * as ChainIcon from './components/ChainIcon'; + // export * as sfuel from './core/sfuel'; diff --git a/src/components/AmountErrorMessage/AmountErrorMessage.tsx b/src/components/AmountErrorMessage/AmountErrorMessage.tsx index bc2a838..f8d9d1a 100644 --- a/src/components/AmountErrorMessage/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage/AmountErrorMessage.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Collapse from '@mui/material/Collapse'; import { cls } from '../../core/helper'; diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index 94c228e..79abdf3 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { cls, getChainAppsMeta, getChainAlias } from '../../core/helper'; import styles from "../../styles/styles.scss"; diff --git a/src/components/ChainIcon/ChainIcon.tsx b/src/components/ChainIcon/ChainIcon.tsx index 2488da8..a1d4694 100644 --- a/src/components/ChainIcon/ChainIcon.tsx +++ b/src/components/ChainIcon/ChainIcon.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import OfflineBoltRoundedIcon from '@mui/icons-material/OfflineBoltRounded'; import { SkaleNetwork } from '../../core/interfaces'; import { chainIconPath } from '../../core/metadata'; diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index ce79b02..7ad3b48 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Button from '@mui/material/Button'; import { cls } from '../../core/helper'; diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index 126052b..f2d8c4e 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -20,6 +20,7 @@ * @copyright SKALE Labs 2023-Present */ +import React from 'react'; import { ConnectButton } from '@rainbow-me/rainbowkit'; import Jazzicon, { jsNumberForAddress } from 'react-jazzicon' diff --git a/src/components/SkPaper/SkPaper.tsx b/src/components/SkPaper/SkPaper.tsx index 84ed4e6..f799cc0 100644 --- a/src/components/SkPaper/SkPaper.tsx +++ b/src/components/SkPaper/SkPaper.tsx @@ -21,7 +21,7 @@ * @copyright SKALE Labs 2023-Present */ -import { ReactElement } from 'react'; +import React, { ReactElement } from 'react'; import { cls } from '../../core/helper'; import styles from "../../styles/styles.scss"; diff --git a/src/components/SkeletonLoader/SkeletonLoader.tsx b/src/components/SkeletonLoader/SkeletonLoader.tsx index ad7d6de..8a4bc8c 100644 --- a/src/components/SkeletonLoader/SkeletonLoader.tsx +++ b/src/components/SkeletonLoader/SkeletonLoader.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Skeleton from '@mui/material/Skeleton'; export default function SkeletonLoader(props) { diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 9ae61e0..1504eb6 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Box from '@mui/material/Box'; import Stepper from '@mui/material/Stepper'; diff --git a/src/components/SwitchDirection/SwitchDirection.tsx b/src/components/SwitchDirection/SwitchDirection.tsx index 0eb2930..555e3b6 100644 --- a/src/components/SwitchDirection/SwitchDirection.tsx +++ b/src/components/SwitchDirection/SwitchDirection.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import React, { useRef } from 'react'; import IconButton from '@mui/material/IconButton'; import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded'; diff --git a/src/components/TokenIcon/TokenIcon.tsx b/src/components/TokenIcon/TokenIcon.tsx index 2332518..0ae8cff 100644 --- a/src/components/TokenIcon/TokenIcon.tsx +++ b/src/components/TokenIcon/TokenIcon.tsx @@ -21,6 +21,7 @@ * @copyright SKALE Labs 2023-Present */ +import React from 'react'; import TollRoundedIcon from '@mui/icons-material/TollRounded'; import { TokenData } from '../../core/dataclasses'; import { tokenIconPath } from '../../core/metadata'; diff --git a/src/components/TokenList/TokenBalance.tsx b/src/components/TokenList/TokenBalance.tsx index ca02b24..d8902fd 100644 --- a/src/components/TokenList/TokenBalance.tsx +++ b/src/components/TokenList/TokenBalance.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { formatUnits } from 'ethers'; import { TokenType, TokenData } from '../../core/dataclasses'; diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index d1804a7..fae7b85 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Button from '@mui/material/Button'; import { TokenData, TokenType } from '../../core/dataclasses'; diff --git a/src/components/Widget/Widget.tsx b/src/components/Widget/Widget.tsx index 5d35b9b..32dc876 100644 --- a/src/components/Widget/Widget.tsx +++ b/src/components/Widget/Widget.tsx @@ -21,7 +21,7 @@ * @copyright SKALE Labs 2023-Present */ -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { RainbowKitProvider, diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 249eb00..73d8967 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { useCollapseStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' diff --git a/src/index.ts b/src/index.ts index 4205b85..037d2b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export { Metaport, interfaces, dataclasses } from "./Metaport"; +export { Metaport, ChainIcon, interfaces, dataclasses } from "./Metaport"; diff --git a/tsconfig.json b/tsconfig.json index 65c9c45..14717e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "target": "es6", "lib": ["es6", "dom", "es2016", "es2017"], "sourceMap": true, - "jsx": "react-jsx", + "jsx": "react", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "resolveJsonModule": true, From 4fea232fd6d040ebfa046a7c16e91f0e50e0a9b1 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Aug 2023 18:18:40 +0100 Subject: [PATCH 010/110] Migrate builder to rollup.js --- .babelrc.json | 16 -- .eslintrc.js | 20 +++ .prettierrc | 7 + .storybook/main.ts | 84 ++-------- .storybook/manager.js | 7 - .storybook/preview.ts | 9 +- .storybook/vite.config.ts | 16 ++ package.json | 155 +++++++++--------- prepare_meta.sh | 23 --- src/Metaport.tsx | 12 +- .../AmountErrorMessage/AmountErrorMessage.tsx | 2 +- ...ountInput.scss => AmountInput.module.scss} | 0 .../AmountInput/AmountInput.module.scss.d.ts | 9 + src/components/AmountInput/AmountInput.tsx | 4 +- src/components/ChainApps/ChainApps.tsx | 4 +- src/components/ChainIcon/ChainIcon.tsx | 2 +- src/components/ChainsList/ChainsList.tsx | 4 +- src/components/ErrorMessage/ErrorMessage.tsx | 4 +- src/components/SkConnect/SkConnect.tsx | 8 +- src/components/SkPaper/SkPaper.tsx | 4 +- .../{SkStepper.scss => SkStepper.module.scss} | 0 .../Stepper/SkStepper.module.scss.d.ts | 16 ++ src/components/Stepper/SkStepper.tsx | 6 +- .../SwitchDirection/SwitchDirection.tsx | 4 +- src/components/TokenIcon/TokenIcon.tsx | 2 +- src/components/TokenList/TokenBalance.tsx | 2 +- src/components/TokenList/TokenList.tsx | 4 +- .../TokenListSection/TokenListSection.tsx | 2 +- src/components/WidgetBody/WidgetBody.tsx | 2 +- src/components/WidgetUI/WidgetUI.tsx | 4 +- src/core/metadata.ts | 11 +- src/index.ts | 2 +- src/styles/_variables.scss | 6 +- .../{common.scss => common.module.scss} | 0 src/styles/common.module.scss.d.ts | 55 +++++++ .../{styles.scss => styles.module.scss} | 2 +- src/vite-env.d.ts | 1 + test/TestTest.ts | 105 ------------ test/test_utils.ts | 0 tsconfig.json | 37 ++--- tsconfig.test.json | 14 -- tslint.json | 22 --- vite.config.ts | 47 ++++++ webpack.config.js | 122 -------------- 44 files changed, 332 insertions(+), 524 deletions(-) delete mode 100644 .babelrc.json create mode 100644 .eslintrc.js create mode 100644 .prettierrc delete mode 100644 .storybook/manager.js create mode 100644 .storybook/vite.config.ts delete mode 100644 prepare_meta.sh rename src/components/AmountInput/{AmountInput.scss => AmountInput.module.scss} (100%) create mode 100644 src/components/AmountInput/AmountInput.module.scss.d.ts rename src/components/Stepper/{SkStepper.scss => SkStepper.module.scss} (100%) create mode 100644 src/components/Stepper/SkStepper.module.scss.d.ts rename src/styles/{common.scss => common.module.scss} (100%) create mode 100644 src/styles/common.module.scss.d.ts rename src/styles/{styles.scss => styles.module.scss} (98%) create mode 100644 src/vite-env.d.ts delete mode 100644 test/TestTest.ts delete mode 100644 test/test_utils.ts delete mode 100644 tsconfig.test.json delete mode 100644 tslint.json create mode 100644 vite.config.ts delete mode 100644 webpack.config.js diff --git a/.babelrc.json b/.babelrc.json deleted file mode 100644 index b5cf683..0000000 --- a/.babelrc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "sourceType": "unambiguous", - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "chrome": 100 - } - } - ], - "@babel/preset-typescript", - "@babel/preset-react" - ], - "plugins": [] -} \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..988ec8b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: ['plugin:react/recommended', 'standard-with-typescript', 'prettier', 'plugin:storybook/recommended'], + overrides: [], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['react'], + rules: { + 'react/jsx-key': 'off', + 'react/react-in-jsx-scope': 'off', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'warn', + 'no-console': 'warn', + }, +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b13587a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "endOfLine": "auto" +} \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts index 79487d7..c8af2c6 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,74 +1,22 @@ - -const path = require("path"); -/** @type { import('@storybook/react-webpack5').StorybookConfig } */ -const config = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], - staticDirs: ["../build"], +import type { StorybookConfig } from '@storybook/react-vite' +const config: StorybookConfig = { + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - // "storybook-css-modules", - // { - // name: '@storybook/addon-styling', - // options: { - // sass: { - // // Require your Sass preprocessor here - // implementation: require('sass'), - // }, - // }, - // } + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + '@storybook/addon-styling', ], framework: { - name: "@storybook/react-webpack5", - options: {}, + name: '@storybook/react-vite', + options: { + builder: { + viteConfigPath: '.storybook/vite.config.ts', + }, + }, }, docs: { - autodocs: "tag", + autodocs: 'tag', }, - webpackFinal: async config => { - if (config.resolve && config.resolve.alias) { - const { - global, - ...alias - } = config.resolve.alias; - config.resolve.alias['browser'] = false; - // const { ...alias } = config.resolve.alias - config.resolve.alias = alias; - } - if (config.resolve && config.resolve.fallback) { - config.resolve.fallback = { - path: require.resolve('path-browserify'), - os: "os-browserify/browser", - "fs": false, - "browser": false, - "https": require.resolve("https-browserify"), - "http": require.resolve("stream-http"), - "crypto": require.resolve("crypto-browserify"), - "stream": require.resolve("stream-browserify"), - "buffer": require.resolve("buffer"), - "constants": require.resolve("constants-browserify") - //...config.resolve.fallback, - }; - } - if (config.module && config.module.rules) { - config.module.rules.push({ - test: /\.scss$/, - use: ["style-loader", { - loader: 'css-loader', - options: { - modules: true, - sourceMap: true, - importLoaders: 2, - esModule: false - } - }, "sass-loader"], - include: path.resolve(__dirname, "../") - }); - } - - return config; - } -}; -export default config; - +} +export default config diff --git a/.storybook/manager.js b/.storybook/manager.js deleted file mode 100644 index c233e6f..0000000 --- a/.storybook/manager.js +++ /dev/null @@ -1,7 +0,0 @@ -// .storybook/manager.js - -import { addons } from '@storybook/manager-api'; - -addons.setConfig({ - panelPosition: 'right' -}); \ No newline at end of file diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 1c372b6..76fc315 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,8 +1,9 @@ -import type { Preview } from "@storybook/react"; +import type { Preview } from '@storybook/react' +import '../src/lib/tailwind/theme.css' const preview: Preview = { parameters: { - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, @@ -10,6 +11,6 @@ const preview: Preview = { }, }, }, -}; +} -export default preview; +export default preview diff --git a/.storybook/vite.config.ts b/.storybook/vite.config.ts new file mode 100644 index 0000000..218663b --- /dev/null +++ b/.storybook/vite.config.ts @@ -0,0 +1,16 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vitest/config' +import { UserConfigExport } from 'vite' + +const app = async (): Promise => { + return defineConfig({ + plugins: [react()], + css: { + postcss: { + plugins: [], + }, + }, + }) +} +// https://vitejs.dev/config/ +export default app diff --git a/package.json b/package.json index d2496fe..05d80c2 100644 --- a/package.json +++ b/package.json @@ -9,86 +9,71 @@ ], "author": "SKALE Labs", "license": "LGPL-3.0-only", - "main": "build/index.js", - "types": "build/index.d.ts", - "source": "src/index.ts", - "files": [ - "build" - ], - "scripts": { - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build", - "serve-storybook": "serve storybook-static", - "prepublish": "npm run build", - "build": "NODE_ENV=production webpack --mode=production", - "build-stats": " webpack --json > stats.json", - "test": "TS_NODE_PROJECT=\"tsconfig.test.json\" mocha -t 300000 -r ts-node/register test/**/*Test.ts", - "test-ts": "ts-mocha -n loader=ts-node/esm -p tsconfig.json test/**/*Test.ts", - "lint": "tslint -c tslint.json 'src/**/*.ts'", - "version": "node -e \"console.log(require('./package.json').version);\"" + "main": "./dist/metaport.umd.js", + "module": "./dist/metaport.es.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/@skalenetwork/metaport.es.js", + "require": "./dist/@skalenetwork/metaport.umd.js" + }, + "./dist/style.css": "./dist/style.css" }, - "resolutions": { - "webpack": "^5" + "engines": { + "node": "18" + }, + "scripts": { + "dev": "storybook dev -p 6006", + "build": "storybook build", + "build:lib": "tsc && vite build", + "lint": "eslint --ext .js,.jsx,.ts,.tsx --fix", + "prettier": "prettier --write \"src/**/*.{ts,tsx,js,mdx}\"", + "test": "vitest", + "test:cov": "vitest run --coverage", + "prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"" }, "devDependencies": { - "@babel/core": "^7.15.0", - "@babel/preset-env": "^7.21.4", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.4", - "@storybook/addon-essentials": "^7.1.0", - "@storybook/addon-interactions": "^7.1.0", - "@storybook/addon-links": "^7.1.0", - "@storybook/addon-mdx-gfm": "^7.1.0", - "@storybook/addon-styling": "^1.3.4", - "@storybook/blocks": "^7.1.0", - "@storybook/react": "^7.1.0", - "@storybook/react-webpack5": "^7.1.0", - "@svgr/webpack": "^7.0.0", - "@types/babel__core": "^7.1.19", - "@types/mocha": "^9.1.1", - "@types/node": "^17.0.38", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.11", - "@types/webpack-env": "^1.18.0", - "babel-loader": "^8.2.2", - "babel-preset-react-app": "^10.0.0", - "coingecko-api-v3": "0.0.13", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.12.0", - "css-loader": "^6.7.1", - "debug": "^4.3.4", - "esm": "^3.2.25", - "fast-glob": "^3.2.11", - "html-webpack-inline-svg-plugin": "^2.3.0", - "https-browserify": "^1.0.0", - "identity-obj-proxy": "^3.0.0", - "mini-css-extract-plugin": "^2.7.6", - "mocha": "^9.2.2", - "node-polyfill-webpack-plugin": "^1.1.4", - "postcss": "^8.4.27", - "postcss-loader": "^7.3.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-jazzicon": "^1.0.4", - "react-script": "^2.0.5", - "react-svg-loader": "^3.0.3", - "sass": "^1.54.0", - "sass-loader": "^13.0.0", - "storybook": "^7.1.0", - "storybook-css-modules": "^1.0.8", - "stream-browserify": "^3.0.0", - "stream-http": "^3.2.0", - "style-loader": "^3.3.1", - "svg-inline-loader": "^0.8.2", - "svg-url-loader": "^8.0.0", - "ts-loader": "^9.3.0", - "ts-mocha": "^10.0.0", - "ts-node": "^10.9.1", - "typescript": "^5.1.6", - "webpack": "^5.88.2", - "webpack-cli": "^5.1.4" + "@babel/core": "7.22.10", + "@storybook/addon-essentials": "7.2.2", + "@storybook/addon-interactions": "7.2.2", + "@storybook/addon-links": "7.2.2", + "@storybook/addon-styling": "1.3.6", + "@storybook/blocks": "7.2.2", + "@storybook/react": "7.2.2", + "@storybook/react-vite": "7.2.2", + "@storybook/testing-library": "0.2.0", + "@testing-library/react": "14.0.0", + "@types/node": "20.4.9", + "@types/react": "18.2.20", + "@types/react-dom": "18.2.7", + "@typescript-eslint/eslint-plugin": "5.60.0", + "@vitejs/plugin-react": "4.0.4", + "@vitest/coverage-v8": "0.34.1", + "autoprefixer": "10.4.14", + "babel-loader": "9.1.3", + "eslint": "8.46.0", + "eslint-config-prettier": "9.0.0", + "eslint-config-standard-with-typescript": "37.0.0", + "eslint-plugin-import": "2.28.0", + "eslint-plugin-n": "16.0.1", + "eslint-plugin-promise": "6.1.1", + "eslint-plugin-react": "7.33.1", + "eslint-plugin-storybook": "0.6.13", + "jsdom": "22.1.0", + "json": "11.0.0", + "lint-staged": "13.2.3", + "postcss": "8.4.27", + "prettier": "3.0.1", + "prop-types": "15.8.1", + "sass": "^1.65.1", + "storybook": "7.2.2", + "typescript": "5.1.6", + "vite": "4.4.9", + "vite-plugin-dts": "3.5.1", + "vite-plugin-sass-dts": "^1.3.9", + "vitest": "0.34.1" }, - "peerDependencies": {}, "dependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", @@ -97,10 +82,24 @@ "@mui/lab": "^5.0.0-alpha.88", "@mui/material": "^5.8.1", "@rainbow-me/rainbowkit": "^1.0.8", - "@skalenetwork/ima-js": "2.0.0-custom.5", + "@skalenetwork/ima-js": "2.0.0-experimental.1", "@skaleproject/pow-ethers": "0.3.2", + "coingecko-api-v3": "^0.0.28", + "react-jazzicon": "^1.0.4", "viem": "^1.5.3", "wagmi": "^1.3.9", "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "files": [ + "dist" + ], + "lint-staged": { + "*.{ts,tsx,js,jsx,json,css,md}": [ + "prettier -w" + ] } -} \ No newline at end of file +} diff --git a/prepare_meta.sh b/prepare_meta.sh deleted file mode 100644 index 749b6ba..0000000 --- a/prepare_meta.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -set -e - -export DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -META_DIR_EXTERNAL=$DIR/skale-network/metadata/ -META_DIR=$DIR/src/meta/ - -if [ -d "$META_DIR" ]; then - echo "Removing ${META_DIR}..." - rm -rf $META_DIR -else - echo "${META_DIR} not found, skipping" -fi - -if [ -d "$META_DIR_EXTERNAL" ]; then - echo "Copying ${META_DIR_EXTERNAL} -> ${META_DIR}..." - cp -R $META_DIR_EXTERNAL $META_DIR -else - cp -R $DIR/skale-network/metadata/mainnet/ $META_DIR - echo "${META_DIR_EXTERNAL} not found, copying Mainnet meta" -fi diff --git a/src/Metaport.tsx b/src/Metaport.tsx index 7d4c0ea..6f13564 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -23,7 +23,7 @@ // @ts-ignore import React from 'react'; -import { createRoot } from 'react-dom/client'; +// import { createRoot } from 'react-dom/client'; import Widget from './components/Widget'; import { internalEvents } from './core/events'; @@ -32,7 +32,13 @@ import * as interfaces from './core/interfaces/index'; export * as dataclasses from './core/dataclasses/index'; export * as interfaces from './core/interfaces/index'; -export * as ChainIcon from './components/ChainIcon'; +import ChainIcon from './components/ChainIcon'; +export { ChainIcon }; + + +import WidgetUI from './components/WidgetUI'; +export { WidgetUI }; + // export * as sfuel from './core/sfuel'; @@ -45,7 +51,7 @@ export class Metaport { if (config.debug === undefined) config.debug = false; const el = document.getElementById('metaport'); if (el) { - createRoot(el).render(); + // createRoot(el).render(); } else { console.log('div with id="metaport" does not exist') } diff --git a/src/components/AmountErrorMessage/AmountErrorMessage.tsx b/src/components/AmountErrorMessage/AmountErrorMessage.tsx index f8d9d1a..531e1c0 100644 --- a/src/components/AmountErrorMessage/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage/AmountErrorMessage.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Collapse from '@mui/material/Collapse'; import { cls } from '../../core/helper'; -import common from '../../styles/common.scss'; +import common from '../../styles/common.module.scss'; import { useMetaportStore } from '../../store/MetaportState' diff --git a/src/components/AmountInput/AmountInput.scss b/src/components/AmountInput/AmountInput.module.scss similarity index 100% rename from src/components/AmountInput/AmountInput.scss rename to src/components/AmountInput/AmountInput.module.scss diff --git a/src/components/AmountInput/AmountInput.module.scss.d.ts b/src/components/AmountInput/AmountInput.module.scss.d.ts new file mode 100644 index 0000000..4653d00 --- /dev/null +++ b/src/components/AmountInput/AmountInput.module.scss.d.ts @@ -0,0 +1,9 @@ +import globalClassNames from '../../style.d' +declare const classNames: typeof globalClassNames & { + readonly mp__inputAmount: 'mp__inputAmount' + readonly tokenSymbol: 'tokenSymbol' + readonly tokenSymbolPlaceholder: 'tokenSymbolPlaceholder' + readonly 'MuiInput-root': 'MuiInput-root' + readonly 'MuiFormControl-root': 'MuiFormControl-root' +} +export = classNames diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index c59e4df..2524f3a 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -4,8 +4,8 @@ import { useAccount } from 'wagmi' import TextField from '@mui/material/TextField'; import { cls } from '../../core/helper'; -import common from '../../styles/common.scss'; -import localStyles from './AmountInput.scss'; +import common from '../../styles/common.module.scss'; +import localStyles from './AmountInput.module.scss'; import { useMetaportStore } from '../../store/MetaportState' diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index 79abdf3..c00eac4 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { cls, getChainAppsMeta, getChainAlias } from '../../core/helper'; -import styles from "../../styles/styles.scss"; -import common from "../../styles/common.scss"; +import styles from "../../styles/styles.module.scss"; +import common from "../../styles/common.module.scss"; import { SkaleNetwork } from '../../core/interfaces'; import ChainIcon from '../ChainIcon'; diff --git a/src/components/ChainIcon/ChainIcon.tsx b/src/components/ChainIcon/ChainIcon.tsx index a1d4694..b18a301 100644 --- a/src/components/ChainIcon/ChainIcon.tsx +++ b/src/components/ChainIcon/ChainIcon.tsx @@ -4,7 +4,7 @@ import { SkaleNetwork } from '../../core/interfaces'; import { chainIconPath } from '../../core/metadata'; import { cls } from '../../core/helper'; -import styles from "../../styles/styles.scss"; +import styles from "../../styles/styles.module.scss"; export default function ChainIcon(props: { diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index b36e98f..28c6e4f 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -14,8 +14,8 @@ import ChainIcon from '../ChainIcon'; import { MetaportConfig } from '../../core/interfaces'; import { cls, getChainAlias } from '../../core/helper'; -import common from "../../styles/common.scss"; -import styles from "../../styles/styles.scss"; +import common from "../../styles/common.module.scss"; +import styles from "../../styles/styles.module.scss"; export default function ChainsList(props: { diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index 7ad3b48..dd2f691 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -2,8 +2,8 @@ import React from 'react'; import Button from '@mui/material/Button'; import { cls } from '../../core/helper'; -import common from "../../styles/common.scss"; -import styles from "../../styles/styles.scss"; +import common from "../../styles/common.module.scss"; +import styles from "../../styles/styles.module.scss"; import { ErrorMessage } from '../../core/dataclasses'; diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index f2d8c4e..4c1b552 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -29,10 +29,10 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { cls } from '../../core/helper'; -import styles from "../../styles/styles.scss"; -import common from "../../styles/common.scss"; +import styles from "../../styles/styles.module.scss"; +import common from "../../styles/common.module.scss"; -import skaleLogoFull from '../WidgetUI/skale_logo.svg'; +// import skaleLogoFull from '../WidgetUI/skale_logo.svg'; import { useMetaportStore } from '../../store/MetaportState'; import ChainIcon from "../ChainIcon"; @@ -80,7 +80,7 @@ export default function SkConnect() { common.margTop20, common.margBott20, )}> - + {/* */}
diff --git a/test/TestTest.ts b/test/TestTest.ts deleted file mode 100644 index 5f6f3e9..0000000 --- a/test/TestTest.ts +++ /dev/null @@ -1,105 +0,0 @@ - -import { Wallet } from "ethers"; - -import MetaportCore from '../src/core/metaport' -import { TokenType } from "../src/core/dataclasses"; -import { MainnetChain } from "@skalenetwork/ima-js"; - - - -const METAPORT_CONFIG = require('../src/metadata/metaportConfigStaging.json'); -METAPORT_CONFIG.mainnetEndpoint = 'https://cloudflare-eth.com/'; - - -describe("BASE LIB Test", () => { - let wallet: Wallet; - - before(async () => { - - - }); - - it("Requests ETH balance for Mainnet chain", async () => { - const mpc = new MetaportCore(METAPORT_CONFIG); - - const chain1 = 'mainnet'; - const chain2 = 'elated-tan-skat'; - const token = '_SKL_1'; - const tokenType = TokenType.erc20; - - const address = ''; - - const tokens = mpc.tokens(chain1, chain2); - - console.log(tokens); - - // const provider1 = mpc.provider(chain1); - // const provider2 = mpc.provider(chain2); - - const endp1 = mpc.endpoint(chain1); - const endp2 = mpc.endpoint(chain2); - - const mainnetChain = mpc.mainnet(); - const sChain = mpc.schain(chain2); - - console.log('======'); - console.log(endp1); - console.log(endp2); - console.log('======'); - - const bnr1 = await mainnetChain.provider.getBlockNumber(); - const bnr2 = await sChain.provider.getBlockNumber(); - - // const contract2 = mpc.tokenContract( - // token.erc20[tokenKeyname], - // provider2 - // ); - - const contract2 = mpc.tokenContract( - chain2, - token, - tokenType, - sChain.provider - ); - - console.log('-----'); - console.log(bnr1); - console.log(bnr2); - console.log('-------') - console.log(await contract2.balanceOf(address)); - console.log('-----'); - - console.log('-----'); - console.log(tokens.erc20); - - const tokenContracts = mpc.tokenContracts( - tokens, - TokenType.erc20, - chain1, - mainnetChain.provider - ); - const tokenBalances = await mpc.tokenBalances( - tokenContracts, - address - ); - - const tokenContractsDest = mpc.tokenContracts( - tokens, - TokenType.erc20, - chain2, - sChain.provider - ); - const tokenBalancesDest = await mpc.tokenBalances( - tokenContractsDest, - address - ); - - console.log('BALANCES:'); - console.log(tokenBalances); - console.log(tokenBalancesDest); - - // console.log(await sChain.ethBalance(address)); - // console.log(await mainnetChain.ethBalance(address)); - }); - -}); \ No newline at end of file diff --git a/test/test_utils.ts b/test/test_utils.ts deleted file mode 100644 index e69de29..0000000 diff --git a/tsconfig.json b/tsconfig.json index 14717e2..99dcac8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,29 +1,22 @@ { "compilerOptions": { - "rootDir": "src", - "declaration": true, - "declarationDir": "build", - "module": "esnext", - "target": "es6", - "lib": ["es6", "dom", "es2016", "es2017"], - "sourceMap": true, - "jsx": "react", - "moduleResolution": "node", + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, "allowSyntheticDefaultImports": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", "resolveJsonModule": true, - "esModuleInterop": true, - "baseUrl": "src", - "types": ["node", "webpack-env", "mocha"], + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", "typeRoots": ["./src/types", "./node_modules/@types"], - "noUnusedLocals": true, /* Report errors on unused locals. */ - "noUnusedParameters": true /* Report errors on unused parameters. */ }, - "include": ["src/**/*", "index.ts", "src/types/custom.d.ts"], - "exclude": [ - "node_modules", - "build", - "storybook-static", - "src/**/*.stories.tsx", - "src/**/*.test.tsx" - ] + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["./src/**/*.stories.*"] } diff --git a/tsconfig.test.json b/tsconfig.test.json deleted file mode 100644 index 5f10f9d..0000000 --- a/tsconfig.test.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "resolveJsonModule": true, - "esModuleInterop": true, - "jsx": "react-jsx", - "typeRoots": ["./node_modules/@types", "./src/types"] - }, - "include": [ - "tests/*.ts" - ], - "exclude": ["src/components/**/*.ts"] -} \ No newline at end of file diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 5ea890a..0000000 --- a/tslint.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended" - ], - "jsRules": {}, - "rules": { - "no-namespace": false, - "max-classes-per-file": false, - "forin": false, - "max-line-length": [ - true, - { - "limit": 100, - "ignore-pattern": "^import |^export {(.*?)}", - "check-strings": true, - "check-regex": true - } - ] - }, - "rulesDirectory": [] -} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..6606745 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,47 @@ +import react from '@vitejs/plugin-react' +import path from 'node:path' +import { defineConfig } from 'vitest/config' +import dts from 'vite-plugin-dts' +import { UserConfigExport } from 'vite' +import { name } from './package.json' + + +const app = async (): Promise => { + return defineConfig({ + css: { + postcss: { + plugins: [], + }, + }, + plugins: [ + react(), + dts({ + insertTypesEntry: true, + }) + ], + build: { + lib: { + entry: path.resolve(__dirname, 'src/index.ts'), + name, + formats: ['es', 'umd'], + fileName: (format) => `${name}.${format}.js`, + }, + rollupOptions: { + external: ['react', 'react/jsx-runtime', 'react-dom'], + output: { + globals: { + react: 'React', + 'react/jsx-runtime': 'react/jsx-runtime', + 'react-dom': 'ReactDOM' + }, + }, + }, + }, + test: { + globals: true, + environment: 'jsdom', + }, + }) +} +// https://vitejs.dev/config/ +export default app diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index edbe161..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,122 +0,0 @@ -const path = require('path'); -const webpack = require("webpack"); - - -module.exports = { - entry: path.join(__dirname, '/src/index.ts'), - mode: 'production', - output: { - filename: 'index.js', - // publicPath: '', - path: path.join(__dirname, 'build'), - library: { - type: 'commonjs' - }, - // chunkFilename: '[id].[chunkhash].js' - }, - module: { - rules: [ - // { test: /\.m?js$/, type: 'javascript/auto' }, - // { test: /\.m?js$/, resolve: { fullySpecified: false } }, - { - test: /\.(js|ts|tsx)$/, - exclude: /node_modules/, // excludes node_modules directory - loader: require.resolve("babel-loader"), - options: { - presets: [["react-app", { - flow: false, - typescript: true - }]] - } - }, - { - test: /\.tsx?$/, - loader: 'ts-loader', - // include: path.resolve(__dirname, 'src'), - exclude: /node_modules/ - }, - { - test: /\.svg$/, - use: [ - { - loader: 'svg-url-loader', - options: { - limit: 200000, - }, - }, - ], - }, - { - test: /\.css$/, - sideEffects: true, - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - importLoaders: 1 - } - } - ] - }, - { - test: /\.s[ac]ss$/i, - use: [ - { - loader: 'style-loader', - options: { - esModule: false, - }, - }, - { - loader: 'css-loader', - options: { - modules: true, - sourceMap: true - } - }, - "sass-loader", - ], - } - ] - }, - resolve: { - extensions: [".tsx", ".ts", ".js", ".css", ".scss"], - fallback: { - // make sure you `npm install path-browserify` to use this - path: require.resolve('path-browserify'), - os: "os-browserify/browser", - "fs": false, - "https": require.resolve("https-browserify"), - "http": require.resolve("stream-http"), - "crypto": require.resolve("crypto-browserify"), - "stream": require.resolve("stream-browserify"), - "buffer": require.resolve("buffer") - } - }, - plugins: [ - new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - }), - new webpack.ProvidePlugin({ - process: 'process/browser', - }) - ], - optimization: { - splitChunks: { - cacheGroups: { - default: false, - vendors: false, - // Merge all the chunks into one. - single: { - name: 'main', - chunks: 'all', - minChunks: 1, - reuseExistingChunk: true, - enforce: true - } - } - }, - runtimeChunk: false - } -}; \ No newline at end of file From b7b85e7df74c01df56bc37812310b6ea4f16c0cb Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Aug 2023 18:35:08 +0100 Subject: [PATCH 011/110] Split injected metaport and mp component --- src/Metaport.tsx | 4 +- src/components/Metaport/Metaport.tsx | 109 +++++++++++++++++++++++++++ src/components/Metaport/index.ts | 1 + src/components/WidgetUI/WidgetUI.tsx | 27 ++++++- 4 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 src/components/Metaport/Metaport.tsx create mode 100644 src/components/Metaport/index.ts diff --git a/src/Metaport.tsx b/src/Metaport.tsx index 6f13564..4113907 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -39,11 +39,13 @@ export { ChainIcon }; import WidgetUI from './components/WidgetUI'; export { WidgetUI }; +import Metaport from './components/Metaport'; +export { Metaport }; // export * as sfuel from './core/sfuel'; -export class Metaport { +export class InjectedMetaport { constructor(config: interfaces.MetaportConfig) { if (config.openButton === undefined) config.openButton = true; if (config.autoLookup === undefined) config.autoLookup = true; diff --git a/src/components/Metaport/Metaport.tsx b/src/components/Metaport/Metaport.tsx new file mode 100644 index 0000000..b96960c --- /dev/null +++ b/src/components/Metaport/Metaport.tsx @@ -0,0 +1,109 @@ + +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +/** + * @file Metaport.ts + * @copyright SKALE Labs 2023-Present + */ + +import { + RainbowKitProvider +} from '@rainbow-me/rainbowkit'; +import { configureChains, createConfig, WagmiConfig } from 'wagmi'; +import { mainnet, goerli } from 'wagmi/chains'; +import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'; +import { connectorsForWallets } from '@rainbow-me/rainbowkit'; + +import { + injectedWallet, + coinbaseWallet, + metaMaskWallet +} from '@rainbow-me/rainbowkit/wallets'; + +import { MetaportConfig } from "../../core/interfaces" + +import WidgetUI from '../WidgetUI' + +import '@rainbow-me/rainbowkit/styles.css'; + +import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network'; + + +const { chains, webSocketPublicClient } = configureChains( + [ + mainnet, + goerli, + constructWagmiChain('staging', "staging-legal-crazy-castor"), + constructWagmiChain('staging', "staging-utter-unripe-menkar"), + constructWagmiChain('staging', "staging-faint-slimy-achird"), + constructWagmiChain('staging', "staging-perfect-parallel-gacrux"), + constructWagmiChain('staging', "staging-severe-violet-wezen"), + constructWagmiChain('staging', "staging-weepy-fitting-caph"), + + constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), + constructWagmiChain('mainnet', 'elated-tan-skat'), + constructWagmiChain('mainnet', 'affectionate-immediate-pollux') + ], + [ + jsonRpcProvider({ + rpc: chain => ({ + http: chain.rpcUrls.default.http[0], + webSocket: getWebSocketUrl(chain) + }), + }) + ] +); + + +const connectors = connectorsForWallets([ + { + groupName: 'Supported Wallets', + wallets: [ + metaMaskWallet({ chains, projectId: '' }), + injectedWallet({ chains }), + coinbaseWallet({ chains, appName: 'TEST' }) + ], + } +]); + + +const wagmiConfig = createConfig({ + autoConnect: true, + connectors, + publicClient: webSocketPublicClient +}); + + +export default function Widget(props: { + config: MetaportConfig +}) { + return ( + + + + + + ) +} \ No newline at end of file diff --git a/src/components/Metaport/index.ts b/src/components/Metaport/index.ts new file mode 100644 index 0000000..293cae0 --- /dev/null +++ b/src/components/Metaport/index.ts @@ -0,0 +1 @@ +export { default } from "./Metaport"; diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index 6fce788..e71b370 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -20,7 +20,7 @@ * @copyright SKALE Labs 2023-Present */ -import React from 'react'; +import React, { useEffect } from 'react'; import { StyledEngineProvider } from '@mui/material/styles'; import { useAccount } from 'wagmi'; @@ -47,17 +47,38 @@ import styles from "../../styles/styles.module.scss"; import common from "../../styles/common.module.scss"; import { PaletteMode } from '@mui/material'; +import { getWidgetTheme } from '../../core/themes'; + import SkConnect from '../SkConnect'; import ErrorMessage from '../ErrorMessage'; +import { MetaportConfig } from '../../core/interfaces'; +import MetaportCore from '../../core/metaport' + + +export function WidgetUI(props: { config: MetaportConfig }) { + + const widgetTheme = getWidgetTheme(props.config.theme); + const setTheme = useUIStore((state) => state.setTheme); + const setMpc = useMetaportStore((state) => state.setMpc); + const setOpen = useUIStore((state) => state.setOpen); + + useEffect(() => { + setOpen(props.config.openOnLoad); + }, []); -export function WidgetUI(props) { + useEffect(() => { + setTheme(widgetTheme); + }, [setTheme]); + + useEffect(() => { + setMpc(new MetaportCore(props.config)); + }, [setMpc]); const { address } = useAccount(); const metaportTheme = useUIStore((state) => state.theme); const isOpen = useUIStore((state) => state.open); - const setOpen = useUIStore((state) => state.setOpen); const errorMessage = useMetaportStore((state) => state.errorMessage); From fda6a4b6ac545afe25931f0a63cac662699f5ec1 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Aug 2023 19:08:19 +0100 Subject: [PATCH 012/110] Add chain icons support for vite --- generate-imports.js | 44 ++++++++++++++++++++ src/components/ChainIcon/ChainIcon.tsx | 2 +- src/core/metadata.ts | 28 ++++++------- src/metadata/metaportConfigStaging.json | 53 ++----------------------- 4 files changed, 61 insertions(+), 66 deletions(-) create mode 100644 generate-imports.js diff --git a/generate-imports.js b/generate-imports.js new file mode 100644 index 0000000..d19ba1c --- /dev/null +++ b/generate-imports.js @@ -0,0 +1,44 @@ +const fs = require('fs'); +const path = require('path'); + +const rootDir = process.argv[2]; + +if (!rootDir) { + console.error('Please provide a root directory as an argument.'); + process.exit(1); +} + +const getSvgFilesInDir = (dir) => { + return fs.readdirSync(dir).filter(file => path.extname(file) === '.svg').map(file => path.join(dir, file)); +}; + +const generateNamespaceExportsForDir = (dir) => { + const svgFiles = getSvgFilesInDir(dir); + + if (svgFiles.length === 0) return; // Skip folders without SVGs + + const namespaceExports = svgFiles.map(file => { + const variableName = path.basename(file, '.svg').replace(/-([a-z])/g, (_, g) => g.toUpperCase()); // Convert kebab-case to camelCase + return `export * as ${variableName} from './${path.basename(file)}';`; + }).join('\n'); + + const outputPath = path.join(dir, 'index.ts'); + fs.writeFileSync(outputPath, namespaceExports, 'utf-8'); +}; + +const walkDirectories = (dir) => { + const items = fs.readdirSync(dir); + + for (const item of items) { + const itemPath = path.join(dir, item); + const stat = fs.statSync(itemPath); + + if (stat && stat.isDirectory()) { + generateNamespaceExportsForDir(itemPath); + walkDirectories(itemPath); + } + } +}; + +generateNamespaceExportsForDir(rootDir); // Generate for rootDir itself +walkDirectories(rootDir); // Then walk its subdirectories diff --git a/src/components/ChainIcon/ChainIcon.tsx b/src/components/ChainIcon/ChainIcon.tsx index b18a301..702e8c7 100644 --- a/src/components/ChainIcon/ChainIcon.tsx +++ b/src/components/ChainIcon/ChainIcon.tsx @@ -23,7 +23,7 @@ export default function ChainIcon(props: { const className = styles[`chainIcon${size}`] + ' ' + props.className; if (iconPath !== undefined) { if (iconPath.default) { - return ; + return ; } return ; } diff --git a/src/core/metadata.ts b/src/core/metadata.ts index a219e73..a0512c3 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -25,32 +25,28 @@ import { TokenData } from './dataclasses'; import { SkaleNetwork } from './interfaces'; import { MAINNET_CHAIN_NAME } from './constants'; - -function importAll(r) { - const images = {}; - r.keys().map((item, _) => { images[item.replace('./', '')] = r(item); }); - return images; -} - +import * as MAINNET_CHAIN_ICONS from '../meta/mainnet/icons'; +import * as STAGING_CHAIN_ICONS from '../meta/staging/icons'; +import * as LEGACY_CHAIN_ICONS from '../meta/legacy/icons'; +import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons'; const icons = {}; const CHAIN_ICONS = { - 'mainnet': {}, - 'staging': {}, - 'legacy': {}, - 'regression': {} + 'mainnet': MAINNET_CHAIN_ICONS, + 'staging': STAGING_CHAIN_ICONS, + 'legacy': LEGACY_CHAIN_ICONS, + 'regression': REGRESSION_CHAIN_ICONS } export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: string) { if (!name) return; - let filename = name.toLowerCase(); - if (app) - filename += `-${app}`; - filename += '.svg'; + let filename = name.toLowerCase() + if (app) filename += `-${app}`; if (name === MAINNET_CHAIN_NAME) { - return icons['eth.svg']; + return CHAIN_ICONS[skaleNetwork]['mainnet']; } + filename = filename.replace(/-([a-z])/g, (_, g) => g.toUpperCase()) if (CHAIN_ICONS[skaleNetwork][filename]) { return CHAIN_ICONS[skaleNetwork][filename]; } diff --git a/src/metadata/metaportConfigStaging.json b/src/metadata/metaportConfigStaging.json index db445c7..eaa3f7a 100644 --- a/src/metadata/metaportConfigStaging.json +++ b/src/metadata/metaportConfigStaging.json @@ -243,67 +243,22 @@ }, "staging-perfect-parallel-gacrux": { "erc20": { - "_ETH_0xBA3f8192e28224790978794102C0D7aaa65B7d70": { - "address": "0xBA3f8192e28224790978794102C0D7aaa65B7d70", - "name": "ETH", - "symbol": "ETH", - "cloneSymbol": "ETH", - "wraps": { - "address": "0xD2Aaa00700000000000000000000000000000000", - "symbol": "ETH", - "name": "aaaa" - } - }, - "_SKILL_0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA": { - "address": "0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA", - "name": "SKILL Token", - "symbol": "SKILL", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Compass/3D/compass_3d.png" - }, - "DMT": { - "address": "0xb36A1DdaBf21161ad71013A34D502381DD1aa7BA", - "name": "DMT", - "symbol": "DMT", - "cloneSymbol": "DMTC", - "wraps": { - "address": "0x688f6d050B935BF06531b51B5e598318788fA7a5", - "symbol": "DMT", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Compass/3D/compass_3d.png" - } - }, - "_SKL_0x099A46F35b627CABee27dc917eDA253fFbC55Be6": { - "address": "0x099A46F35b627CABee27dc917eDA253fFbC55Be6", - "decimals": "18", - "name": "SKL S2S", - "symbol": "SKL" - } }, "erc721": { - "_SPS_0x30216880A73B67133F37de35e769b8e1A943D35c": { - "address": "0x30216880A73B67133F37de35e769b8e1A943D35c", - "name": "SKALE Space S2S", - "symbol": "SPS", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Glowing%20star/3D/glowing_star_3d.png" - } + }, "erc1155": { "skaliens": { "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", - "name": "SKALIENS Collection", - "symbol": "SKALIENS2S", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png" + "chains": {} }, "medals": { "address": "0x5D8bD602dC5468B3998e8514A1851bd5888E9639", - "name": "Medals", - "symbol": "MEDALS2S", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/1st%20place%20medal/3D/1st_place_medal_3d.png" + "chains": {} }, "_ANIMALS_0xDf87EEF0977148129969b01b329379b17756cdDE": { "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", - "name": "Funny Animals", - "symbol": "ANIMALS", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Frog/3D/frog_3d.png" + "chains": {} } } } From 476a9d46f9f071b9f40b136f454421e8334c6aae Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Aug 2023 19:21:07 +0100 Subject: [PATCH 013/110] Add token icons in vite build --- .gitignore | 1 + src/components/TokenIcon/TokenIcon.tsx | 8 +- src/core/metadata.ts | 12 +- src/icons/{0xbtc.svg => _0xbtc.svg} | 0 src/icons/{2give.svg => _2give.svg} | 0 src/icons/index.ts | 471 +++++++++++++++++++++++++ src/icons/{$pac.svg => pac.svg} | 0 7 files changed, 485 insertions(+), 7 deletions(-) rename src/icons/{0xbtc.svg => _0xbtc.svg} (100%) rename src/icons/{2give.svg => _2give.svg} (100%) create mode 100644 src/icons/index.ts rename src/icons/{$pac.svg => pac.svg} (100%) diff --git a/.gitignore b/.gitignore index 3c31b63..13d7e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ test.mjs src/meta/ +src/icons/index.ts metaportConfig.json metaportConfigMainnet.json diff --git a/src/components/TokenIcon/TokenIcon.tsx b/src/components/TokenIcon/TokenIcon.tsx index b373568..fd5290c 100644 --- a/src/components/TokenIcon/TokenIcon.tsx +++ b/src/components/TokenIcon/TokenIcon.tsx @@ -37,7 +37,11 @@ export default function TokenIcon(props: { const className = styles[`chainIcon${size}`]; if (props.token === undefined || props.token === null) { return - // return } - return + + const iconPath = tokenIconPath(props.token); + if (iconPath.default) { + return ; + } + return ; } \ No newline at end of file diff --git a/src/core/metadata.ts b/src/core/metadata.ts index a0512c3..e150fda 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -30,7 +30,9 @@ import * as STAGING_CHAIN_ICONS from '../meta/staging/icons'; import * as LEGACY_CHAIN_ICONS from '../meta/legacy/icons'; import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons'; -const icons = {}; +import * as icons from '../icons'; + + const CHAIN_ICONS = { 'mainnet': MAINNET_CHAIN_ICONS, 'staging': STAGING_CHAIN_ICONS, @@ -53,18 +55,18 @@ export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: st } -export function tokenIcon(name: string): string { +export function tokenIcon(name: string) { if (!name) return; - const key = name.toLowerCase() + '.svg'; + const key = name.toLowerCase() if (icons[key]) { return icons[key]; } else { - return icons['eth.svg']; + return icons['eth']; } } -export function tokenIconPath(token: TokenData): string { +export function tokenIconPath(token: TokenData) { return token.meta.iconUrl ?? tokenIcon(token.meta.symbol); } diff --git a/src/icons/0xbtc.svg b/src/icons/_0xbtc.svg similarity index 100% rename from src/icons/0xbtc.svg rename to src/icons/_0xbtc.svg diff --git a/src/icons/2give.svg b/src/icons/_2give.svg similarity index 100% rename from src/icons/2give.svg rename to src/icons/_2give.svg diff --git a/src/icons/index.ts b/src/icons/index.ts new file mode 100644 index 0000000..5b947a4 --- /dev/null +++ b/src/icons/index.ts @@ -0,0 +1,471 @@ +export * as _0xbtc from './_0xbtc.svg'; +export * as _2give from './_2give.svg'; +export * as aave from './aave.svg'; +export * as abt from './abt.svg'; +export * as act from './act.svg'; +export * as actn from './actn.svg'; +export * as ada from './ada.svg'; +export * as add from './add.svg'; +export * as adx from './adx.svg'; +export * as ae from './ae.svg'; +export * as aeon from './aeon.svg'; +export * as aeur from './aeur.svg'; +export * as agi from './agi.svg'; +export * as agrs from './agrs.svg'; +export * as aion from './aion.svg'; +export * as algo from './algo.svg'; +export * as amb from './amb.svg'; +export * as amp from './amp.svg'; +export * as ampl from './ampl.svg'; +export * as ankr from './ankr.svg'; +export * as ant from './ant.svg'; +export * as apex from './apex.svg'; +export * as appc from './appc.svg'; +export * as ardr from './ardr.svg'; +export * as arg from './arg.svg'; +export * as ark from './ark.svg'; +export * as arn from './arn.svg'; +export * as arnx from './arnx.svg'; +export * as ary from './ary.svg'; +export * as ast from './ast.svg'; +export * as atm from './atm.svg'; +export * as atom from './atom.svg'; +export * as audr from './audr.svg'; +export * as auto from './auto.svg'; +export * as aywa from './aywa.svg'; +export * as bab from './bab.svg'; +export * as bal from './bal.svg'; +export * as band from './band.svg'; +export * as bat from './bat.svg'; +export * as bay from './bay.svg'; +export * as bcbc from './bcbc.svg'; +export * as bcc from './bcc.svg'; +export * as bcd from './bcd.svg'; +export * as bch from './bch.svg'; +export * as bcio from './bcio.svg'; +export * as bcn from './bcn.svg'; +export * as bco from './bco.svg'; +export * as bcpt from './bcpt.svg'; +export * as bdl from './bdl.svg'; +export * as beam from './beam.svg'; +export * as bela from './bela.svg'; +export * as bix from './bix.svg'; +export * as blcn from './blcn.svg'; +export * as blk from './blk.svg'; +export * as block from './block.svg'; +export * as blz from './blz.svg'; +export * as bnb from './bnb.svg'; +export * as bnt from './bnt.svg'; +export * as bnty from './bnty.svg'; +export * as booty from './booty.svg'; +export * as bos from './bos.svg'; +export * as bpt from './bpt.svg'; +export * as bq from './bq.svg'; +export * as brd from './brd.svg'; +export * as bsd from './bsd.svg'; +export * as bsv from './bsv.svg'; +export * as btc from './btc.svg'; +export * as btcd from './btcd.svg'; +export * as btch from './btch.svg'; +export * as btcp from './btcp.svg'; +export * as btcz from './btcz.svg'; +export * as btdx from './btdx.svg'; +export * as btg from './btg.svg'; +export * as btm from './btm.svg'; +export * as bts from './bts.svg'; +export * as btt from './btt.svg'; +export * as btx from './btx.svg'; +export * as burst from './burst.svg'; +export * as bze from './bze.svg'; +export * as call from './call.svg'; +export * as cc from './cc.svg'; +export * as cdn from './cdn.svg'; +export * as cdt from './cdt.svg'; +export * as cenz from './cenz.svg'; +export * as chain from './chain.svg'; +export * as chat from './chat.svg'; +export * as chips from './chips.svg'; +export * as chsb from './chsb.svg'; +export * as cix from './cix.svg'; +export * as clam from './clam.svg'; +export * as cloak from './cloak.svg'; +export * as cmm from './cmm.svg'; +export * as cmt from './cmt.svg'; +export * as cnd from './cnd.svg'; +export * as cnx from './cnx.svg'; +export * as cny from './cny.svg'; +export * as cob from './cob.svg'; +export * as colx from './colx.svg'; +export * as comp from './comp.svg'; +export * as coqui from './coqui.svg'; +export * as cred from './cred.svg'; +export * as crpt from './crpt.svg'; +export * as crv from './crv.svg'; +export * as crw from './crw.svg'; +export * as cs from './cs.svg'; +export * as ctr from './ctr.svg'; +export * as ctxc from './ctxc.svg'; +export * as cvc from './cvc.svg'; +export * as dai from './dai.svg'; +export * as dash from './dash.svg'; +export * as dat from './dat.svg'; +export * as data from './data.svg'; +export * as dbc from './dbc.svg'; +export * as dcn from './dcn.svg'; +export * as dcr from './dcr.svg'; +export * as deez from './deez.svg'; +export * as dent from './dent.svg'; +export * as dew from './dew.svg'; +export * as dgb from './dgb.svg'; +export * as dgd from './dgd.svg'; +export * as dlt from './dlt.svg'; +export * as dnt from './dnt.svg'; +export * as dock from './dock.svg'; +export * as doge from './doge.svg'; +export * as dot from './dot.svg'; +export * as drgn from './drgn.svg'; +export * as drop from './drop.svg'; +export * as dta from './dta.svg'; +export * as dth from './dth.svg'; +export * as dtr from './dtr.svg'; +export * as ebst from './ebst.svg'; +export * as eca from './eca.svg'; +export * as edg from './edg.svg'; +export * as edo from './edo.svg'; +export * as edoge from './edoge.svg'; +export * as ela from './ela.svg'; +export * as elec from './elec.svg'; +export * as elf from './elf.svg'; +export * as elix from './elix.svg'; +export * as ella from './ella.svg'; +export * as emb from './emb.svg'; +export * as emc from './emc.svg'; +export * as emc2 from './emc2.svg'; +export * as eng from './eng.svg'; +export * as enj from './enj.svg'; +export * as entrp from './entrp.svg'; +export * as eon from './eon.svg'; +export * as eop from './eop.svg'; +export * as eos from './eos.svg'; +export * as eqli from './eqli.svg'; +export * as equa from './equa.svg'; +export * as etc from './etc.svg'; +export * as eth from './eth.svg'; +export * as eth_white from './eth_white.svg'; +export * as ethos from './ethos.svg'; +export * as etn from './etn.svg'; +export * as etp from './etp.svg'; +export * as eur from './eur.svg'; +export * as evx from './evx.svg'; +export * as exmo from './exmo.svg'; +export * as exp from './exp.svg'; +export * as fair from './fair.svg'; +export * as fct from './fct.svg'; +export * as fil from './fil.svg'; +export * as fjc from './fjc.svg'; +export * as fldc from './fldc.svg'; +export * as flo from './flo.svg'; +export * as flux from './flux.svg'; +export * as fsn from './fsn.svg'; +export * as ftc from './ftc.svg'; +export * as fuel from './fuel.svg'; +export * as fun from './fun.svg'; +export * as game from './game.svg'; +export * as gas from './gas.svg'; +export * as gbp from './gbp.svg'; +export * as gbx from './gbx.svg'; +export * as gbyte from './gbyte.svg'; +export * as generic from './generic.svg'; +export * as gin from './gin.svg'; +export * as glxt from './glxt.svg'; +export * as gmr from './gmr.svg'; +export * as gno from './gno.svg'; +export * as gnt from './gnt.svg'; +export * as gold from './gold.svg'; +export * as grc from './grc.svg'; +export * as grin from './grin.svg'; +export * as grs from './grs.svg'; +export * as grt from './grt.svg'; +export * as gsc from './gsc.svg'; +export * as gto from './gto.svg'; +export * as gup from './gup.svg'; +export * as gusd from './gusd.svg'; +export * as gvt from './gvt.svg'; +export * as gxs from './gxs.svg'; +export * as gzr from './gzr.svg'; +export * as hight from './hight.svg'; +export * as hns from './hns.svg'; +export * as hodl from './hodl.svg'; +export * as hot from './hot.svg'; +export * as hpb from './hpb.svg'; +export * as hsr from './hsr.svg'; +export * as ht from './ht.svg'; +export * as html from './html.svg'; +export * as huc from './huc.svg'; +export * as husd from './husd.svg'; +export * as hush from './hush.svg'; +export * as icn from './icn.svg'; +export * as icp from './icp.svg'; +export * as icx from './icx.svg'; +export * as ignis from './ignis.svg'; +export * as ilk from './ilk.svg'; +export * as ink from './ink.svg'; +export * as ins from './ins.svg'; +export * as ion from './ion.svg'; +export * as iop from './iop.svg'; +export * as iost from './iost.svg'; +export * as iotx from './iotx.svg'; +export * as iq from './iq.svg'; +export * as itc from './itc.svg'; +export * as jnt from './jnt.svg'; +export * as jpy from './jpy.svg'; +export * as kcs from './kcs.svg'; +export * as kin from './kin.svg'; +export * as klown from './klown.svg'; +export * as kmd from './kmd.svg'; +export * as knc from './knc.svg'; +export * as krb from './krb.svg'; +export * as ksm from './ksm.svg'; +export * as lbc from './lbc.svg'; +export * as lend from './lend.svg'; +export * as leo from './leo.svg'; +export * as link from './link.svg'; +export * as lkk from './lkk.svg'; +export * as loom from './loom.svg'; +export * as lpt from './lpt.svg'; +export * as lrc from './lrc.svg'; +export * as lsk from './lsk.svg'; +export * as ltc from './ltc.svg'; +export * as lun from './lun.svg'; +export * as maid from './maid.svg'; +export * as mana from './mana.svg'; +export * as matic from './matic.svg'; +export * as max from './max.svg'; +export * as mcap from './mcap.svg'; +export * as mco from './mco.svg'; +export * as mda from './mda.svg'; +export * as mds from './mds.svg'; +export * as med from './med.svg'; +export * as meetone from './meetone.svg'; +export * as mft from './mft.svg'; +export * as miota from './miota.svg'; +export * as mith from './mith.svg'; +export * as mkr from './mkr.svg'; +export * as mln from './mln.svg'; +export * as mnx from './mnx.svg'; +export * as mnz from './mnz.svg'; +export * as moac from './moac.svg'; +export * as mod from './mod.svg'; +export * as mona from './mona.svg'; +export * as msr from './msr.svg'; +export * as mth from './mth.svg'; +export * as mtl from './mtl.svg'; +export * as music from './music.svg'; +export * as mzc from './mzc.svg'; +export * as nano from './nano.svg'; +export * as nas from './nas.svg'; +export * as nav from './nav.svg'; +export * as ncash from './ncash.svg'; +export * as ndz from './ndz.svg'; +export * as nebl from './nebl.svg'; +export * as neo from './neo.svg'; +export * as neos from './neos.svg'; +export * as neu from './neu.svg'; +export * as nexo from './nexo.svg'; +export * as ngc from './ngc.svg'; +export * as nio from './nio.svg'; +export * as nkn from './nkn.svg'; +export * as nlc2 from './nlc2.svg'; +export * as nlg from './nlg.svg'; +export * as nmc from './nmc.svg'; +export * as nmr from './nmr.svg'; +export * as npxs from './npxs.svg'; +export * as ntbc from './ntbc.svg'; +export * as nuls from './nuls.svg'; +export * as nxs from './nxs.svg'; +export * as nxt from './nxt.svg'; +export * as oax from './oax.svg'; +export * as ok from './ok.svg'; +export * as omg from './omg.svg'; +export * as omni from './omni.svg'; +export * as one from './one.svg'; +export * as ong from './ong.svg'; +export * as ont from './ont.svg'; +export * as oot from './oot.svg'; +export * as ost from './ost.svg'; +export * as ox from './ox.svg'; +export * as oxt from './oxt.svg'; +export * as pac from './pac.svg'; +export * as part from './part.svg'; +export * as pasc from './pasc.svg'; +export * as pasl from './pasl.svg'; +export * as pax from './pax.svg'; +export * as paxg from './paxg.svg'; +export * as pay from './pay.svg'; +export * as payx from './payx.svg'; +export * as pink from './pink.svg'; +export * as pirl from './pirl.svg'; +export * as pivx from './pivx.svg'; +export * as plr from './plr.svg'; +export * as poa from './poa.svg'; +export * as poe from './poe.svg'; +export * as polis from './polis.svg'; +export * as poly from './poly.svg'; +export * as pot from './pot.svg'; +export * as powr from './powr.svg'; +export * as ppc from './ppc.svg'; +export * as ppp from './ppp.svg'; +export * as ppt from './ppt.svg'; +export * as pre from './pre.svg'; +export * as prl from './prl.svg'; +export * as pungo from './pungo.svg'; +export * as pura from './pura.svg'; +export * as qash from './qash.svg'; +export * as qiwi from './qiwi.svg'; +export * as qlc from './qlc.svg'; +export * as qrl from './qrl.svg'; +export * as qsp from './qsp.svg'; +export * as qtum from './qtum.svg'; +export * as r from './r.svg'; +export * as rads from './rads.svg'; +export * as rap from './rap.svg'; +export * as rcn from './rcn.svg'; +export * as rdd from './rdd.svg'; +export * as rdn from './rdn.svg'; +export * as ren from './ren.svg'; +export * as rep from './rep.svg'; +export * as repv2 from './repv2.svg'; +export * as req from './req.svg'; +export * as rhoc from './rhoc.svg'; +export * as ric from './ric.svg'; +export * as rise from './rise.svg'; +export * as rlc from './rlc.svg'; +export * as rpx from './rpx.svg'; +export * as rub from './rub.svg'; +export * as rvn from './rvn.svg'; +export * as ryo from './ryo.svg'; +export * as safe from './safe.svg'; +export * as safemoon from './safemoon.svg'; +export * as sai from './sai.svg'; +export * as salt from './salt.svg'; +export * as san from './san.svg'; +export * as sand from './sand.svg'; +export * as sbd from './sbd.svg'; +export * as sberbank from './sberbank.svg'; +export * as sc from './sc.svg'; +export * as shift from './shift.svg'; +export * as sib from './sib.svg'; +export * as sin from './sin.svg'; +export * as skl from './skl.svg'; +export * as sky from './sky.svg'; +export * as slr from './slr.svg'; +export * as sls from './sls.svg'; +export * as smart from './smart.svg'; +export * as sngls from './sngls.svg'; +export * as snm from './snm.svg'; +export * as snt from './snt.svg'; +export * as snx from './snx.svg'; +export * as soc from './soc.svg'; +export * as sol from './sol.svg'; +export * as spacehbit from './spacehbit.svg'; +export * as spank from './spank.svg'; +export * as sphtx from './sphtx.svg'; +export * as srn from './srn.svg'; +export * as stak from './stak.svg'; +export * as start from './start.svg'; +export * as steem from './steem.svg'; +export * as storj from './storj.svg'; +export * as storm from './storm.svg'; +export * as stox from './stox.svg'; +export * as stq from './stq.svg'; +export * as strat from './strat.svg'; +export * as stx from './stx.svg'; +export * as sub from './sub.svg'; +export * as sumo from './sumo.svg'; +export * as sushi from './sushi.svg'; +export * as sys from './sys.svg'; +export * as taas from './taas.svg'; +export * as tau from './tau.svg'; +export * as tbx from './tbx.svg'; +export * as tel from './tel.svg'; +export * as ten from './ten.svg'; +export * as tern from './tern.svg'; +export * as tgch from './tgch.svg'; +export * as theta from './theta.svg'; +export * as tix from './tix.svg'; +export * as tkn from './tkn.svg'; +export * as tks from './tks.svg'; +export * as tnb from './tnb.svg'; +export * as tnc from './tnc.svg'; +export * as tnt from './tnt.svg'; +export * as tomo from './tomo.svg'; +export * as tpay from './tpay.svg'; +export * as trig from './trig.svg'; +export * as trtl from './trtl.svg'; +export * as trx from './trx.svg'; +export * as tusd from './tusd.svg'; +export * as tzc from './tzc.svg'; +export * as ubq from './ubq.svg'; +export * as uma from './uma.svg'; +export * as uni from './uni.svg'; +export * as unity from './unity.svg'; +export * as usd from './usd.svg'; +export * as usdc from './usdc.svg'; +export * as usdt from './usdt.svg'; +export * as utk from './utk.svg'; +export * as veri from './veri.svg'; +export * as vet from './vet.svg'; +export * as via from './via.svg'; +export * as vib from './vib.svg'; +export * as vibe from './vibe.svg'; +export * as vivo from './vivo.svg'; +export * as vrc from './vrc.svg'; +export * as vrsc from './vrsc.svg'; +export * as vtc from './vtc.svg'; +export * as vtho from './vtho.svg'; +export * as wabi from './wabi.svg'; +export * as wan from './wan.svg'; +export * as waves from './waves.svg'; +export * as wax from './wax.svg'; +export * as wbtc from './wbtc.svg'; +export * as wgr from './wgr.svg'; +export * as wicc from './wicc.svg'; +export * as wings from './wings.svg'; +export * as wpr from './wpr.svg'; +export * as wtc from './wtc.svg'; +export * as x from './x.svg'; +export * as xas from './xas.svg'; +export * as xbc from './xbc.svg'; +export * as xbp from './xbp.svg'; +export * as xby from './xby.svg'; +export * as xcp from './xcp.svg'; +export * as xdn from './xdn.svg'; +export * as xem from './xem.svg'; +export * as xin from './xin.svg'; +export * as xlm from './xlm.svg'; +export * as xmcc from './xmcc.svg'; +export * as xmg from './xmg.svg'; +export * as xmo from './xmo.svg'; +export * as xmr from './xmr.svg'; +export * as xmy from './xmy.svg'; +export * as xp from './xp.svg'; +export * as xpa from './xpa.svg'; +export * as xpm from './xpm.svg'; +export * as xpr from './xpr.svg'; +export * as xrp from './xrp.svg'; +export * as xsg from './xsg.svg'; +export * as xtz from './xtz.svg'; +export * as xuc from './xuc.svg'; +export * as xvc from './xvc.svg'; +export * as xvg from './xvg.svg'; +export * as xzc from './xzc.svg'; +export * as yfi from './yfi.svg'; +export * as yoyow from './yoyow.svg'; +export * as zcl from './zcl.svg'; +export * as zec from './zec.svg'; +export * as zel from './zel.svg'; +export * as zen from './zen.svg'; +export * as zest from './zest.svg'; +export * as zil from './zil.svg'; +export * as zilla from './zilla.svg'; +export * as zrx from './zrx.svg'; \ No newline at end of file diff --git a/src/icons/$pac.svg b/src/icons/pac.svg similarity index 100% rename from src/icons/$pac.svg rename to src/icons/pac.svg From dc2dce76df8a4f8f53f6f30199b38c07bef5f420 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Aug 2023 19:22:13 +0100 Subject: [PATCH 014/110] Add prepare_meta script --- prepare_meta.sh | 26 +++ src/icons/index.ts | 471 --------------------------------------------- 2 files changed, 26 insertions(+), 471 deletions(-) create mode 100644 prepare_meta.sh delete mode 100644 src/icons/index.ts diff --git a/prepare_meta.sh b/prepare_meta.sh new file mode 100644 index 0000000..957542d --- /dev/null +++ b/prepare_meta.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e + +export DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +META_DIR_EXTERNAL=$DIR/skale-network/metadata/ +META_DIR=$DIR/src/meta/ + +if [ -d "$META_DIR" ]; then + echo "Removing ${META_DIR}..." + rm -rf $META_DIR +else + echo "${META_DIR} not found, skipping" +fi + +if [ -d "$META_DIR_EXTERNAL" ]; then + echo "Copying ${META_DIR_EXTERNAL} -> ${META_DIR}..." + cp -R $META_DIR_EXTERNAL $META_DIR +else + cp -R $DIR/skale-network/metadata/mainnet/ $META_DIR + echo "${META_DIR_EXTERNAL} not found, copying Mainnet meta" +fi + +node generate-imports.js ./src/meta +node generate-imports.js ./src/icons \ No newline at end of file diff --git a/src/icons/index.ts b/src/icons/index.ts deleted file mode 100644 index 5b947a4..0000000 --- a/src/icons/index.ts +++ /dev/null @@ -1,471 +0,0 @@ -export * as _0xbtc from './_0xbtc.svg'; -export * as _2give from './_2give.svg'; -export * as aave from './aave.svg'; -export * as abt from './abt.svg'; -export * as act from './act.svg'; -export * as actn from './actn.svg'; -export * as ada from './ada.svg'; -export * as add from './add.svg'; -export * as adx from './adx.svg'; -export * as ae from './ae.svg'; -export * as aeon from './aeon.svg'; -export * as aeur from './aeur.svg'; -export * as agi from './agi.svg'; -export * as agrs from './agrs.svg'; -export * as aion from './aion.svg'; -export * as algo from './algo.svg'; -export * as amb from './amb.svg'; -export * as amp from './amp.svg'; -export * as ampl from './ampl.svg'; -export * as ankr from './ankr.svg'; -export * as ant from './ant.svg'; -export * as apex from './apex.svg'; -export * as appc from './appc.svg'; -export * as ardr from './ardr.svg'; -export * as arg from './arg.svg'; -export * as ark from './ark.svg'; -export * as arn from './arn.svg'; -export * as arnx from './arnx.svg'; -export * as ary from './ary.svg'; -export * as ast from './ast.svg'; -export * as atm from './atm.svg'; -export * as atom from './atom.svg'; -export * as audr from './audr.svg'; -export * as auto from './auto.svg'; -export * as aywa from './aywa.svg'; -export * as bab from './bab.svg'; -export * as bal from './bal.svg'; -export * as band from './band.svg'; -export * as bat from './bat.svg'; -export * as bay from './bay.svg'; -export * as bcbc from './bcbc.svg'; -export * as bcc from './bcc.svg'; -export * as bcd from './bcd.svg'; -export * as bch from './bch.svg'; -export * as bcio from './bcio.svg'; -export * as bcn from './bcn.svg'; -export * as bco from './bco.svg'; -export * as bcpt from './bcpt.svg'; -export * as bdl from './bdl.svg'; -export * as beam from './beam.svg'; -export * as bela from './bela.svg'; -export * as bix from './bix.svg'; -export * as blcn from './blcn.svg'; -export * as blk from './blk.svg'; -export * as block from './block.svg'; -export * as blz from './blz.svg'; -export * as bnb from './bnb.svg'; -export * as bnt from './bnt.svg'; -export * as bnty from './bnty.svg'; -export * as booty from './booty.svg'; -export * as bos from './bos.svg'; -export * as bpt from './bpt.svg'; -export * as bq from './bq.svg'; -export * as brd from './brd.svg'; -export * as bsd from './bsd.svg'; -export * as bsv from './bsv.svg'; -export * as btc from './btc.svg'; -export * as btcd from './btcd.svg'; -export * as btch from './btch.svg'; -export * as btcp from './btcp.svg'; -export * as btcz from './btcz.svg'; -export * as btdx from './btdx.svg'; -export * as btg from './btg.svg'; -export * as btm from './btm.svg'; -export * as bts from './bts.svg'; -export * as btt from './btt.svg'; -export * as btx from './btx.svg'; -export * as burst from './burst.svg'; -export * as bze from './bze.svg'; -export * as call from './call.svg'; -export * as cc from './cc.svg'; -export * as cdn from './cdn.svg'; -export * as cdt from './cdt.svg'; -export * as cenz from './cenz.svg'; -export * as chain from './chain.svg'; -export * as chat from './chat.svg'; -export * as chips from './chips.svg'; -export * as chsb from './chsb.svg'; -export * as cix from './cix.svg'; -export * as clam from './clam.svg'; -export * as cloak from './cloak.svg'; -export * as cmm from './cmm.svg'; -export * as cmt from './cmt.svg'; -export * as cnd from './cnd.svg'; -export * as cnx from './cnx.svg'; -export * as cny from './cny.svg'; -export * as cob from './cob.svg'; -export * as colx from './colx.svg'; -export * as comp from './comp.svg'; -export * as coqui from './coqui.svg'; -export * as cred from './cred.svg'; -export * as crpt from './crpt.svg'; -export * as crv from './crv.svg'; -export * as crw from './crw.svg'; -export * as cs from './cs.svg'; -export * as ctr from './ctr.svg'; -export * as ctxc from './ctxc.svg'; -export * as cvc from './cvc.svg'; -export * as dai from './dai.svg'; -export * as dash from './dash.svg'; -export * as dat from './dat.svg'; -export * as data from './data.svg'; -export * as dbc from './dbc.svg'; -export * as dcn from './dcn.svg'; -export * as dcr from './dcr.svg'; -export * as deez from './deez.svg'; -export * as dent from './dent.svg'; -export * as dew from './dew.svg'; -export * as dgb from './dgb.svg'; -export * as dgd from './dgd.svg'; -export * as dlt from './dlt.svg'; -export * as dnt from './dnt.svg'; -export * as dock from './dock.svg'; -export * as doge from './doge.svg'; -export * as dot from './dot.svg'; -export * as drgn from './drgn.svg'; -export * as drop from './drop.svg'; -export * as dta from './dta.svg'; -export * as dth from './dth.svg'; -export * as dtr from './dtr.svg'; -export * as ebst from './ebst.svg'; -export * as eca from './eca.svg'; -export * as edg from './edg.svg'; -export * as edo from './edo.svg'; -export * as edoge from './edoge.svg'; -export * as ela from './ela.svg'; -export * as elec from './elec.svg'; -export * as elf from './elf.svg'; -export * as elix from './elix.svg'; -export * as ella from './ella.svg'; -export * as emb from './emb.svg'; -export * as emc from './emc.svg'; -export * as emc2 from './emc2.svg'; -export * as eng from './eng.svg'; -export * as enj from './enj.svg'; -export * as entrp from './entrp.svg'; -export * as eon from './eon.svg'; -export * as eop from './eop.svg'; -export * as eos from './eos.svg'; -export * as eqli from './eqli.svg'; -export * as equa from './equa.svg'; -export * as etc from './etc.svg'; -export * as eth from './eth.svg'; -export * as eth_white from './eth_white.svg'; -export * as ethos from './ethos.svg'; -export * as etn from './etn.svg'; -export * as etp from './etp.svg'; -export * as eur from './eur.svg'; -export * as evx from './evx.svg'; -export * as exmo from './exmo.svg'; -export * as exp from './exp.svg'; -export * as fair from './fair.svg'; -export * as fct from './fct.svg'; -export * as fil from './fil.svg'; -export * as fjc from './fjc.svg'; -export * as fldc from './fldc.svg'; -export * as flo from './flo.svg'; -export * as flux from './flux.svg'; -export * as fsn from './fsn.svg'; -export * as ftc from './ftc.svg'; -export * as fuel from './fuel.svg'; -export * as fun from './fun.svg'; -export * as game from './game.svg'; -export * as gas from './gas.svg'; -export * as gbp from './gbp.svg'; -export * as gbx from './gbx.svg'; -export * as gbyte from './gbyte.svg'; -export * as generic from './generic.svg'; -export * as gin from './gin.svg'; -export * as glxt from './glxt.svg'; -export * as gmr from './gmr.svg'; -export * as gno from './gno.svg'; -export * as gnt from './gnt.svg'; -export * as gold from './gold.svg'; -export * as grc from './grc.svg'; -export * as grin from './grin.svg'; -export * as grs from './grs.svg'; -export * as grt from './grt.svg'; -export * as gsc from './gsc.svg'; -export * as gto from './gto.svg'; -export * as gup from './gup.svg'; -export * as gusd from './gusd.svg'; -export * as gvt from './gvt.svg'; -export * as gxs from './gxs.svg'; -export * as gzr from './gzr.svg'; -export * as hight from './hight.svg'; -export * as hns from './hns.svg'; -export * as hodl from './hodl.svg'; -export * as hot from './hot.svg'; -export * as hpb from './hpb.svg'; -export * as hsr from './hsr.svg'; -export * as ht from './ht.svg'; -export * as html from './html.svg'; -export * as huc from './huc.svg'; -export * as husd from './husd.svg'; -export * as hush from './hush.svg'; -export * as icn from './icn.svg'; -export * as icp from './icp.svg'; -export * as icx from './icx.svg'; -export * as ignis from './ignis.svg'; -export * as ilk from './ilk.svg'; -export * as ink from './ink.svg'; -export * as ins from './ins.svg'; -export * as ion from './ion.svg'; -export * as iop from './iop.svg'; -export * as iost from './iost.svg'; -export * as iotx from './iotx.svg'; -export * as iq from './iq.svg'; -export * as itc from './itc.svg'; -export * as jnt from './jnt.svg'; -export * as jpy from './jpy.svg'; -export * as kcs from './kcs.svg'; -export * as kin from './kin.svg'; -export * as klown from './klown.svg'; -export * as kmd from './kmd.svg'; -export * as knc from './knc.svg'; -export * as krb from './krb.svg'; -export * as ksm from './ksm.svg'; -export * as lbc from './lbc.svg'; -export * as lend from './lend.svg'; -export * as leo from './leo.svg'; -export * as link from './link.svg'; -export * as lkk from './lkk.svg'; -export * as loom from './loom.svg'; -export * as lpt from './lpt.svg'; -export * as lrc from './lrc.svg'; -export * as lsk from './lsk.svg'; -export * as ltc from './ltc.svg'; -export * as lun from './lun.svg'; -export * as maid from './maid.svg'; -export * as mana from './mana.svg'; -export * as matic from './matic.svg'; -export * as max from './max.svg'; -export * as mcap from './mcap.svg'; -export * as mco from './mco.svg'; -export * as mda from './mda.svg'; -export * as mds from './mds.svg'; -export * as med from './med.svg'; -export * as meetone from './meetone.svg'; -export * as mft from './mft.svg'; -export * as miota from './miota.svg'; -export * as mith from './mith.svg'; -export * as mkr from './mkr.svg'; -export * as mln from './mln.svg'; -export * as mnx from './mnx.svg'; -export * as mnz from './mnz.svg'; -export * as moac from './moac.svg'; -export * as mod from './mod.svg'; -export * as mona from './mona.svg'; -export * as msr from './msr.svg'; -export * as mth from './mth.svg'; -export * as mtl from './mtl.svg'; -export * as music from './music.svg'; -export * as mzc from './mzc.svg'; -export * as nano from './nano.svg'; -export * as nas from './nas.svg'; -export * as nav from './nav.svg'; -export * as ncash from './ncash.svg'; -export * as ndz from './ndz.svg'; -export * as nebl from './nebl.svg'; -export * as neo from './neo.svg'; -export * as neos from './neos.svg'; -export * as neu from './neu.svg'; -export * as nexo from './nexo.svg'; -export * as ngc from './ngc.svg'; -export * as nio from './nio.svg'; -export * as nkn from './nkn.svg'; -export * as nlc2 from './nlc2.svg'; -export * as nlg from './nlg.svg'; -export * as nmc from './nmc.svg'; -export * as nmr from './nmr.svg'; -export * as npxs from './npxs.svg'; -export * as ntbc from './ntbc.svg'; -export * as nuls from './nuls.svg'; -export * as nxs from './nxs.svg'; -export * as nxt from './nxt.svg'; -export * as oax from './oax.svg'; -export * as ok from './ok.svg'; -export * as omg from './omg.svg'; -export * as omni from './omni.svg'; -export * as one from './one.svg'; -export * as ong from './ong.svg'; -export * as ont from './ont.svg'; -export * as oot from './oot.svg'; -export * as ost from './ost.svg'; -export * as ox from './ox.svg'; -export * as oxt from './oxt.svg'; -export * as pac from './pac.svg'; -export * as part from './part.svg'; -export * as pasc from './pasc.svg'; -export * as pasl from './pasl.svg'; -export * as pax from './pax.svg'; -export * as paxg from './paxg.svg'; -export * as pay from './pay.svg'; -export * as payx from './payx.svg'; -export * as pink from './pink.svg'; -export * as pirl from './pirl.svg'; -export * as pivx from './pivx.svg'; -export * as plr from './plr.svg'; -export * as poa from './poa.svg'; -export * as poe from './poe.svg'; -export * as polis from './polis.svg'; -export * as poly from './poly.svg'; -export * as pot from './pot.svg'; -export * as powr from './powr.svg'; -export * as ppc from './ppc.svg'; -export * as ppp from './ppp.svg'; -export * as ppt from './ppt.svg'; -export * as pre from './pre.svg'; -export * as prl from './prl.svg'; -export * as pungo from './pungo.svg'; -export * as pura from './pura.svg'; -export * as qash from './qash.svg'; -export * as qiwi from './qiwi.svg'; -export * as qlc from './qlc.svg'; -export * as qrl from './qrl.svg'; -export * as qsp from './qsp.svg'; -export * as qtum from './qtum.svg'; -export * as r from './r.svg'; -export * as rads from './rads.svg'; -export * as rap from './rap.svg'; -export * as rcn from './rcn.svg'; -export * as rdd from './rdd.svg'; -export * as rdn from './rdn.svg'; -export * as ren from './ren.svg'; -export * as rep from './rep.svg'; -export * as repv2 from './repv2.svg'; -export * as req from './req.svg'; -export * as rhoc from './rhoc.svg'; -export * as ric from './ric.svg'; -export * as rise from './rise.svg'; -export * as rlc from './rlc.svg'; -export * as rpx from './rpx.svg'; -export * as rub from './rub.svg'; -export * as rvn from './rvn.svg'; -export * as ryo from './ryo.svg'; -export * as safe from './safe.svg'; -export * as safemoon from './safemoon.svg'; -export * as sai from './sai.svg'; -export * as salt from './salt.svg'; -export * as san from './san.svg'; -export * as sand from './sand.svg'; -export * as sbd from './sbd.svg'; -export * as sberbank from './sberbank.svg'; -export * as sc from './sc.svg'; -export * as shift from './shift.svg'; -export * as sib from './sib.svg'; -export * as sin from './sin.svg'; -export * as skl from './skl.svg'; -export * as sky from './sky.svg'; -export * as slr from './slr.svg'; -export * as sls from './sls.svg'; -export * as smart from './smart.svg'; -export * as sngls from './sngls.svg'; -export * as snm from './snm.svg'; -export * as snt from './snt.svg'; -export * as snx from './snx.svg'; -export * as soc from './soc.svg'; -export * as sol from './sol.svg'; -export * as spacehbit from './spacehbit.svg'; -export * as spank from './spank.svg'; -export * as sphtx from './sphtx.svg'; -export * as srn from './srn.svg'; -export * as stak from './stak.svg'; -export * as start from './start.svg'; -export * as steem from './steem.svg'; -export * as storj from './storj.svg'; -export * as storm from './storm.svg'; -export * as stox from './stox.svg'; -export * as stq from './stq.svg'; -export * as strat from './strat.svg'; -export * as stx from './stx.svg'; -export * as sub from './sub.svg'; -export * as sumo from './sumo.svg'; -export * as sushi from './sushi.svg'; -export * as sys from './sys.svg'; -export * as taas from './taas.svg'; -export * as tau from './tau.svg'; -export * as tbx from './tbx.svg'; -export * as tel from './tel.svg'; -export * as ten from './ten.svg'; -export * as tern from './tern.svg'; -export * as tgch from './tgch.svg'; -export * as theta from './theta.svg'; -export * as tix from './tix.svg'; -export * as tkn from './tkn.svg'; -export * as tks from './tks.svg'; -export * as tnb from './tnb.svg'; -export * as tnc from './tnc.svg'; -export * as tnt from './tnt.svg'; -export * as tomo from './tomo.svg'; -export * as tpay from './tpay.svg'; -export * as trig from './trig.svg'; -export * as trtl from './trtl.svg'; -export * as trx from './trx.svg'; -export * as tusd from './tusd.svg'; -export * as tzc from './tzc.svg'; -export * as ubq from './ubq.svg'; -export * as uma from './uma.svg'; -export * as uni from './uni.svg'; -export * as unity from './unity.svg'; -export * as usd from './usd.svg'; -export * as usdc from './usdc.svg'; -export * as usdt from './usdt.svg'; -export * as utk from './utk.svg'; -export * as veri from './veri.svg'; -export * as vet from './vet.svg'; -export * as via from './via.svg'; -export * as vib from './vib.svg'; -export * as vibe from './vibe.svg'; -export * as vivo from './vivo.svg'; -export * as vrc from './vrc.svg'; -export * as vrsc from './vrsc.svg'; -export * as vtc from './vtc.svg'; -export * as vtho from './vtho.svg'; -export * as wabi from './wabi.svg'; -export * as wan from './wan.svg'; -export * as waves from './waves.svg'; -export * as wax from './wax.svg'; -export * as wbtc from './wbtc.svg'; -export * as wgr from './wgr.svg'; -export * as wicc from './wicc.svg'; -export * as wings from './wings.svg'; -export * as wpr from './wpr.svg'; -export * as wtc from './wtc.svg'; -export * as x from './x.svg'; -export * as xas from './xas.svg'; -export * as xbc from './xbc.svg'; -export * as xbp from './xbp.svg'; -export * as xby from './xby.svg'; -export * as xcp from './xcp.svg'; -export * as xdn from './xdn.svg'; -export * as xem from './xem.svg'; -export * as xin from './xin.svg'; -export * as xlm from './xlm.svg'; -export * as xmcc from './xmcc.svg'; -export * as xmg from './xmg.svg'; -export * as xmo from './xmo.svg'; -export * as xmr from './xmr.svg'; -export * as xmy from './xmy.svg'; -export * as xp from './xp.svg'; -export * as xpa from './xpa.svg'; -export * as xpm from './xpm.svg'; -export * as xpr from './xpr.svg'; -export * as xrp from './xrp.svg'; -export * as xsg from './xsg.svg'; -export * as xtz from './xtz.svg'; -export * as xuc from './xuc.svg'; -export * as xvc from './xvc.svg'; -export * as xvg from './xvg.svg'; -export * as xzc from './xzc.svg'; -export * as yfi from './yfi.svg'; -export * as yoyow from './yoyow.svg'; -export * as zcl from './zcl.svg'; -export * as zec from './zec.svg'; -export * as zel from './zel.svg'; -export * as zen from './zen.svg'; -export * as zest from './zest.svg'; -export * as zil from './zil.svg'; -export * as zilla from './zilla.svg'; -export * as zrx from './zrx.svg'; \ No newline at end of file From 1f201fb3a2f012ae11ccac3505a63e4240b11950 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Aug 2023 19:59:49 +0100 Subject: [PATCH 015/110] Run prettier --- src/Metaport.tsx | 52 +- .../AmountErrorMessage/AmountErrorMessage.tsx | 40 +- src/components/AmountErrorMessage/index.ts | 2 +- src/components/AmountInput/AmountInput.tsx | 52 +- src/components/AmountInput/index.ts | 2 +- src/components/ChainApps/ChainApps.tsx | 52 +- src/components/ChainApps/index.ts | 2 +- src/components/ChainIcon/ChainIcon.tsx | 39 +- src/components/ChainIcon/index.ts | 2 +- src/components/ChainsList/ChainsList.tsx | 171 ++--- src/components/ChainsList/index.ts | 2 +- src/components/ErrorMessage/ErrorMessage.tsx | 110 ++-- src/components/ErrorMessage/index.ts | 2 +- src/components/Metaport/Metaport.tsx | 137 ++-- src/components/Metaport/index.ts | 2 +- src/components/SkConnect/SkConnect.tsx | 330 +++++----- src/components/SkConnect/index.ts | 2 +- src/components/SkPaper/SkPaper.tsx | 48 +- src/components/SkPaper/index.ts | 2 +- .../SkeletonLoader/SkeletonLoader.tsx | 4 +- src/components/SkeletonLoader/index.ts | 2 +- src/components/Stepper/SkStepper.tsx | 202 +++--- src/components/Stepper/index.ts | 2 +- .../SwitchDirection/SwitchDirection.tsx | 70 +-- src/components/SwitchDirection/index.ts | 2 +- src/components/TokenIcon/TokenIcon.tsx | 28 +- src/components/TokenIcon/index.ts | 2 +- src/components/TokenList/TokenBalance.tsx | 50 +- src/components/TokenList/TokenList.tsx | 113 ++-- src/components/TokenList/index.ts | 2 +- .../TokenListSection/TokenListSection.tsx | 73 +-- src/components/TokenListSection/index.ts | 2 +- src/components/Widget/Widget.mdx | 15 +- src/components/Widget/Widget.stories.tsx | 24 +- src/components/Widget/Widget.tsx | 190 +++--- src/components/Widget/index.ts | 2 +- src/components/WidgetBody/WidgetBody.tsx | 59 +- src/components/WidgetBody/index.ts | 2 +- src/components/WidgetUI/WidgetUI.tsx | 154 +++-- src/components/WidgetUI/index.ts | 2 +- src/core/actions/action.ts | 448 +++++++------ src/core/actions/actionState.ts | 78 +-- src/core/actions/checks.ts | 291 ++++----- src/core/actions/erc20.ts | 594 ++++++++---------- src/core/actions/index.ts | 103 ++- src/core/chain_id.ts | 28 +- src/core/community_pool.ts | 6 +- src/core/constants.ts | 105 ++-- src/core/contracts.ts | 54 +- src/core/convertation.ts | 10 +- src/core/dataclasses/ErrorMessage.ts | 65 +- src/core/dataclasses/EthTokenData.ts | 37 +- src/core/dataclasses/Position.ts | 26 +- src/core/dataclasses/StepMetadata.ts | 129 ++-- src/core/dataclasses/TokenData.ts | 71 ++- src/core/dataclasses/TokenType.ts | 18 +- src/core/dataclasses/TransferRequestStatus.ts | 15 +- src/core/dataclasses/View.ts | 13 +- src/core/dataclasses/index.ts | 12 +- src/core/ethers.ts | 50 +- src/core/events.ts | 325 +++++----- src/core/explorer.ts | 22 +- src/core/faucet.ts | 4 +- src/core/fee_calculator.ts | 46 +- src/core/gas_station.ts | 11 +- src/core/helper.ts | 90 ++- src/core/interfaces/ChainsMetadata.ts | 24 +- src/core/interfaces/CheckRes.ts | 9 +- src/core/interfaces/CommunityPoolData.ts | 15 +- src/core/interfaces/Config.ts | 24 +- src/core/interfaces/RouteParams.ts | 11 +- src/core/interfaces/Theme.ts | 18 +- src/core/interfaces/TokenDataMap.ts | 36 +- src/core/interfaces/TokenMetadata.ts | 12 +- src/core/interfaces/Tokens.ts | 30 +- src/core/interfaces/TransactionHistory.ts | 17 +- src/core/interfaces/TransferParams.ts | 27 +- src/core/interfaces/index.ts | 20 +- src/core/metadata.ts | 69 +- src/core/metaport.ts | 323 ++++------ src/core/network.ts | 93 ++- src/core/sfuel.ts | 8 - src/core/themes.ts | 65 +- src/core/tokens/helper.ts | 46 +- src/core/transfer_steps.ts | 98 ++- src/core/views.ts | 12 +- src/core/wagmi_network.ts | 66 +- src/index.ts | 4 +- src/store/MetaportState.ts | 484 +++++++------- src/store/Store.ts | 73 ++- src/types/custom.d.ts | 39 +- src/types/index.d.ts | 6 +- src/types/react-app-env.d.ts | 5 +- 93 files changed, 2932 insertions(+), 3402 deletions(-) diff --git a/src/Metaport.tsx b/src/Metaport.tsx index 4113907..e85d3a5 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -22,36 +22,34 @@ */ // @ts-ignore -import React from 'react'; +import React from 'react' // import { createRoot } from 'react-dom/client'; -import Widget from './components/Widget'; -import { internalEvents } from './core/events'; +import Widget from './components/Widget' +import { internalEvents } from './core/events' -import * as interfaces from './core/interfaces/index'; -export * as dataclasses from './core/dataclasses/index'; -export * as interfaces from './core/interfaces/index'; +import * as interfaces from './core/interfaces/index' +export * as dataclasses from './core/dataclasses/index' +export * as interfaces from './core/interfaces/index' -import ChainIcon from './components/ChainIcon'; -export { ChainIcon }; +import ChainIcon from './components/ChainIcon' +export { ChainIcon } +import WidgetUI from './components/WidgetUI' +export { WidgetUI } -import WidgetUI from './components/WidgetUI'; -export { WidgetUI }; - -import Metaport from './components/Metaport'; -export { Metaport }; +import Metaport from './components/Metaport' +export { Metaport } // export * as sfuel from './core/sfuel'; - export class InjectedMetaport { constructor(config: interfaces.MetaportConfig) { - if (config.openButton === undefined) config.openButton = true; - if (config.autoLookup === undefined) config.autoLookup = true; - if (config.skaleNetwork === undefined) config.skaleNetwork = 'mainnet'; - if (config.debug === undefined) config.debug = false; - const el = document.getElementById('metaport'); + if (config.openButton === undefined) config.openButton = true + if (config.autoLookup === undefined) config.autoLookup = true + if (config.skaleNetwork === undefined) config.skaleNetwork = 'mainnet' + if (config.debug === undefined) config.debug = false + const el = document.getElementById('metaport') if (el) { // createRoot(el).render(); } else { @@ -68,8 +66,16 @@ export class InjectedMetaport { // updateParams(params) { internalEvents.updateParams(params) } // requestBalance(params) { internalEvents.requestBalance(params) } - setTheme(theme: any) { internalEvents.setTheme(theme) } - close() { internalEvents.close() } - open() { internalEvents.open() } - reset() { internalEvents.reset() } + setTheme(theme: any) { + internalEvents.setTheme(theme) + } + close() { + internalEvents.close() + } + open() { + internalEvents.open() + } + reset() { + internalEvents.reset() + } } diff --git a/src/components/AmountErrorMessage/AmountErrorMessage.tsx b/src/components/AmountErrorMessage/AmountErrorMessage.tsx index 531e1c0..40b2c67 100644 --- a/src/components/AmountErrorMessage/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage/AmountErrorMessage.tsx @@ -1,28 +1,30 @@ -import React from 'react'; -import Collapse from '@mui/material/Collapse'; +import React from 'react' +import Collapse from '@mui/material/Collapse' -import { cls } from '../../core/helper'; -import common from '../../styles/common.module.scss'; +import { cls } from '../../core/helper' +import common from '../../styles/common.module.scss' import { useMetaportStore } from '../../store/MetaportState' - export default function AmountErrorMessage() { - const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage); + const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage) return ( - -

+ +

🔴 {amountErrorMessage}

-
) + + ) } diff --git a/src/components/AmountErrorMessage/index.ts b/src/components/AmountErrorMessage/index.ts index adc7ed2..0944661 100644 --- a/src/components/AmountErrorMessage/index.ts +++ b/src/components/AmountErrorMessage/index.ts @@ -1 +1 @@ -export { default } from "./AmountErrorMessage"; \ No newline at end of file +export { default } from './AmountErrorMessage' diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index 2524f3a..5a84f29 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -1,31 +1,29 @@ -import React from "react"; +import React from 'react' import { useAccount } from 'wagmi' -import TextField from '@mui/material/TextField'; +import TextField from '@mui/material/TextField' -import { cls } from '../../core/helper'; -import common from '../../styles/common.module.scss'; -import localStyles from './AmountInput.module.scss'; +import { cls } from '../../core/helper' +import common from '../../styles/common.module.scss' +import localStyles from './AmountInput.module.scss' import { useMetaportStore } from '../../store/MetaportState' - export default function AmountInput() { - const { address } = useAccount() - const token = useMetaportStore((state) => state.token); - const transferInProgress = useMetaportStore((state) => state.transferInProgress); - const setAmount = useMetaportStore((state) => state.setAmount); - const amount = useMetaportStore((state) => state.amount); + const token = useMetaportStore((state) => state.token) + const transferInProgress = useMetaportStore((state) => state.transferInProgress) + const setAmount = useMetaportStore((state) => state.setAmount) + const amount = useMetaportStore((state) => state.amount) const handleChange = (event: React.ChangeEvent) => { if (parseFloat(event.target.value) < 0) { - setAmount('', address); - return; + setAmount('', address) + return } - setAmount(event.target.value, address); - }; + setAmount(event.target.value, address) + } // const setMaxAmount = () => { // if (token && !token.clone && @@ -43,7 +41,7 @@ export default function AmountInput() { // } // } - if (!token) return; + if (!token) return return (
@@ -57,16 +55,18 @@ export default function AmountInput() { />
-
+
{token.meta.symbol}
diff --git a/src/components/AmountInput/index.ts b/src/components/AmountInput/index.ts index 0315fc8..28f19b1 100644 --- a/src/components/AmountInput/index.ts +++ b/src/components/AmountInput/index.ts @@ -1 +1 @@ -export { default } from "./AmountInput"; +export { default } from './AmountInput' diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index c00eac4..ba9356a 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -1,44 +1,36 @@ -import React from 'react'; -import { cls, getChainAppsMeta, getChainAlias } from '../../core/helper'; +import React from 'react' +import { cls, getChainAppsMeta, getChainAlias } from '../../core/helper' -import styles from "../../styles/styles.module.scss"; -import common from "../../styles/common.module.scss"; -import { SkaleNetwork } from '../../core/interfaces'; +import styles from '../../styles/styles.module.scss' +import common from '../../styles/common.module.scss' +import { SkaleNetwork } from '../../core/interfaces' -import ChainIcon from '../ChainIcon'; +import ChainIcon from '../ChainIcon' - -export default function ChainApps(props: { - skaleNetwork: SkaleNetwork, - chain: string -}) { - - const apps = getChainAppsMeta(props.chain, props.skaleNetwork); - if (!apps || !Object.keys(apps) || Object.keys(apps).length === 0) return
; +export default function ChainApps(props: { skaleNetwork: SkaleNetwork; chain: string }) { + const apps = getChainAppsMeta(props.chain, props.skaleNetwork) + if (!apps || !Object.keys(apps) || Object.keys(apps).length === 0) return
return ( -
+
{Object.keys(apps).map((key, _) => ( -
+

{getChainAlias(props.skaleNetwork, props.chain, key)} @@ -48,4 +40,4 @@ export default function ChainApps(props: {

) -} \ No newline at end of file +} diff --git a/src/components/ChainApps/index.ts b/src/components/ChainApps/index.ts index 625df1e..47b61bd 100644 --- a/src/components/ChainApps/index.ts +++ b/src/components/ChainApps/index.ts @@ -1 +1 @@ -export { default } from "./ChainApps"; +export { default } from './ChainApps' diff --git a/src/components/ChainIcon/ChainIcon.tsx b/src/components/ChainIcon/ChainIcon.tsx index 702e8c7..739ef13 100644 --- a/src/components/ChainIcon/ChainIcon.tsx +++ b/src/components/ChainIcon/ChainIcon.tsx @@ -1,31 +1,26 @@ -import React from 'react'; -import OfflineBoltRoundedIcon from '@mui/icons-material/OfflineBoltRounded'; -import { SkaleNetwork } from '../../core/interfaces'; -import { chainIconPath } from '../../core/metadata'; - -import { cls } from '../../core/helper'; -import styles from "../../styles/styles.module.scss"; +import React from 'react' +import OfflineBoltRoundedIcon from '@mui/icons-material/OfflineBoltRounded' +import { SkaleNetwork } from '../../core/interfaces' +import { chainIconPath } from '../../core/metadata' +import { cls } from '../../core/helper' +import styles from '../../styles/styles.module.scss' export default function ChainIcon(props: { - skaleNetwork: SkaleNetwork, - chainName: string, - className?: string, - app?: string, + skaleNetwork: SkaleNetwork + chainName: string + className?: string + app?: string size?: 'xs' | 'sm' | 'md' | 'lg' }) { - const iconPath = chainIconPath( - props.skaleNetwork, - props.chainName, - props.app - ) - const size = props.size ?? 'sm'; - const className = styles[`chainIcon${size}`] + ' ' + props.className; + const iconPath = chainIconPath(props.skaleNetwork, props.chainName, props.app) + const size = props.size ?? 'sm' + const className = styles[`chainIcon${size}`] + ' ' + props.className if (iconPath !== undefined) { if (iconPath.default) { - return ; + return } - return ; + return } - return (); -} \ No newline at end of file + return +} diff --git a/src/components/ChainIcon/index.ts b/src/components/ChainIcon/index.ts index 78b7638..afe24d4 100644 --- a/src/components/ChainIcon/index.ts +++ b/src/components/ChainIcon/index.ts @@ -1 +1 @@ -export { default } from "./ChainIcon"; +export { default } from './ChainIcon' diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index 28c6e4f..04cf519 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -1,49 +1,46 @@ -import React from 'react'; -import Accordion from '@mui/material/Accordion'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import Typography from '@mui/material/Typography'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import Tooltip from '@mui/material/Tooltip'; -import Button from '@mui/material/Button'; +import React from 'react' +import Accordion from '@mui/material/Accordion' +import AccordionDetails from '@mui/material/AccordionDetails' +import AccordionSummary from '@mui/material/AccordionSummary' +import Typography from '@mui/material/Typography' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import Tooltip from '@mui/material/Tooltip' +import Button from '@mui/material/Button' +import ChainApps from '../ChainApps' +import ChainIcon from '../ChainIcon' -import ChainApps from '../ChainApps'; -import ChainIcon from '../ChainIcon'; - -import { MetaportConfig } from '../../core/interfaces'; - -import { cls, getChainAlias } from '../../core/helper'; -import common from "../../styles/common.module.scss"; -import styles from "../../styles/styles.module.scss"; +import { MetaportConfig } from '../../core/interfaces' +import { cls, getChainAlias } from '../../core/helper' +import common from '../../styles/common.module.scss' +import styles from '../../styles/styles.module.scss' export default function ChainsList(props: { - config: MetaportConfig, - expanded: string | false, - setExpanded: (expanded: string | false) => void, - setChain: (chain: string) => void, - chain: string, - disabledChain: string, - from?: boolean, + config: MetaportConfig + expanded: string | false + setExpanded: (expanded: string | false) => void + setChain: (chain: string) => void + chain: string + disabledChain: string + from?: boolean disabled?: boolean }) { - const handleChange = - (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { - props.setExpanded(isExpanded ? panel : false); - }; + const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { + props.setExpanded(isExpanded ? panel : false) + } - const schainNames = []; + const schainNames = [] for (let chain of props.config.chains) { if (chain != props.disabledChain && chain != props.chain) { - schainNames.push(chain); + schainNames.push(chain) } } function handle(schainName) { - props.setExpanded(false); - props.setChain(schainName); + props.setExpanded(false) + props.setChain(schainName) } return ( @@ -63,20 +60,10 @@ export default function ChainsList(props: { {props.chain ? (
- +
-

+

{getChainAlias(props.config.skaleNetwork, props.chain)}

@@ -91,74 +78,56 @@ export default function ChainsList(props: { ) : (
- +
-

+

Transfer {props.from ? 'from' : 'to'}...

- ) - } + )} -
+
- +
{schainNames.map((name) => ( -
- +
-
))}
-
+
) -} \ No newline at end of file +} diff --git a/src/components/ChainsList/index.ts b/src/components/ChainsList/index.ts index b0a2431..04f8e63 100644 --- a/src/components/ChainsList/index.ts +++ b/src/components/ChainsList/index.ts @@ -1 +1 @@ -export { default } from "./ChainsList"; +export { default } from './ChainsList' diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index dd2f691..3305422 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -1,25 +1,23 @@ -import React from 'react'; -import Button from '@mui/material/Button'; +import React from 'react' +import Button from '@mui/material/Button' -import { cls } from '../../core/helper'; -import common from "../../styles/common.module.scss"; -import styles from "../../styles/styles.module.scss"; +import { cls } from '../../core/helper' +import common from '../../styles/common.module.scss' +import styles from '../../styles/styles.module.scss' -import { ErrorMessage } from '../../core/dataclasses'; - -import LinkOffRoundedIcon from '@mui/icons-material/LinkOffRounded'; -import PublicOffRoundedIcon from '@mui/icons-material/PublicOffRounded'; -import SentimentDissatisfiedRoundedIcon from '@mui/icons-material/SentimentDissatisfiedRounded'; -import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded'; +import { ErrorMessage } from '../../core/dataclasses' +import LinkOffRoundedIcon from '@mui/icons-material/LinkOffRounded' +import PublicOffRoundedIcon from '@mui/icons-material/PublicOffRounded' +import SentimentDissatisfiedRoundedIcon from '@mui/icons-material/SentimentDissatisfiedRounded' +import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded' const ERROR_ICONS = { 'link-off': , 'public-off': , - 'sentiment': , - 'error': -}; - + sentiment: , + error: , +} export default function Error(props: { errorMessage: ErrorMessage }) { if (!props.errorMessage) return @@ -28,49 +26,63 @@ export default function Error(props: { errorMessage: ErrorMessage }) {
{ERROR_ICONS[props.errorMessage.icon]}
-

- Error occured -

-

- Please check logs in developer console -

-
-

+ )} + > + Error occured +

+

+ Please check logs in developer console +

+
+

{props.errorMessage.text}

- {props.errorMessage.fallback ? () : null} + {props.errorMessage.fallback ? ( + + ) : null}
) } diff --git a/src/components/ErrorMessage/index.ts b/src/components/ErrorMessage/index.ts index 26f0b73..e4f7f45 100644 --- a/src/components/ErrorMessage/index.ts +++ b/src/components/ErrorMessage/index.ts @@ -1 +1 @@ -export { default } from "./ErrorMessage"; \ No newline at end of file +export { default } from './ErrorMessage' diff --git a/src/components/Metaport/Metaport.tsx b/src/components/Metaport/Metaport.tsx index b96960c..8540da0 100644 --- a/src/components/Metaport/Metaport.tsx +++ b/src/components/Metaport/Metaport.tsx @@ -1,4 +1,3 @@ - /** * @license * SKALE Metaport @@ -21,89 +20,77 @@ * @copyright SKALE Labs 2023-Present */ -import { - RainbowKitProvider -} from '@rainbow-me/rainbowkit'; -import { configureChains, createConfig, WagmiConfig } from 'wagmi'; -import { mainnet, goerli } from 'wagmi/chains'; -import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'; -import { connectorsForWallets } from '@rainbow-me/rainbowkit'; +import { RainbowKitProvider } from '@rainbow-me/rainbowkit' +import { configureChains, createConfig, WagmiConfig } from 'wagmi' +import { mainnet, goerli } from 'wagmi/chains' +import { jsonRpcProvider } from 'wagmi/providers/jsonRpc' +import { connectorsForWallets } from '@rainbow-me/rainbowkit' -import { - injectedWallet, - coinbaseWallet, - metaMaskWallet -} from '@rainbow-me/rainbowkit/wallets'; +import { injectedWallet, coinbaseWallet, metaMaskWallet } from '@rainbow-me/rainbowkit/wallets' -import { MetaportConfig } from "../../core/interfaces" +import { MetaportConfig } from '../../core/interfaces' import WidgetUI from '../WidgetUI' -import '@rainbow-me/rainbowkit/styles.css'; - -import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network'; +import '@rainbow-me/rainbowkit/styles.css' +import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network' const { chains, webSocketPublicClient } = configureChains( - [ - mainnet, - goerli, - constructWagmiChain('staging', "staging-legal-crazy-castor"), - constructWagmiChain('staging', "staging-utter-unripe-menkar"), - constructWagmiChain('staging', "staging-faint-slimy-achird"), - constructWagmiChain('staging', "staging-perfect-parallel-gacrux"), - constructWagmiChain('staging', "staging-severe-violet-wezen"), - constructWagmiChain('staging', "staging-weepy-fitting-caph"), - - constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), - constructWagmiChain('mainnet', 'elated-tan-skat'), - constructWagmiChain('mainnet', 'affectionate-immediate-pollux') - ], - [ - jsonRpcProvider({ - rpc: chain => ({ - http: chain.rpcUrls.default.http[0], - webSocket: getWebSocketUrl(chain) - }), - }) - ] -); - + [ + mainnet, + goerli, + constructWagmiChain('staging', 'staging-legal-crazy-castor'), + constructWagmiChain('staging', 'staging-utter-unripe-menkar'), + constructWagmiChain('staging', 'staging-faint-slimy-achird'), + constructWagmiChain('staging', 'staging-perfect-parallel-gacrux'), + constructWagmiChain('staging', 'staging-severe-violet-wezen'), + constructWagmiChain('staging', 'staging-weepy-fitting-caph'), + + constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), + constructWagmiChain('mainnet', 'elated-tan-skat'), + constructWagmiChain('mainnet', 'affectionate-immediate-pollux'), + ], + [ + jsonRpcProvider({ + rpc: (chain) => ({ + http: chain.rpcUrls.default.http[0], + webSocket: getWebSocketUrl(chain), + }), + }), + ], +) const connectors = connectorsForWallets([ - { - groupName: 'Supported Wallets', - wallets: [ - metaMaskWallet({ chains, projectId: '' }), - injectedWallet({ chains }), - coinbaseWallet({ chains, appName: 'TEST' }) - ], - } -]); - + { + groupName: 'Supported Wallets', + wallets: [ + metaMaskWallet({ chains, projectId: '' }), + injectedWallet({ chains }), + coinbaseWallet({ chains, appName: 'TEST' }), + ], + }, +]) const wagmiConfig = createConfig({ - autoConnect: true, - connectors, - publicClient: webSocketPublicClient -}); - - -export default function Widget(props: { - config: MetaportConfig -}) { - return ( - - - - - - ) -} \ No newline at end of file + autoConnect: true, + connectors, + publicClient: webSocketPublicClient, +}) + +export default function Widget(props: { config: MetaportConfig }) { + return ( + + + + + + ) +} diff --git a/src/components/Metaport/index.ts b/src/components/Metaport/index.ts index 293cae0..d2d1941 100644 --- a/src/components/Metaport/index.ts +++ b/src/components/Metaport/index.ts @@ -1 +1 @@ -export { default } from "./Metaport"; +export { default } from './Metaport' diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index 4c1b552..a286ead 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -20,196 +20,174 @@ * @copyright SKALE Labs 2023-Present */ -import React from 'react'; -import { ConnectButton } from '@rainbow-me/rainbowkit'; +import React from 'react' +import { ConnectButton } from '@rainbow-me/rainbowkit' import Jazzicon, { jsNumberForAddress } from 'react-jazzicon' -import Button from '@mui/material/Button'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import Button from '@mui/material/Button' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { cls } from '../../core/helper'; +import { cls } from '../../core/helper' -import styles from "../../styles/styles.module.scss"; -import common from "../../styles/common.module.scss"; +import styles from '../../styles/styles.module.scss' +import common from '../../styles/common.module.scss' // import skaleLogoFull from '../WidgetUI/skale_logo.svg'; -import { useMetaportStore } from '../../store/MetaportState'; +import { useMetaportStore } from '../../store/MetaportState' -import ChainIcon from "../ChainIcon"; +import ChainIcon from '../ChainIcon' export default function SkConnect() { - const transferInProgress = useMetaportStore((state) => state.transferInProgress); - return ( - - {({ - account, - chain, - openAccountModal, - openChainModal, - openConnectModal, - authenticationStatus, - mounted, - }) => { - // Note: If your app doesn't use authentication, you - // can remove all 'authenticationStatus' checks - const ready = mounted && authenticationStatus !== 'loading'; - const connected = - ready && - account && - chain && - (!authenticationStatus || - authenticationStatus === 'authenticated'); + const transferInProgress = useMetaportStore((state) => state.transferInProgress) + return ( + + {({ account, chain, openAccountModal, openChainModal, openConnectModal, authenticationStatus, mounted }) => { + // Note: If your app doesn't use authentication, you + // can remove all 'authenticationStatus' checks + const ready = mounted && authenticationStatus !== 'loading' + const connected = + ready && account && chain && (!authenticationStatus || authenticationStatus === 'authenticated') + return ( +
+ {(() => { + if (!connected) { return ( -
+
+ {/* */} +
+
+ + + + + + + + +
+

- {(() => { - if (!connected) { - return ( -

-
- {/* */} -
-
- - - - - - - - -
-

Connect a wallet to use SKALE Metaport

- -
- - ); - } - if (chain.unsupported) { - return ( - - - - ); - } - return ( -
-
- {/* + +
+ ) + } + if (chain.unsupported) { + return ( + + ) + } + return ( +
+
+ {/* */} -
-
-
+
+ -
- -
- ); - })()} -
- ); - }} - ) -}; \ No newline at end of file + + +
+
+ ) + })()} +
+ ) + }} + + ) +} diff --git a/src/components/SkConnect/index.ts b/src/components/SkConnect/index.ts index 03890dd..1395555 100644 --- a/src/components/SkConnect/index.ts +++ b/src/components/SkConnect/index.ts @@ -1 +1 @@ -export { default } from "./SkConnect"; +export { default } from './SkConnect' diff --git a/src/components/SkPaper/SkPaper.tsx b/src/components/SkPaper/SkPaper.tsx index 537686d..5d6cd95 100644 --- a/src/components/SkPaper/SkPaper.tsx +++ b/src/components/SkPaper/SkPaper.tsx @@ -19,37 +19,41 @@ /** * @file SkPaper.ts * @copyright SKALE Labs 2023-Present -*/ + */ -import React, { ReactElement } from 'react'; -import { cls } from '../../core/helper'; +import React, { ReactElement } from 'react' +import { cls } from '../../core/helper' -import styles from "../../styles/styles.module.scss"; -import common from "../../styles/common.module.scss"; +import styles from '../../styles/styles.module.scss' +import common from '../../styles/common.module.scss' import { useUIStore } from '../../store/Store' - export default function SkPaper(props: { - className?: string, - children?: ReactElement | ReactElement[], - background?: string, - gray?: boolean, - rounded?: boolean, - fullHeight?: boolean, - margTop?: boolean + className?: string + children?: ReactElement | ReactElement[] + background?: string + gray?: boolean + rounded?: boolean + fullHeight?: boolean + margTop?: boolean }) { - const metaportTheme = useUIStore((state) => state.theme); - const localStyle = { - 'background': props.background ?? metaportTheme.background - }; - return (
state.theme) + const localStyle = { + background: props.background ?? metaportTheme.background, + } + return ( +
- {props.children} -
) -} \ No newline at end of file + )} + > + {props.children} +
+ ) +} diff --git a/src/components/SkPaper/index.ts b/src/components/SkPaper/index.ts index d565a0d..bee60c9 100644 --- a/src/components/SkPaper/index.ts +++ b/src/components/SkPaper/index.ts @@ -1 +1 @@ -export { default } from "./SkPaper"; +export { default } from './SkPaper' diff --git a/src/components/SkeletonLoader/SkeletonLoader.tsx b/src/components/SkeletonLoader/SkeletonLoader.tsx index 8a4bc8c..bb91a3b 100644 --- a/src/components/SkeletonLoader/SkeletonLoader.tsx +++ b/src/components/SkeletonLoader/SkeletonLoader.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import Skeleton from '@mui/material/Skeleton'; +import React from 'react' +import Skeleton from '@mui/material/Skeleton' export default function SkeletonLoader(props) { return ( diff --git a/src/components/SkeletonLoader/index.ts b/src/components/SkeletonLoader/index.ts index de11545..0d3d587 100644 --- a/src/components/SkeletonLoader/index.ts +++ b/src/components/SkeletonLoader/index.ts @@ -1 +1 @@ -export { default } from "./SkeletonLoader"; +export { default } from './SkeletonLoader' diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 2851325..2047ed1 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -1,131 +1,135 @@ -import React, { useEffect, useState } from 'react'; - -import Box from '@mui/material/Box'; -import Stepper from '@mui/material/Stepper'; -import Step from '@mui/material/Step'; -import StepLabel from '@mui/material/StepLabel'; -import StepContent from '@mui/material/StepContent'; -import Button from '@mui/material/Button'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import { cls, getChainAlias, getRandom } from '../../core/helper'; -import common from '../../styles/common.module.scss'; -import styles from '../../styles/styles.module.scss'; -import localStyles from './SkStepper.module.scss'; -import ChainIcon from "../ChainIcon"; -import SkPaper from "../SkPaper"; +import React, { useEffect, useState } from 'react' + +import Box from '@mui/material/Box' +import Stepper from '@mui/material/Stepper' +import Step from '@mui/material/Step' +import StepLabel from '@mui/material/StepLabel' +import StepContent from '@mui/material/StepContent' +import Button from '@mui/material/Button' +import LoadingButton from '@mui/lab/LoadingButton' + +import { cls, getChainAlias, getRandom } from '../../core/helper' +import common from '../../styles/common.module.scss' +import styles from '../../styles/styles.module.scss' +import localStyles from './SkStepper.module.scss' +import ChainIcon from '../ChainIcon' +import SkPaper from '../SkPaper' import { useMetaportStore } from '../../store/MetaportState' -import { Collapse } from '@mui/material'; -import { SkaleNetwork } from '../../core/interfaces'; +import { Collapse } from '@mui/material' +import { SkaleNetwork } from '../../core/interfaces' import { useWalletClient } from 'wagmi' - -import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded'; +import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded' import { useSwitchNetwork, useAccount } from 'wagmi' -import { SUCCESS_EMOJIS } from '../../core/constants'; - - -export default function SkStepper(props: { - skaleNetwork: SkaleNetwork -}) { +import { SUCCESS_EMOJIS } from '../../core/constants' +export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { const { address } = useAccount() const { switchNetworkAsync } = useSwitchNetwork() const { data: walletClient } = useWalletClient() - const stepsMetadata = useMetaportStore((state) => state.stepsMetadata); - const currentStep = useMetaportStore((state) => state.currentStep); - const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage); - const actionBtnDisabled = useMetaportStore((state) => state.actionBtnDisabled); - const loading = useMetaportStore((state) => state.loading); - const btnText = useMetaportStore((state) => state.btnText); + const stepsMetadata = useMetaportStore((state) => state.stepsMetadata) + const currentStep = useMetaportStore((state) => state.currentStep) + const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage) + const actionBtnDisabled = useMetaportStore((state) => state.actionBtnDisabled) + const loading = useMetaportStore((state) => state.loading) + const btnText = useMetaportStore((state) => state.btnText) - const execute = useMetaportStore((state) => state.execute); - const startOver = useMetaportStore((state) => state.startOver); + const execute = useMetaportStore((state) => state.execute) + const startOver = useMetaportStore((state) => state.startOver) - const [emoji, setEmoji] = useState(); + const [emoji, setEmoji] = useState() useEffect(() => { - setEmoji(getRandom(SUCCESS_EMOJIS)); - }, []); + setEmoji(getRandom(SUCCESS_EMOJIS)) + }, []) - if (stepsMetadata.length === 0) return (
); + if (stepsMetadata.length === 0) return
return ( - {stepsMetadata.map((step, i) => ( - -
+ {stepsMetadata.map((step, i) => ( + +
-

{step.headline}

-
- +
+

{step.headline}

+
+ +
+

+ {getChainAlias(props.skaleNetwork, step.onSource ? step.from : step.to)} +

-

{getChainAlias( - props.skaleNetwork, - step.onSource ? step.from : step.to - )}

-
-
- - -

- {step.text} -

-
- {loading ? ( - - {btnText} - {/* {props.loadingTokens ? 'Loading...' : step.btnLoadingText} */} - - ) : ( - - )} -
-
-
-
))} + + + +

+ {step.text} +

+
+ {loading ? ( + + {btnText} + {/* {props.loadingTokens ? 'Loading...' : step.btnLoadingText} */} + + ) : ( + + )} +
+
+
+ + ))} - {currentStep === stepsMetadata.length && (
-

+

{emoji} Transfer completed

@@ -139,11 +143,9 @@ export default function SkStepper(props: { Start over
- )} - - ); -} \ No newline at end of file + ) +} diff --git a/src/components/Stepper/index.ts b/src/components/Stepper/index.ts index e3595f4..8a38c36 100644 --- a/src/components/Stepper/index.ts +++ b/src/components/Stepper/index.ts @@ -1 +1 @@ -export { default } from "./SkStepper"; +export { default } from './SkStepper' diff --git a/src/components/SwitchDirection/SwitchDirection.tsx b/src/components/SwitchDirection/SwitchDirection.tsx index e0b1855..6d5138c 100644 --- a/src/components/SwitchDirection/SwitchDirection.tsx +++ b/src/components/SwitchDirection/SwitchDirection.tsx @@ -1,73 +1,69 @@ -import React, { useRef } from 'react'; +import React, { useRef } from 'react' -import IconButton from '@mui/material/IconButton'; -import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded'; -import styles from '../../styles/styles.module.scss'; -import common from '../../styles/common.module.scss'; -import { cls } from '../../core/helper'; +import IconButton from '@mui/material/IconButton' +import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded' +import styles from '../../styles/styles.module.scss' +import common from '../../styles/common.module.scss' +import { cls } from '../../core/helper' import { useUIStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' - export default function SwitchDirection() { + const myElement = useRef(null) - const myElement = useRef(null); - - const metaportTheme = useUIStore((state) => state.theme); + const metaportTheme = useUIStore((state) => state.theme) - const chainName1 = useMetaportStore((state) => state.chainName1); - const chainName2 = useMetaportStore((state) => state.chainName2); + const chainName1 = useMetaportStore((state) => state.chainName1) + const chainName2 = useMetaportStore((state) => state.chainName2) - const setChainName1 = useMetaportStore((state) => state.setChainName1); - const setChainName2 = useMetaportStore((state) => state.setChainName2); - const startOver = useMetaportStore((state) => state.startOver); - const loading = useMetaportStore((state) => state.loading); - const transferInProgress = useMetaportStore((state) => state.transferInProgress); + const setChainName1 = useMetaportStore((state) => state.setChainName1) + const setChainName2 = useMetaportStore((state) => state.setChainName2) + const startOver = useMetaportStore((state) => state.startOver) + const loading = useMetaportStore((state) => state.loading) + const transferInProgress = useMetaportStore((state) => state.transferInProgress) return (
-
-
+
+ borderRadius: '50%', + }} + > { - const element = myElement.current; + const element = myElement.current const rotate = () => { if (element) { - element.classList.add('spin'); + element.classList.add('spin') setTimeout(() => { - element.classList.remove('spin'); - }, 400); + element.classList.remove('spin') + }, 400) } - }; + } rotate() - let chain1 = chainName1; - setChainName1(chainName2); - setChainName2(chain1); - startOver(); - }}> + let chain1 = chainName1 + setChainName1(chainName2) + setChainName2(chain1) + startOver() + }} + >
-
-
- - +
) -} \ No newline at end of file +} diff --git a/src/components/SwitchDirection/index.ts b/src/components/SwitchDirection/index.ts index b86abe8..0540fc4 100644 --- a/src/components/SwitchDirection/index.ts +++ b/src/components/SwitchDirection/index.ts @@ -1 +1 @@ -export { default } from "./SwitchDirection"; +export { default } from './SwitchDirection' diff --git a/src/components/TokenIcon/TokenIcon.tsx b/src/components/TokenIcon/TokenIcon.tsx index fd5290c..4722c48 100644 --- a/src/components/TokenIcon/TokenIcon.tsx +++ b/src/components/TokenIcon/TokenIcon.tsx @@ -21,27 +21,23 @@ * @copyright SKALE Labs 2023-Present */ -import React from 'react'; -import TollRoundedIcon from '@mui/icons-material/TollRounded'; -import { TokenData } from '../../core/dataclasses'; -import { tokenIconPath } from '../../core/metadata'; +import React from 'react' +import TollRoundedIcon from '@mui/icons-material/TollRounded' +import { TokenData } from '../../core/dataclasses' +import { tokenIconPath } from '../../core/metadata' -import styles from "../../styles/styles.module.scss"; +import styles from '../../styles/styles.module.scss' - -export default function TokenIcon(props: { - token?: TokenData, - size?: 'xs' | 'sm' | 'md' | 'lg' -}) { - const size = props.size ?? 'sm'; - const className = styles[`chainIcon${size}`]; +export default function TokenIcon(props: { token?: TokenData; size?: 'xs' | 'sm' | 'md' | 'lg' }) { + const size = props.size ?? 'sm' + const className = styles[`chainIcon${size}`] if (props.token === undefined || props.token === null) { return } - const iconPath = tokenIconPath(props.token); + const iconPath = tokenIconPath(props.token) if (iconPath.default) { - return ; + return } - return ; -} \ No newline at end of file + return +} diff --git a/src/components/TokenIcon/index.ts b/src/components/TokenIcon/index.ts index 6412a21..2440af5 100644 --- a/src/components/TokenIcon/index.ts +++ b/src/components/TokenIcon/index.ts @@ -1 +1 @@ -export { default } from "./TokenIcon"; +export { default } from './TokenIcon' diff --git a/src/components/TokenList/TokenBalance.tsx b/src/components/TokenList/TokenBalance.tsx index 802f5e1..559efc9 100644 --- a/src/components/TokenList/TokenBalance.tsx +++ b/src/components/TokenList/TokenBalance.tsx @@ -1,39 +1,27 @@ -import React from 'react'; -import { formatUnits } from 'ethers'; +import React from 'react' +import { formatUnits } from 'ethers' -import { TokenType, TokenData } from '../../core/dataclasses'; -import { TokenBalancesMap } from '../../core/interfaces'; - -import { cls } from '../../core/helper'; -import common from "../../styles/common.module.scss"; +import { TokenType, TokenData } from '../../core/dataclasses' +import { TokenBalancesMap } from '../../core/interfaces' +import { cls } from '../../core/helper' +import common from '../../styles/common.module.scss' function formatBalance(balance: bigint, token: TokenData): string { - return formatUnits(balance, parseInt(token.meta.decimals)); + return formatUnits(balance, parseInt(token.meta.decimals)) } +export default function TokenBalance(props: { token: TokenData; tokenBalances: TokenBalancesMap }) { + if ([TokenType.erc721, TokenType.erc721meta, TokenType.erc1155].includes(props.token.type)) return -export default function TokenBalance(props: { - token: TokenData, - tokenBalances: TokenBalancesMap -}) { - if ([TokenType.erc721, TokenType.erc721meta, TokenType.erc1155].includes(props.token.type)) return; - - const balance = props.tokenBalances[props.token.keyname]; + const balance = props.tokenBalances[props.token.keyname] - if (balance === undefined || balance === null) return; - return ( -
-

- {formatBalance(balance, props.token)} {props.token.meta.symbol} -

-
- ) -} \ No newline at end of file + if (balance === undefined || balance === null) return + return ( +
+

+ {formatBalance(balance, props.token)} {props.token.meta.symbol} +

+
+ ) +} diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index 72a5f93..3f0d7ee 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -1,82 +1,75 @@ -import { useEffect } from 'react'; -import React from 'react'; +import { useEffect } from 'react' +import React from 'react' import { useAccount } from 'wagmi' -import Accordion from '@mui/material/Accordion'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import AccordionSummary from '@mui/material/AccordionSummary'; +import Accordion from '@mui/material/Accordion' +import AccordionDetails from '@mui/material/AccordionDetails' +import AccordionSummary from '@mui/material/AccordionSummary' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { getAvailableTokensTotal, getDefaultToken } from '../../core/tokens/helper'; +import { getAvailableTokensTotal, getDefaultToken } from '../../core/tokens/helper' -import { cls } from '../../core/helper'; +import { cls } from '../../core/helper' -import ErrorMessage from '../ErrorMessage'; +import ErrorMessage from '../ErrorMessage' -import TokenListSection from '../TokenListSection'; -import TokenBalance from './TokenBalance'; -import TokenIcon from "../TokenIcon"; +import TokenListSection from '../TokenListSection' +import TokenBalance from './TokenBalance' +import TokenIcon from '../TokenIcon' -import styles from "../../styles/styles.module.scss"; -import common from "../../styles/common.module.scss"; -import { getTokenName } from "../../core/metadata"; - -import { useCollapseStore } from '../../store/Store'; -import { useMetaportStore } from '../../store/MetaportState'; -import { TokenType, NoTokenPairsMessage } from '../../core/dataclasses'; +import styles from '../../styles/styles.module.scss' +import common from '../../styles/common.module.scss' +import { getTokenName } from '../../core/metadata' +import { useCollapseStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' +import { TokenType, NoTokenPairsMessage } from '../../core/dataclasses' export default function TokenList() { + const token = useMetaportStore((state) => state.token) + const tokens = useMetaportStore((state) => state.tokens) + const setToken = useMetaportStore((state) => state.setToken) + const updateTokenBalances = useMetaportStore((state) => state.updateTokenBalances) + const tokenContracts = useMetaportStore((state) => state.tokenContracts) - const token = useMetaportStore((state) => state.token); - const tokens = useMetaportStore((state) => state.tokens); - const setToken = useMetaportStore((state) => state.setToken); - const updateTokenBalances = useMetaportStore((state) => state.updateTokenBalances); - const tokenContracts = useMetaportStore((state) => state.tokenContracts); - - const tokenBalances = useMetaportStore((state) => state.tokenBalances); - const transferInProgress = useMetaportStore((state) => state.transferInProgress); - - const expandedTokens = useCollapseStore((state) => state.expandedTokens); - const setExpandedTokens = useCollapseStore((state) => state.setExpandedTokens); + const tokenBalances = useMetaportStore((state) => state.tokenBalances) + const transferInProgress = useMetaportStore((state) => state.transferInProgress) - const { address } = useAccount(); + const expandedTokens = useCollapseStore((state) => state.expandedTokens) + const setExpandedTokens = useCollapseStore((state) => state.setExpandedTokens) + const { address } = useAccount() useEffect(() => { - updateTokenBalances(address); // Fetch users immediately on component mount + updateTokenBalances(address) // Fetch users immediately on component mount const intervalId = setInterval(() => { - updateTokenBalances(address); + updateTokenBalances(address) }, 10000) // Fetch users every 10 seconds return () => { clearInterval(intervalId) // Clear interval on component unmount } - }, [updateTokenBalances, tokenContracts, address]); + }, [updateTokenBalances, tokenContracts, address]) useEffect(() => { - const defaultToken = getDefaultToken(tokens); + const defaultToken = getDefaultToken(tokens) if (defaultToken) { - setToken(defaultToken); + setToken(defaultToken) } - }, [tokens]); + }, [tokens]) + let availableTokensTotal = getAvailableTokensTotal(tokens) + let disabled = availableTokensTotal === 1 + let noTokens = availableTokensTotal === 0 - let availableTokensTotal = getAvailableTokensTotal(tokens); - let disabled = availableTokensTotal === 1; - let noTokens = availableTokensTotal === 0; - - const handleChange = - (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { - setExpandedTokens(isExpanded ? panel : false); - }; + const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { + setExpandedTokens(isExpanded ? panel : false) + } if (noTokens) { - return () + return } return ( @@ -92,21 +85,23 @@ export default function TokenList() { aria-controls="panel1bh-content" id="panel1bh-header" className={styles.accordionSummary} - style={{paddingTop: '0'}} + style={{ paddingTop: '0' }} >
-

+

{token ? getTokenName(token) : 'Select token'}

@@ -150,4 +145,4 @@ export default function TokenList() {
) -} \ No newline at end of file +} diff --git a/src/components/TokenList/index.ts b/src/components/TokenList/index.ts index 152b99b..ba9b61b 100644 --- a/src/components/TokenList/index.ts +++ b/src/components/TokenList/index.ts @@ -1 +1 @@ -export { default } from "./TokenList"; +export { default } from './TokenList' diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index 791797a..131f213 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -1,36 +1,33 @@ -import React from 'react'; -import Button from '@mui/material/Button'; +import React from 'react' +import Button from '@mui/material/Button' -import { TokenData, TokenType } from '../../core/dataclasses'; -import { TokenBalancesMap, TokenDataMap } from '../../core/interfaces'; -import { cls } from '../../core/helper'; +import { TokenData, TokenType } from '../../core/dataclasses' +import { TokenBalancesMap, TokenDataMap } from '../../core/interfaces' +import { cls } from '../../core/helper' -import TokenBalance from '../TokenList/TokenBalance'; -import TokenIcon from '../TokenIcon'; +import TokenBalance from '../TokenList/TokenBalance' +import TokenIcon from '../TokenIcon' -import common from "../../styles/common.module.scss"; - -import { getTokenName } from "../../core/metadata"; +import common from '../../styles/common.module.scss' +import { getTokenName } from '../../core/metadata' export default function TokenListSection(props: { - setExpanded: (expanded: string | false) => void, - setToken: (token: TokenData) => void, - tokens: TokenDataMap, - type: TokenType, + setExpanded: (expanded: string | false) => void + setToken: (token: TokenData) => void + tokens: TokenDataMap + type: TokenType tokenBalances?: TokenBalancesMap }) { - function handle(tokenData: TokenData): void { - props.setExpanded(false); - props.setToken(tokenData); + props.setExpanded(false) + props.setToken(tokenData) } - if (Object.keys(props.tokens).length === 0) return; + if (Object.keys(props.tokens).length === 0) return return ( -
+

@@ -53,26 +50,24 @@ export default function TokenListSection(props: { className={common.fullWidth} onClick={() => handle(props.tokens[key])} > -

+
-

+

{getTokenName(props.tokens[key])}

@@ -83,4 +78,4 @@ export default function TokenListSection(props: { ))}
) -} \ No newline at end of file +} diff --git a/src/components/TokenListSection/index.ts b/src/components/TokenListSection/index.ts index e177cb9..76e5a12 100644 --- a/src/components/TokenListSection/index.ts +++ b/src/components/TokenListSection/index.ts @@ -1 +1 @@ -export { default } from "./TokenListSection"; +export { default } from './TokenListSection' diff --git a/src/components/Widget/Widget.mdx b/src/components/Widget/Widget.mdx index bdf93cc..425c0a4 100644 --- a/src/components/Widget/Widget.mdx +++ b/src/components/Widget/Widget.mdx @@ -1,14 +1,13 @@ -import { useState } from "react"; +import { useState } from 'react' -import { Canvas, Story, ArgsTable } from "@storybook/blocks"; +import { Canvas, Story, ArgsTable } from '@storybook/blocks' -import Grid from "@mui/material/Grid"; -import TextField from "@mui/material/TextField"; -import Button from "@mui/material/Button"; - -import { internalEvents } from "../../core/events"; -import * as WidgetStories from "./Widget.stories"; +import Grid from '@mui/material/Grid' +import TextField from '@mui/material/TextField' +import Button from '@mui/material/Button' +import { internalEvents } from '../../core/events' +import * as WidgetStories from './Widget.stories' # Functional Metaport Demo diff --git a/src/components/Widget/Widget.stories.tsx b/src/components/Widget/Widget.stories.tsx index c052ee8..1d9405d 100644 --- a/src/components/Widget/Widget.stories.tsx +++ b/src/components/Widget/Widget.stories.tsx @@ -1,23 +1,21 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import Widget from "./Widget"; +import type { Meta, StoryObj } from '@storybook/react' +import Widget from './Widget' // import { storyDecorator } from "../WidgetUI/StorybookHelper"; -const METAPORT_CONFIG = require('../../metadata/metaportConfigStaging.json'); -METAPORT_CONFIG.mainnetEndpoint = process.env.STORYBOOK_MAINNET_ENDPOINT; - +const METAPORT_CONFIG = require('../../metadata/metaportConfigStaging.json') +METAPORT_CONFIG.mainnetEndpoint = process.env.STORYBOOK_MAINNET_ENDPOINT const meta: Meta = { - title: "Functional/Widget", + title: 'Functional/Widget', component: Widget, // decorators: [storyDecorator], -}; - -export default meta; -type Story = StoryObj; +} +export default meta +type Story = StoryObj export const WidgetDemo: Story = { args: { - config: METAPORT_CONFIG - } -}; \ No newline at end of file + config: METAPORT_CONFIG, + }, +} diff --git a/src/components/Widget/Widget.tsx b/src/components/Widget/Widget.tsx index 32dc876..12fbec2 100644 --- a/src/components/Widget/Widget.tsx +++ b/src/components/Widget/Widget.tsx @@ -1,4 +1,3 @@ - /** * @license * SKALE Metaport @@ -21,121 +20,106 @@ * @copyright SKALE Labs 2023-Present */ -import React, { useEffect } from 'react'; - -import { - RainbowKitProvider, - darkTheme, - lightTheme -} from '@rainbow-me/rainbowkit'; -import { configureChains, createConfig, WagmiConfig } from 'wagmi'; -import { mainnet, goerli } from 'wagmi/chains'; -import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'; -import { connectorsForWallets } from '@rainbow-me/rainbowkit'; +import React, { useEffect } from 'react' +import { RainbowKitProvider, darkTheme, lightTheme } from '@rainbow-me/rainbowkit' +import { configureChains, createConfig, WagmiConfig } from 'wagmi' +import { mainnet, goerli } from 'wagmi/chains' +import { jsonRpcProvider } from 'wagmi/providers/jsonRpc' +import { connectorsForWallets } from '@rainbow-me/rainbowkit' -import { - injectedWallet, - coinbaseWallet, - metaMaskWallet -} from '@rainbow-me/rainbowkit/wallets'; +import { injectedWallet, coinbaseWallet, metaMaskWallet } from '@rainbow-me/rainbowkit/wallets' -import { MetaportConfig } from "../../core/interfaces" +import { MetaportConfig } from '../../core/interfaces' import WidgetUI from '../WidgetUI' import { useUIStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' -import { getWidgetTheme } from '../../core/themes'; +import { getWidgetTheme } from '../../core/themes' import MetaportCore from '../../core/metaport' -import '@rainbow-me/rainbowkit/styles.css'; - -import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network'; +import '@rainbow-me/rainbowkit/styles.css' +import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network' const { chains, webSocketPublicClient } = configureChains( - [ - mainnet, - goerli, - constructWagmiChain('staging', "staging-legal-crazy-castor"), - constructWagmiChain('staging', "staging-utter-unripe-menkar"), - constructWagmiChain('staging', "staging-faint-slimy-achird"), - constructWagmiChain('staging', "staging-perfect-parallel-gacrux"), - constructWagmiChain('staging', "staging-severe-violet-wezen"), - constructWagmiChain('staging', "staging-weepy-fitting-caph"), - - constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), - constructWagmiChain('mainnet', 'elated-tan-skat'), - constructWagmiChain('mainnet', 'affectionate-immediate-pollux') - ], - [ - jsonRpcProvider({ - rpc: chain => ({ - http: chain.rpcUrls.default.http[0], - webSocket: getWebSocketUrl(chain) - }), - }) - ] -); - + [ + mainnet, + goerli, + constructWagmiChain('staging', 'staging-legal-crazy-castor'), + constructWagmiChain('staging', 'staging-utter-unripe-menkar'), + constructWagmiChain('staging', 'staging-faint-slimy-achird'), + constructWagmiChain('staging', 'staging-perfect-parallel-gacrux'), + constructWagmiChain('staging', 'staging-severe-violet-wezen'), + constructWagmiChain('staging', 'staging-weepy-fitting-caph'), + + constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), + constructWagmiChain('mainnet', 'elated-tan-skat'), + constructWagmiChain('mainnet', 'affectionate-immediate-pollux'), + ], + [ + jsonRpcProvider({ + rpc: (chain) => ({ + http: chain.rpcUrls.default.http[0], + webSocket: getWebSocketUrl(chain), + }), + }), + ], +) const connectors = connectorsForWallets([ - { - groupName: 'Supported Wallets', - wallets: [ - metaMaskWallet({ chains, projectId: '' }), - injectedWallet({ chains }), - coinbaseWallet({ chains, appName: 'TEST' }) - ], - } -]); - + { + groupName: 'Supported Wallets', + wallets: [ + metaMaskWallet({ chains, projectId: '' }), + injectedWallet({ chains }), + coinbaseWallet({ chains, appName: 'TEST' }), + ], + }, +]) const wagmiConfig = createConfig({ - autoConnect: true, - connectors, - publicClient: webSocketPublicClient -}); - - -export default function Widget(props: { - config: MetaportConfig -}) { - const widgetTheme = getWidgetTheme(props.config.theme); - const theme = widgetTheme.mode === 'dark' ? darkTheme() : lightTheme(); - - const setTheme = useUIStore((state) => state.setTheme); - const setMpc = useMetaportStore((state) => state.setMpc); - const setOpen = useUIStore((state) => state.setOpen); - - theme.colors.connectButtonInnerBackground = widgetTheme.background; - theme.colors.connectButtonBackground = widgetTheme.background; - - useEffect(() => { - setOpen(props.config.openOnLoad); - }, []); - - useEffect(() => { - setTheme(widgetTheme); - }, [setTheme]); - - useEffect(() => { - setMpc(new MetaportCore(props.config)); - }, [setMpc]); - - return ( - - - - - - ) -} \ No newline at end of file + autoConnect: true, + connectors, + publicClient: webSocketPublicClient, +}) + +export default function Widget(props: { config: MetaportConfig }) { + const widgetTheme = getWidgetTheme(props.config.theme) + const theme = widgetTheme.mode === 'dark' ? darkTheme() : lightTheme() + + const setTheme = useUIStore((state) => state.setTheme) + const setMpc = useMetaportStore((state) => state.setMpc) + const setOpen = useUIStore((state) => state.setOpen) + + theme.colors.connectButtonInnerBackground = widgetTheme.background + theme.colors.connectButtonBackground = widgetTheme.background + + useEffect(() => { + setOpen(props.config.openOnLoad) + }, []) + + useEffect(() => { + setTheme(widgetTheme) + }, [setTheme]) + + useEffect(() => { + setMpc(new MetaportCore(props.config)) + }, [setMpc]) + + return ( + + + + + + ) +} diff --git a/src/components/Widget/index.ts b/src/components/Widget/index.ts index 820393d..81db243 100644 --- a/src/components/Widget/index.ts +++ b/src/components/Widget/index.ts @@ -1 +1 @@ -export { default } from "./Widget"; +export { default } from './Widget' diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index ae621f2..fa7f668 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -1,56 +1,51 @@ -import React from 'react'; +import React from 'react' import { useCollapseStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' -import TokenList from '../TokenList'; -import ChainsList from '../ChainsList'; -import AmountInput from '../AmountInput'; -import SkStepper from '../Stepper'; -import SkPaper from '../SkPaper'; -import AmountErrorMessage from '../AmountErrorMessage'; -import SwitchDirection from '../SwitchDirection'; - -import common from '../../styles/common.module.scss'; -import { cls } from '../../core/helper'; -import { Collapse } from '@mui/material'; +import TokenList from '../TokenList' +import ChainsList from '../ChainsList' +import AmountInput from '../AmountInput' +import SkStepper from '../Stepper' +import SkPaper from '../SkPaper' +import AmountErrorMessage from '../AmountErrorMessage' +import SwitchDirection from '../SwitchDirection' +import common from '../../styles/common.module.scss' +import { cls } from '../../core/helper' +import { Collapse } from '@mui/material' export function WidgetBody(props) { + const expandedFrom = useCollapseStore((state) => state.expandedFrom) + const setExpandedFrom = useCollapseStore((state) => state.setExpandedFrom) - const expandedFrom = useCollapseStore((state) => state.expandedFrom); - const setExpandedFrom = useCollapseStore((state) => state.setExpandedFrom); - - const expandedTo = useCollapseStore((state) => state.expandedTo); - const setExpandedTo = useCollapseStore((state) => state.setExpandedTo); + const expandedTo = useCollapseStore((state) => state.expandedTo) + const setExpandedTo = useCollapseStore((state) => state.setExpandedTo) - const token = useMetaportStore((state) => state.token); - const chainName1 = useMetaportStore((state) => state.chainName1); - const chainName2 = useMetaportStore((state) => state.chainName2); + const token = useMetaportStore((state) => state.token) + const chainName1 = useMetaportStore((state) => state.chainName1) + const chainName2 = useMetaportStore((state) => state.chainName2) - const setChainName1 = useMetaportStore((state) => state.setChainName1); - const setChainName2 = useMetaportStore((state) => state.setChainName2); + const setChainName1 = useMetaportStore((state) => state.setChainName1) + const setChainName2 = useMetaportStore((state) => state.setChainName2) - const transferInProgress = useMetaportStore((state) => state.transferInProgress); + const transferInProgress = useMetaportStore((state) => state.transferInProgress) return (
- + -
+
@@ -70,7 +65,6 @@ export function WidgetBody(props) { setExpanded={setExpandedTo} chain={chainName2} setChain={setChainName2} - disabledChain={chainName1} disabled={transferInProgress} /> @@ -83,14 +77,11 @@ export function WidgetBody(props) { {token ? : null}
*/} -
- - ); + ) } - -export default WidgetBody; +export default WidgetBody diff --git a/src/components/WidgetBody/index.ts b/src/components/WidgetBody/index.ts index f10e885..9480734 100644 --- a/src/components/WidgetBody/index.ts +++ b/src/components/WidgetBody/index.ts @@ -1 +1 @@ -export { default } from "./WidgetBody"; +export { default } from './WidgetBody' diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index e71b370..ea7402a 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -20,67 +20,64 @@ * @copyright SKALE Labs 2023-Present */ -import React, { useEffect } from 'react'; -import { StyledEngineProvider } from '@mui/material/styles'; +import React, { useEffect } from 'react' +import { StyledEngineProvider } from '@mui/material/styles' -import { useAccount } from 'wagmi'; +import { useAccount } from 'wagmi' -import Collapse from '@mui/material/Collapse'; -import Fab from '@mui/material/Fab'; -import CloseIcon from '@mui/icons-material/Close'; +import Collapse from '@mui/material/Collapse' +import Fab from '@mui/material/Fab' +import CloseIcon from '@mui/icons-material/Close' -import { createTheme, ThemeProvider } from '@mui/material/styles'; -import { getMuiZIndex } from '../../core/themes'; +import { createTheme, ThemeProvider } from '@mui/material/styles' +import { getMuiZIndex } from '../../core/themes' -import skaleLogo from './skale_logo_short.svg'; +import skaleLogo from './skale_logo_short.svg' import { useUIStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' -import SkPaper from '../SkPaper'; +import SkPaper from '../SkPaper' -import WidgetBody from '../WidgetBody'; +import WidgetBody from '../WidgetBody' +import { cls } from '../../core/helper' -import { cls } from '../../core/helper'; +import styles from '../../styles/styles.module.scss' +import common from '../../styles/common.module.scss' +import { PaletteMode } from '@mui/material' -import styles from "../../styles/styles.module.scss"; -import common from "../../styles/common.module.scss"; -import { PaletteMode } from '@mui/material'; +import { getWidgetTheme } from '../../core/themes' -import { getWidgetTheme } from '../../core/themes'; - -import SkConnect from '../SkConnect'; -import ErrorMessage from '../ErrorMessage'; -import { MetaportConfig } from '../../core/interfaces'; +import SkConnect from '../SkConnect' +import ErrorMessage from '../ErrorMessage' +import { MetaportConfig } from '../../core/interfaces' import MetaportCore from '../../core/metaport' - export function WidgetUI(props: { config: MetaportConfig }) { + const widgetTheme = getWidgetTheme(props.config.theme) - const widgetTheme = getWidgetTheme(props.config.theme); - - const setTheme = useUIStore((state) => state.setTheme); - const setMpc = useMetaportStore((state) => state.setMpc); - const setOpen = useUIStore((state) => state.setOpen); + const setTheme = useUIStore((state) => state.setTheme) + const setMpc = useMetaportStore((state) => state.setMpc) + const setOpen = useUIStore((state) => state.setOpen) useEffect(() => { - setOpen(props.config.openOnLoad); - }, []); + setOpen(props.config.openOnLoad) + }, []) useEffect(() => { - setTheme(widgetTheme); - }, [setTheme]); + setTheme(widgetTheme) + }, [setTheme]) useEffect(() => { - setMpc(new MetaportCore(props.config)); - }, [setMpc]); + setMpc(new MetaportCore(props.config)) + }, [setMpc]) - const { address } = useAccount(); + const { address } = useAccount() - const metaportTheme = useUIStore((state) => state.theme); - const isOpen = useUIStore((state) => state.open); + const metaportTheme = useUIStore((state) => state.theme) + const isOpen = useUIStore((state) => state.open) - const errorMessage = useMetaportStore((state) => state.errorMessage); + const errorMessage = useMetaportStore((state) => state.errorMessage) if (!metaportTheme) return
@@ -89,55 +86,55 @@ export function WidgetUI(props: { config: MetaportConfig }) { palette: { mode: metaportTheme.mode as PaletteMode, background: { - paper: metaportTheme.background + paper: metaportTheme.background, }, primary: { main: metaportTheme.primary, }, secondary: { - main: metaportTheme.background + main: metaportTheme.background, }, }, - }); + }) const handleClick = (_: React.MouseEvent) => { - setOpen(isOpen ? false : true); - }; + setOpen(isOpen ? false : true) + } - const themeCls = metaportTheme.mode === 'dark' ? styles.darkTheme : styles.lightTheme; - const commonThemeCls = metaportTheme.mode === 'dark' ? common.darkTheme : common.lightTheme; + const themeCls = metaportTheme.mode === 'dark' ? styles.darkTheme : styles.lightTheme + const commonThemeCls = metaportTheme.mode === 'dark' ? common.darkTheme : common.lightTheme - let fabTop: boolean = false; - let fabLeft: boolean = false; + let fabTop: boolean = false + let fabLeft: boolean = false if (metaportTheme) { - fabTop = metaportTheme.position.bottom === 'auto'; - fabLeft = metaportTheme.position.right === 'auto'; + fabTop = metaportTheme.position.bottom === 'auto' + fabLeft = metaportTheme.position.right === 'auto' } - const fabButton = (
-
-
- - {isOpen ? ( - - ) : () - } - + const fabButton = ( +
+
+
+ + {isOpen ? ( + + ) : ( + + )} + +
-
); + ) return ( @@ -146,9 +143,7 @@ export function WidgetUI(props: { config: MetaportConfig }) { className={cls(styles.imaWidgetBody, themeCls, commonThemeCls)} style={metaportTheme ? { ...metaportTheme.position, zIndex: metaportTheme.zIndex } : null} > -
- {fabTop ? fabButton : null} -
+
{fabTop ? fabButton : null}
@@ -156,19 +151,14 @@ export function WidgetUI(props: { config: MetaportConfig }) { - - {address ? :
} -
+ {address ? :
}
-
- {fabTop ? null : fabButton} -
+
{fabTop ? null : fabButton}
- ); + ) } - -export default WidgetUI; +export default WidgetUI diff --git a/src/components/WidgetUI/index.ts b/src/components/WidgetUI/index.ts index aedb06f..6a13aef 100644 --- a/src/components/WidgetUI/index.ts +++ b/src/components/WidgetUI/index.ts @@ -1 +1 @@ -export { default } from "./WidgetUI"; +export { default } from './WidgetUI' diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index 2332845..353f1bc 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -21,255 +21,235 @@ * @copyright SKALE Labs 2022-Present */ -import debug from 'debug'; +import debug from 'debug' -import { Chain } from '@wagmi/core'; -import { WalletClient } from 'viem'; -import { Contract, Provider } from 'ethers'; +import { Chain } from '@wagmi/core' +import { WalletClient } from 'viem' +import { Contract, Provider } from 'ethers' -import { MainnetChain, SChain } from '@skalenetwork/ima-js'; -import { TokenData, CustomAbiTokenType } from '../dataclasses'; -import MetaportCore, { createTokenData } from '../metaport'; -import { externalEvents } from '../events'; -import { toWei } from '../convertation'; -import { ActionState, LOADING_BUTTON_TEXT } from './actionState'; -import { isMainnet } from '../helper'; +import { MainnetChain, SChain } from '@skalenetwork/ima-js' +import { TokenData, CustomAbiTokenType } from '../dataclasses' +import MetaportCore, { createTokenData } from '../metaport' +import { externalEvents } from '../events' +import { toWei } from '../convertation' +import { ActionState, LOADING_BUTTON_TEXT } from './actionState' +import { isMainnet } from '../helper' -import { IMA_ABIS } from '../contracts'; -import { isMainnetChainId, getMainnetAbi } from '../network'; +import { IMA_ABIS } from '../contracts' +import { isMainnetChainId, getMainnetAbi } from '../network' -import { walletClientToSigner } from '../ethers'; +import { walletClientToSigner } from '../ethers' -debug.enable('*'); -const log = debug('metaport:actions'); +debug.enable('*') +const log = debug('metaport:actions') +export type ActionType = typeof Action -export type ActionType = typeof Action; +export class Action { + execute(): void { + return + } + preAction(): void { + return + } + + mpc: MetaportCore + + mainnet: MainnetChain + sChain1: SChain + sChain2: SChain + + chainName1: string + chainName2: string + address: string + amount: string + amountWei: bigint + tokenId: number + token: TokenData + + walletClient: WalletClient + + sourceToken: Contract + destToken: Contract + unwrappedToken: Contract | undefined + + originAddress: string + + activeStep: number + setActiveStep: React.Dispatch> + + setAmountErrorMessage: React.Dispatch> + setBtnText: (btnText: string) => void + + _switchNetwork: (chainId: number | bigint) => Chain | undefined + + constructor( + mpc: MetaportCore, + // mainnet: MainnetChain, + // sChain1: SChain, + // sChain2: SChain, + chainName1: string, + chainName2: string, + address: string, + amount: string, + tokenId: number, + token: TokenData, + setAmountErrorMessage: (amountErrorMessage: string) => void, + setBtnText: (btnText: string) => void, + switchNetwork: (chainId: number | bigint) => Chain | undefined, + walletClient: WalletClient, + ) { + this.mpc = mpc + // this.mainnet = mainnet; + // this.sChain1 = sChain1; + // this.sChain2 = sChain2; + this.chainName1 = chainName1 + this.chainName2 = chainName2 + this.address = address + this.amount = amount + if (amount) this.amountWei = toWei(amount, token.meta.decimals) + this.tokenId = Number(tokenId) + + this.token = createTokenData(token.keyname, chainName1, token.type, this.mpc.config) + //! todo: init token here!!!!!, do not pass !!! + + // todo: init token contracts! + + if (isMainnet(chainName1)) { + this.mainnet = this.mpc.mainnet() + } else { + this.sChain1 = this.mpc.schain(this.chainName1) + } + if (isMainnet(chainName2)) { + this.mainnet = this.mpc.mainnet() + } else { + this.sChain2 = this.mpc.schain(this.chainName2) + } + const provider1 = isMainnet(chainName1) ? this.mainnet.provider : this.sChain1.provider + const provider2 = isMainnet(chainName2) ? this.mainnet.provider : this.sChain2.provider -export class Action { - execute(): void { return; }; - preAction(): void { return; }; - - mpc: MetaportCore - - mainnet: MainnetChain - sChain1: SChain - sChain2: SChain - - chainName1: string - chainName2: string - address: string - amount: string - amountWei: bigint - tokenId: number - token: TokenData - - walletClient: WalletClient - - sourceToken: Contract - destToken: Contract - unwrappedToken: Contract | undefined - - originAddress: string - - activeStep: number - setActiveStep: React.Dispatch> - - setAmountErrorMessage: React.Dispatch> - setBtnText: (btnText: string) => void - - _switchNetwork: (chainId: number | bigint) => Chain | undefined - - constructor( - mpc: MetaportCore, - // mainnet: MainnetChain, - // sChain1: SChain, - // sChain2: SChain, - chainName1: string, - chainName2: string, - address: string, - amount: string, - tokenId: number, - token: TokenData, - setAmountErrorMessage: (amountErrorMessage: string) => void, - setBtnText: (btnText: string) => void, - switchNetwork: (chainId: number | bigint) => Chain | undefined, - walletClient: WalletClient - ) { - this.mpc = mpc; - // this.mainnet = mainnet; - // this.sChain1 = sChain1; - // this.sChain2 = sChain2; - this.chainName1 = chainName1; - this.chainName2 = chainName2; - this.address = address; - this.amount = amount; - if (amount) this.amountWei = toWei(amount, token.meta.decimals); - this.tokenId = Number(tokenId); - - this.token = createTokenData(token.keyname, chainName1, token.type, this.mpc.config); - //! todo: init token here!!!!!, do not pass !!! - - // todo: init token contracts! - - if (isMainnet(chainName1)) { - this.mainnet = this.mpc.mainnet() - } else { - this.sChain1 = this.mpc.schain(this.chainName1) - } - if (isMainnet(chainName2)) { - this.mainnet = this.mpc.mainnet() - } else { - this.sChain2 = this.mpc.schain(this.chainName2) - } - - const provider1 = isMainnet(chainName1) ? this.mainnet.provider : this.sChain1.provider; - const provider2 = isMainnet(chainName2) ? this.mainnet.provider : this.sChain2.provider; - - this.sourceToken = mpc.tokenContract( - chainName1, - token.keyname, - token.type, - provider1, - this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, - this.token.wrapper(this.chainName2) ? this.chainName2 : null - ); - - this.originAddress = this.mpc.originAddress( - chainName1, chainName2, token.keyname, token.type); - - - console.log('----') - console.log(this.chainName2) - console.log(token) - console.log(token.wrapper(this.chainName2)) - console.log('----') - - if (this.token.wrapper(this.chainName2)) { - this.unwrappedToken = mpc.tokenContract( - chainName1, - token.keyname, - token.type, - provider1 - ); - } - - // todo: use wrapper address! - const destWrapperAddress = this.mpc.config.connections[this.chainName2][this.token.type][this.token.keyname].chains[this.chainName1].wrapper; - if (this.token.isClone(this.chainName2) && destWrapperAddress) { - this.destToken = mpc.tokenContract( - chainName2, - token.keyname, - token.type, - provider2, - CustomAbiTokenType.erc20wrap, - this.chainName1 - ) - } else { - this.destToken = mpc.tokenContract( - chainName2, - token.keyname, - token.type, - provider2 - ) - } - - // this.switchMetamaskChain = switchMetamaskChain; - - // this.setActiveStep = setActiveStep; - // this.activeStep = activeStep; - - this.setAmountErrorMessage = setAmountErrorMessage; - this.setBtnText = setBtnText; - this._switchNetwork = switchNetwork; - this.walletClient = walletClient; - - // if (this.tokenData) this.wrap = !!this.token.unwrappedSymbol && !this.token.clone; + this.sourceToken = mpc.tokenContract( + chainName1, + token.keyname, + token.type, + provider1, + this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, + this.token.wrapper(this.chainName2) ? this.chainName2 : null, + ) + + this.originAddress = this.mpc.originAddress(chainName1, chainName2, token.keyname, token.type) + + console.log('----') + console.log(this.chainName2) + console.log(token) + console.log(token.wrapper(this.chainName2)) + console.log('----') + + if (this.token.wrapper(this.chainName2)) { + this.unwrappedToken = mpc.tokenContract(chainName1, token.keyname, token.type, provider1) } - // tokenContract( - // provider: Provider, - // source: boolean = true - // ): Contract { - // return this.mpc.tokenContract( - // source ? this.chainName1 : this.chainName2, - // this.token.keyname, - // this.token.type, - // provider - // ) - // } - - updateState( - currentState: ActionState, - transactionHash?: string, - timestamp?: string | number - ) { - log(`actionStateUpd: ${this.constructor.name} - ${currentState} - ${this.token.keyname} \ -- ${this.chainName1} -> ${this.chainName2}`); - externalEvents.actionStateUpdated( - this.constructor.name, - currentState, - { - chainName1: this.chainName1, - chainName2: this.chainName2, - address: this.address, - amount: this.amount, - amountWei: this.amountWei, - tokenId: this.tokenId - }, - transactionHash, - timestamp - ); - this.setBtnText(LOADING_BUTTON_TEXT[currentState]); + // todo: use wrapper address! + const destWrapperAddress = + this.mpc.config.connections[this.chainName2][this.token.type][this.token.keyname].chains[this.chainName1].wrapper + if (this.token.isClone(this.chainName2) && destWrapperAddress) { + this.destToken = mpc.tokenContract( + chainName2, + token.keyname, + token.type, + provider2, + CustomAbiTokenType.erc20wrap, + this.chainName1, + ) + } else { + this.destToken = mpc.tokenContract(chainName2, token.keyname, token.type, provider2) } - async getConnectedChain( - provider: Provider, - customAbiTokenType?: CustomAbiTokenType, - destChainName?: string - ): Promise { - let chain: MainnetChain | SChain; - this.updateState('switch'); - const currentChainId = this.walletClient.chain.id; - const { chainId } = await provider.getNetwork(); - log(`Current chainId: ${currentChainId}, required chainId: ${chainId} `); - if (currentChainId !== Number(chainId)) { - log(`Switching network to ${chainId}...`); - const chain = await this._switchNetwork(Number(chainId)); - if (!chain) { - throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `); - } - log(`Network switched to ${chainId}...`); - } - const signer = walletClientToSigner(this.walletClient) - if (isMainnetChainId(chainId, this.mpc.config.skaleNetwork)) { - chain = new MainnetChain(signer.provider, getMainnetAbi(this.mpc.config.skaleNetwork)); - } else { - chain = new SChain(signer.provider, IMA_ABIS.schain); - } - const token = this.mpc.tokenContract( - destChainName === this.chainName1 ? this.chainName2 : this.chainName1, - this.token.keyname, - this.token.type, - chain.provider, - customAbiTokenType, - destChainName - ); - chain.erc20.addToken(this.token.keyname, token); - return chain; + // this.switchMetamaskChain = switchMetamaskChain; + + // this.setActiveStep = setActiveStep; + // this.activeStep = activeStep; + + this.setAmountErrorMessage = setAmountErrorMessage + this.setBtnText = setBtnText + this._switchNetwork = switchNetwork + this.walletClient = walletClient + + // if (this.tokenData) this.wrap = !!this.token.unwrappedSymbol && !this.token.clone; + } + + // tokenContract( + // provider: Provider, + // source: boolean = true + // ): Contract { + // return this.mpc.tokenContract( + // source ? this.chainName1 : this.chainName2, + // this.token.keyname, + // this.token.type, + // provider + // ) + // } + + updateState(currentState: ActionState, transactionHash?: string, timestamp?: string | number) { + log(`actionStateUpd: ${this.constructor.name} - ${currentState} - ${this.token.keyname} \ +- ${this.chainName1} -> ${this.chainName2}`) + externalEvents.actionStateUpdated( + this.constructor.name, + currentState, + { + chainName1: this.chainName1, + chainName2: this.chainName2, + address: this.address, + amount: this.amount, + amountWei: this.amountWei, + tokenId: this.tokenId, + }, + transactionHash, + timestamp, + ) + this.setBtnText(LOADING_BUTTON_TEXT[currentState]) + } + + async getConnectedChain( + provider: Provider, + customAbiTokenType?: CustomAbiTokenType, + destChainName?: string, + ): Promise { + let chain: MainnetChain | SChain + this.updateState('switch') + const currentChainId = this.walletClient.chain.id + const { chainId } = await provider.getNetwork() + log(`Current chainId: ${currentChainId}, required chainId: ${chainId} `) + if (currentChainId !== Number(chainId)) { + log(`Switching network to ${chainId}...`) + const chain = await this._switchNetwork(Number(chainId)) + if (!chain) { + throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `) + } + log(`Network switched to ${chainId}...`) + } + const signer = walletClientToSigner(this.walletClient) + if (isMainnetChainId(chainId, this.mpc.config.skaleNetwork)) { + chain = new MainnetChain(signer.provider, getMainnetAbi(this.mpc.config.skaleNetwork)) + } else { + chain = new SChain(signer.provider, IMA_ABIS.schain) } + const token = this.mpc.tokenContract( + destChainName === this.chainName1 ? this.chainName2 : this.chainName1, + this.token.keyname, + this.token.type, + chain.provider, + customAbiTokenType, + destChainName, + ) + chain.erc20.addToken(this.token.keyname, token) + return chain + } } - export abstract class TransferAction extends Action { - transferComplete(tx): void { - externalEvents.transferComplete( - tx, - this.chainName1, - this.chainName2, - this.token.keyname, - false - ); - } + transferComplete(tx): void { + externalEvents.transferComplete(tx, this.chainName1, this.chainName2, this.token.keyname, false) + } } diff --git a/src/core/actions/actionState.ts b/src/core/actions/actionState.ts index 09f98c5..415e888 100644 --- a/src/core/actions/actionState.ts +++ b/src/core/actions/actionState.ts @@ -22,46 +22,46 @@ */ export type ActionState = - | 'init' - | 'approve' - | 'approveDone' - | 'transfer' - | 'transferDone' - | 'received' - | 'transferETH' - | 'transferETHDone' - | 'receivedETH' - | 'approveWrap' - | 'approveWrapDone' - | 'wrap' - | 'wrapDone' - | 'unwrap' - | 'unwrapDone' - | 'switch' - | 'unlock' - | 'unlockDone'; + | 'init' + | 'approve' + | 'approveDone' + | 'transfer' + | 'transferDone' + | 'received' + | 'transferETH' + | 'transferETHDone' + | 'receivedETH' + | 'approveWrap' + | 'approveWrapDone' + | 'wrap' + | 'wrapDone' + | 'unwrap' + | 'unwrapDone' + | 'switch' + | 'unlock' + | 'unlockDone' type LoadingButtonTextMap = { - [key in ActionState]: string; -}; + [key in ActionState]: string +} export const LOADING_BUTTON_TEXT: LoadingButtonTextMap = { - init: 'Initializing', - approve: 'Approving transfer', - approveDone: 'Transfer approved', - transfer: 'Transferring tokens', - transferDone: 'Waiting for tokens to be received', - received: 'Tokens received', - transferETH: 'Transferring ETH', - transferETHDone: 'Waiting for ETH to be received', - receivedETH: 'ETH received', - approveWrap: 'Approving wrap', - approveWrapDone: 'Wrap approved', - wrap: 'Wrapping tokens', - wrapDone: 'Tokens wrapped', - unwrap: 'Unwrapping tokens', - unwrapDone: 'Tokens unwrapped', - switch: 'Waiting for network switch', - unlock: 'Unlocking ETH', - unlockDone: 'ETH unlocked' -}; + init: 'Initializing', + approve: 'Approving transfer', + approveDone: 'Transfer approved', + transfer: 'Transferring tokens', + transferDone: 'Waiting for tokens to be received', + received: 'Tokens received', + transferETH: 'Transferring ETH', + transferETHDone: 'Waiting for ETH to be received', + receivedETH: 'ETH received', + approveWrap: 'Approving wrap', + approveWrapDone: 'Wrap approved', + wrap: 'Wrapping tokens', + wrapDone: 'Tokens wrapped', + unwrap: 'Unwrapping tokens', + unwrapDone: 'Tokens unwrapped', + switch: 'Waiting for network switch', + unlock: 'Unlocking ETH', + unlockDone: 'ETH unlocked', +} diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index f971539..0e6a83c 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -21,185 +21,168 @@ * @copyright SKALE Labs 2022-Present */ +import debug from 'debug' +import { Contract } from 'ethers' +import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import debug from 'debug'; -import { Contract } from "ethers"; -import { MainnetChain, SChain } from '@skalenetwork/ima-js'; - -import { fromWei } from '../convertation'; -import { TokenData } from '../dataclasses/TokenData'; -import * as interfaces from '../interfaces'; -import { addressesEqual } from '../helper'; -import { DEFAULT_ERC20_DECIMALS, SFUEL_RESERVE_AMOUNT } from '../constants'; - - -debug.enable('*'); -const log = debug('metaport:actions:checks'); +import { fromWei } from '../convertation' +import { TokenData } from '../dataclasses/TokenData' +import * as interfaces from '../interfaces' +import { addressesEqual } from '../helper' +import { DEFAULT_ERC20_DECIMALS, SFUEL_RESERVE_AMOUNT } from '../constants' +debug.enable('*') +const log = debug('metaport:actions:checks') export async function checkEthBalance( // TODO: optimize balance checks - chain: MainnetChain | SChain, - address: string, - amount: string, - tokenData: TokenData, + chain: MainnetChain | SChain, + address: string, + amount: string, + tokenData: TokenData, ): Promise { - const checkRes: interfaces.CheckRes = { res: false }; - - try { - const balance = await chain.ethBalance(address); - log(`address: ${address}, eth balance: ${balance}, amount: ${amount}`); - const balanceEther = fromWei(balance, tokenData.meta.decimals); - if (Number(amount) + SFUEL_RESERVE_AMOUNT > Number(balanceEther)) { - checkRes.msg = `Current balance: ${balanceEther} ${tokenData.meta.symbol}. \ - ${SFUEL_RESERVE_AMOUNT} ETH will be reserved to cover transfer costs.`; - } else { - checkRes.res = true; - } - return checkRes; - } catch (err) { - log(err); - checkRes.msg = 'Something went wrong, check developer console'; - return checkRes; + const checkRes: interfaces.CheckRes = { res: false } + + try { + const balance = await chain.ethBalance(address) + log(`address: ${address}, eth balance: ${balance}, amount: ${amount}`) + const balanceEther = fromWei(balance, tokenData.meta.decimals) + if (Number(amount) + SFUEL_RESERVE_AMOUNT > Number(balanceEther)) { + checkRes.msg = `Current balance: ${balanceEther} ${tokenData.meta.symbol}. \ + ${SFUEL_RESERVE_AMOUNT} ETH will be reserved to cover transfer costs.` + } else { + checkRes.res = true } + return checkRes + } catch (err) { + log(err) + checkRes.msg = 'Something went wrong, check developer console' + return checkRes + } } - export async function checkERC20Balance( - address: string, - amount: string, - tokenData: TokenData, - tokenContract: Contract + address: string, + amount: string, + tokenData: TokenData, + tokenContract: Contract, ): Promise { - const checkRes: interfaces.CheckRes = { res: false }; - if (!amount || Number(amount) === 0) return checkRes; - try { - const balance = await tokenContract.balanceOf(address); - log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`); - const balanceEther = fromWei(balance, tokenData.meta.decimals); - if (Number(amount) > Number(balanceEther)) { - checkRes.msg = `Insufficient balance: ${balanceEther} ${tokenData.meta.symbol}`; - } else { - checkRes.res = true; - } - return checkRes; - } catch (err) { - log(err); - checkRes.msg = 'Something went wrong, check developer console'; - return checkRes; + const checkRes: interfaces.CheckRes = { res: false } + if (!amount || Number(amount) === 0) return checkRes + try { + const balance = await tokenContract.balanceOf(address) + log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`) + const balanceEther = fromWei(balance, tokenData.meta.decimals) + if (Number(amount) > Number(balanceEther)) { + checkRes.msg = `Insufficient balance: ${balanceEther} ${tokenData.meta.symbol}` + } else { + checkRes.res = true } + return checkRes + } catch (err) { + log(err) + checkRes.msg = 'Something went wrong, check developer console' + return checkRes + } } -export async function checkSFuelBalance( - address: string, - amount: string, - sChain: SChain -): Promise { - const checkRes: interfaces.CheckRes = { res: false }; - if (!amount || Number(amount) === 0) return checkRes; - try { - const balance = await sChain.provider.getBalance(address); - log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`); - const balanceEther = fromWei(balance, DEFAULT_ERC20_DECIMALS); - if (Number(amount) + SFUEL_RESERVE_AMOUNT > Number(balanceEther)) { - checkRes.msg = `Current balance: ${balanceEther}. \ - ${SFUEL_RESERVE_AMOUNT} sFUEL will be reserved for transfers.`; - } else { - checkRes.res = true; - } - return checkRes; - } catch (err) { - log(err); - checkRes.msg = 'Something went wrong, check developer console'; - return checkRes; +export async function checkSFuelBalance(address: string, amount: string, sChain: SChain): Promise { + const checkRes: interfaces.CheckRes = { res: false } + if (!amount || Number(amount) === 0) return checkRes + try { + const balance = await sChain.provider.getBalance(address) + log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`) + const balanceEther = fromWei(balance, DEFAULT_ERC20_DECIMALS) + if (Number(amount) + SFUEL_RESERVE_AMOUNT > Number(balanceEther)) { + checkRes.msg = `Current balance: ${balanceEther}. \ + ${SFUEL_RESERVE_AMOUNT} sFUEL will be reserved for transfers.` + } else { + checkRes.res = true } + return checkRes + } catch (err) { + log(err) + checkRes.msg = 'Something went wrong, check developer console' + return checkRes + } } - export async function checkERC20Allowance( - address: string, - approvalAddress: string, - amount: string, - tokenData: TokenData, - tokenContract: Contract + address: string, + approvalAddress: string, + amount: string, + tokenData: TokenData, + tokenContract: Contract, ): Promise { - const checkRes: interfaces.CheckRes = { res: false }; - if (!amount || Number(amount) === 0) return checkRes; - try { - const allowance = await tokenContract.allowance( - address, - approvalAddress - ) - const allowanceEther = fromWei(allowance, tokenData.meta.decimals); - log(`allowanceEther: ${allowanceEther}, amount: ${amount}`); - checkRes.res = Number(allowanceEther) >= Number(amount); - return checkRes; - } catch (err) { - log(err); - checkRes.msg = 'Something went wrong, check developer console'; - return checkRes; - } + const checkRes: interfaces.CheckRes = { res: false } + if (!amount || Number(amount) === 0) return checkRes + try { + const allowance = await tokenContract.allowance(address, approvalAddress) + const allowanceEther = fromWei(allowance, tokenData.meta.decimals) + log(`allowanceEther: ${allowanceEther}, amount: ${amount}`) + checkRes.res = Number(allowanceEther) >= Number(amount) + return checkRes + } catch (err) { + log(err) + checkRes.msg = 'Something went wrong, check developer console' + return checkRes + } } - export async function checkERC721( - address: string, - approvalAddress: string, - tokenId: number, - tokenContract: Contract + address: string, + approvalAddress: string, + tokenId: number, + tokenContract: Contract, ): Promise { - let approvedAddress: string; - const checkRes: interfaces.CheckRes = { res: true, approved: false }; - if (!tokenId) return checkRes; - try { - approvedAddress = await tokenContract.getApproved(tokenId) - log(`approvedAddress: ${approvedAddress}, address: ${address}`); - } catch (err) { - log(err); - checkRes.msg = 'tokenId does not exist, try again' - return checkRes; - } - try { - const currentOwner = await tokenContract.ownerOf(tokenId); - log(`currentOwner: ${currentOwner}, address: ${address}`); - if (!addressesEqual(currentOwner, address)) { - checkRes.msg = 'This account is not an owner of this tokenId'; - return checkRes; - } - } catch (err) { - log(err); - checkRes.msg = 'Something went wrong, check developer console'; - return checkRes; + let approvedAddress: string + const checkRes: interfaces.CheckRes = { res: true, approved: false } + if (!tokenId) return checkRes + try { + approvedAddress = await tokenContract.getApproved(tokenId) + log(`approvedAddress: ${approvedAddress}, address: ${address}`) + } catch (err) { + log(err) + checkRes.msg = 'tokenId does not exist, try again' + return checkRes + } + try { + const currentOwner = await tokenContract.ownerOf(tokenId) + log(`currentOwner: ${currentOwner}, address: ${address}`) + if (!addressesEqual(currentOwner, address)) { + checkRes.msg = 'This account is not an owner of this tokenId' + return checkRes } - checkRes.approved = addressesEqual(approvedAddress, approvalAddress); - return checkRes; + } catch (err) { + log(err) + checkRes.msg = 'Something went wrong, check developer console' + return checkRes + } + checkRes.approved = addressesEqual(approvedAddress, approvalAddress) + return checkRes } - export async function checkERC1155( - address: string, - approvalAddress: string, - tokenId: number, - amount: string, - tokenData: TokenData, - tokenContract: Contract + address: string, + approvalAddress: string, + tokenId: number, + amount: string, + tokenData: TokenData, + tokenContract: Contract, ): Promise { - const checkRes: interfaces.CheckRes = { res: true, approved: false }; - if (!tokenId || !amount) return checkRes; - - try { - const balance = await tokenContract.balanceOf(address, tokenId) - log(`address: ${address}, balanceEther: ${balance}, amount: ${amount}`); - if (Number(amount) > Number(balance)) { - checkRes.msg = `Current balance: ${balance} ${tokenData.meta.symbol}`; - } - checkRes.approved = await tokenContract.isApprovedForAll( - address, - approvalAddress - ) - } catch (err) { - log(err); - checkRes.msg = 'Something went wrong, check developer console'; - return checkRes; + const checkRes: interfaces.CheckRes = { res: true, approved: false } + if (!tokenId || !amount) return checkRes + + try { + const balance = await tokenContract.balanceOf(address, tokenId) + log(`address: ${address}, balanceEther: ${balance}, amount: ${amount}`) + if (Number(amount) > Number(balance)) { + checkRes.msg = `Current balance: ${balance} ${tokenData.meta.symbol}` } - return checkRes; -} \ No newline at end of file + checkRes.approved = await tokenContract.isApprovedForAll(address, approvalAddress) + } catch (err) { + log(err) + checkRes.msg = 'Something went wrong, check developer console' + return checkRes + } + return checkRes +} diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index 18b549e..fc19744 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -21,204 +21,157 @@ * @copyright SKALE Labs 2022-Present */ +import debug from 'debug' -import debug from 'debug'; +import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import { MainnetChain, SChain } from '@skalenetwork/ima-js'; +import { externalEvents } from '../events' +import { toWei } from '../convertation' +import { MAX_APPROVE_AMOUNT } from '../constants' -import { externalEvents } from '../events'; -import { toWei } from '../convertation'; -import { MAX_APPROVE_AMOUNT } from '../constants'; - -import { TransferAction, Action } from '../actions/action'; -import { checkERC20Balance, checkERC20Allowance, checkSFuelBalance } from './checks'; -import { CustomAbiTokenType } from '../dataclasses'; - - -debug.enable('*'); -const log = debug('metaport:actions:erc20'); +import { TransferAction, Action } from '../actions/action' +import { checkERC20Balance, checkERC20Allowance, checkSFuelBalance } from './checks' +import { CustomAbiTokenType } from '../dataclasses' +debug.enable('*') +const log = debug('metaport:actions:erc20') export class TransferERC20S2S extends TransferAction { - async execute() { - this.updateState('init'); - const checkResAllowance = await checkERC20Allowance( - this.address, - this.sChain1.erc20.address, - this.amount, - this.token, - this.sourceToken - ); - const sChain = await this.getConnectedChain( - this.sChain1.provider, - this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, - this.token.wrapper(this.chainName2) ? this.chainName2 : null - ) as SChain; - if (!checkResAllowance.res) { - this.updateState('approve'); - const approveTx = await sChain.erc20.approve( - this.token.keyname, - MAX_APPROVE_AMOUNT, - sChain.erc20.address, - { address: this.address } - ); - const txBlock = await sChain.provider.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.hash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approve'); - log('ApproveERC20S:execute - tx completed: %O', approveTx); - } + async execute() { + this.updateState('init') + const checkResAllowance = await checkERC20Allowance( + this.address, + this.sChain1.erc20.address, + this.amount, + this.token, + this.sourceToken, + ) + const sChain = (await this.getConnectedChain( + this.sChain1.provider, + this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, + this.token.wrapper(this.chainName2) ? this.chainName2 : null, + )) as SChain + if (!checkResAllowance.res) { + this.updateState('approve') + const approveTx = await sChain.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, sChain.erc20.address, { + address: this.address, + }) + const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) + this.updateState('approveDone', approveTx.hash, txBlock.timestamp) + externalEvents.transactionCompleted(approveTx, txBlock.timestamp, this.chainName1, 'approve') + log('ApproveERC20S:execute - tx completed: %O', approveTx) + } - // main transfer + // main transfer - this.updateState('transfer'); + this.updateState('transfer') - const amountWei = toWei(this.amount, this.token.meta.decimals); + const amountWei = toWei(this.amount, this.token.meta.decimals) - let balanceOnDestination; + let balanceOnDestination - const tokenConnection = this.token.connections[this.chainName2]; + const tokenConnection = this.token.connections[this.chainName2] - const isDestinationSFuel = tokenConnection.wrapsSFuel && tokenConnection.clone; // TODO! + const isDestinationSFuel = tokenConnection.wrapsSFuel && tokenConnection.clone // TODO! - if (isDestinationSFuel) { - balanceOnDestination = await this.sChain2.provider.getBalance(this.address); - } else { - balanceOnDestination = await this.sChain2.getERC20Balance( - this.destToken, - this.address - ); - } - const tx = await sChain.erc20.transferToSchain( - this.chainName2, - this.originAddress, - amountWei, - { address: this.address } - ); - const block = await sChain.provider.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.hash, block.timestamp); - if (isDestinationSFuel) { - await this.sChain2.waitETHBalanceChange( - this.address, - balanceOnDestination - ); - } else { - await this.sChain2.waitERC20BalanceChange( - this.destToken, - this.address, - balanceOnDestination - ); - } - this.updateState('received'); + if (isDestinationSFuel) { + balanceOnDestination = await this.sChain2.provider.getBalance(this.address) + } else { + balanceOnDestination = await this.sChain2.getERC20Balance(this.destToken, this.address) } - - async preAction() { - const checkResBalance = await checkERC20Balance( - this.address, - this.amount, - this.token, - this.sourceToken - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - this.setAmountErrorMessage(null); + const tx = await sChain.erc20.transferToSchain(this.chainName2, this.originAddress, amountWei, { + address: this.address, + }) + const block = await sChain.provider.getBlock(tx.blockNumber) + this.updateState('transferDone', tx.hash, block.timestamp) + if (isDestinationSFuel) { + await this.sChain2.waitETHBalanceChange(this.address, balanceOnDestination) + } else { + await this.sChain2.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination) } + this.updateState('received') + } + + async preAction() { + const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.sourceToken) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return + } + this.setAmountErrorMessage(null) + } } - export class WrapSFuelERC20S extends Action { - async execute() { - log('WrapSFuelERC20S:execute - starting'); - this.updateState('wrap'); - const tx = await this.sChain1.erc20.fundExit( - this.token.keyname, - { - address: this.address, - value: this.amountWei - } - ); - const block = await this.sChain1.provider.getBlock(tx.blockNumber); - this.updateState('wrapDone', tx.hash, block.timestamp); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'wrapsfuel'); - log('WrapSFuelERC20S:execute - tx completed %O', tx); - } - - async preAction() { - log('WrapSFuelERC20S:preAction - starting'); - const checkResBalance = await checkSFuelBalance( - this.address, - this.amount, - this.sChain1 - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - this.setAmountErrorMessage(null); + async execute() { + log('WrapSFuelERC20S:execute - starting') + this.updateState('wrap') + const tx = await this.sChain1.erc20.fundExit(this.token.keyname, { + address: this.address, + value: this.amountWei, + }) + const block = await this.sChain1.provider.getBlock(tx.blockNumber) + this.updateState('wrapDone', tx.hash, block.timestamp) + externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'wrapsfuel') + log('WrapSFuelERC20S:execute - tx completed %O', tx) + } + + async preAction() { + log('WrapSFuelERC20S:preAction - starting') + const checkResBalance = await checkSFuelBalance(this.address, this.amount, this.sChain1) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return } + this.setAmountErrorMessage(null) + } } - export class WrapERC20S extends Action { - async execute() { - this.updateState('init'); - const checkResAllowance = await checkERC20Allowance( - this.address, - this.token.connections[this.chainName2].wrapper, - this.amount, - this.token, - this.unwrappedToken - ); - const sChain = await this.getConnectedChain(this.sChain1.provider) as SChain; - const wrapperToken = this.mpc.tokenContract( - this.chainName1, - this.token.keyname, - this.token.type, - sChain.provider, - CustomAbiTokenType.erc20wrap, - this.chainName2 - ); - sChain.erc20.addToken(`wrap_${this.token.keyname}`, wrapperToken); - if (!checkResAllowance.res) { - this.updateState('approveWrap'); - const approveTx = await sChain.erc20.approve( - this.token.keyname, - MAX_APPROVE_AMOUNT, - this.token.address, - { address: this.address } - ); - const txBlock = await this.sChain1.provider.getBlock(approveTx.blockNumber); - this.updateState('approveWrapDone', approveTx.hash, txBlock.timestamp); - } - this.updateState('wrap'); - const amountWei = toWei(this.amount, this.token.meta.decimals); - const tx = await sChain.erc20.wrap( - `wrap_${this.token.keyname}`, - amountWei, - { address: this.address } - ); - const block = await this.sChain1.provider.getBlock(tx.blockNumber); - this.updateState('wrapDone', tx.hash, block.timestamp); + async execute() { + this.updateState('init') + const checkResAllowance = await checkERC20Allowance( + this.address, + this.token.connections[this.chainName2].wrapper, + this.amount, + this.token, + this.unwrappedToken, + ) + const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain + const wrapperToken = this.mpc.tokenContract( + this.chainName1, + this.token.keyname, + this.token.type, + sChain.provider, + CustomAbiTokenType.erc20wrap, + this.chainName2, + ) + sChain.erc20.addToken(`wrap_${this.token.keyname}`, wrapperToken) + if (!checkResAllowance.res) { + this.updateState('approveWrap') + const approveTx = await sChain.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, this.token.address, { + address: this.address, + }) + const txBlock = await this.sChain1.provider.getBlock(approveTx.blockNumber) + this.updateState('approveWrapDone', approveTx.hash, txBlock.timestamp) } - - async preAction() { - const checkResBalance = await checkERC20Balance( - this.address, - this.amount, - this.token, - this.unwrappedToken - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - this.setAmountErrorMessage(null); + this.updateState('wrap') + const amountWei = toWei(this.amount, this.token.meta.decimals) + const tx = await sChain.erc20.wrap(`wrap_${this.token.keyname}`, amountWei, { address: this.address }) + const block = await this.sChain1.provider.getBlock(tx.blockNumber) + this.updateState('wrapDone', tx.hash, block.timestamp) + } + + async preAction() { + const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.unwrappedToken) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return } + this.setAmountErrorMessage(null) + } } - // export class UnWrapERC20S2S123 extends Action { // static label = 'Unwrap' // static buttonText = 'Unwrap' @@ -262,194 +215,137 @@ export class WrapERC20S extends Action { // } // } - export class UnWrapERC20S extends Action { - async execute() { - const sChain = await this.getConnectedChain( - this.sChain2.provider, - CustomAbiTokenType.erc20wrap, - this.chainName1 - ) as SChain; - // const token = this.mpc.tokenContract( - // this.chainName2, - // this.token.keyname, - // this.token.type, - // sChain.provider, - // CustomAbiTokenType.erc20wrap, - // this.chainName1 - // ); - // sChain.erc20.addToken(this.token.keyname, token); - this.updateState('unwrap'); - let tx; - if (this.token.connections[this.chainName2].wrapsSFuel) { - tx = await sChain.erc20.undoExit( - this.token.keyname, - { address: this.address } - ); - } else { - const amountWei = toWei(this.amount, this.token.meta.decimals); - tx = await sChain.erc20.unwrap( - this.token.keyname, - amountWei, - { address: this.address } - ); - } - log('UnWrapERC20S:execute - tx completed %O', tx); - const block = await sChain.provider.getBlock(tx.blockNumber); - this.updateState('unwrapDone', tx.hash, block.timestamp); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'unwrap'); - externalEvents.unwrapComplete(tx, this.chainName2, this.token.keyname); + async execute() { + const sChain = (await this.getConnectedChain( + this.sChain2.provider, + CustomAbiTokenType.erc20wrap, + this.chainName1, + )) as SChain + // const token = this.mpc.tokenContract( + // this.chainName2, + // this.token.keyname, + // this.token.type, + // sChain.provider, + // CustomAbiTokenType.erc20wrap, + // this.chainName1 + // ); + // sChain.erc20.addToken(this.token.keyname, token); + this.updateState('unwrap') + let tx + if (this.token.connections[this.chainName2].wrapsSFuel) { + tx = await sChain.erc20.undoExit(this.token.keyname, { address: this.address }) + } else { + const amountWei = toWei(this.amount, this.token.meta.decimals) + tx = await sChain.erc20.unwrap(this.token.keyname, amountWei, { address: this.address }) } - - async preAction() { - log('preAction: UnWrapERC20S'); - const tokenContract = this.sChain1.erc20.tokens[this.token.keyname]; - const checkResBalance = await checkERC20Balance( - this.address, - this.amount, - this.token, - tokenContract - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } + log('UnWrapERC20S:execute - tx completed %O', tx) + const block = await sChain.provider.getBlock(tx.blockNumber) + this.updateState('unwrapDone', tx.hash, block.timestamp) + externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'unwrap') + externalEvents.unwrapComplete(tx, this.chainName2, this.token.keyname) + } + + async preAction() { + log('preAction: UnWrapERC20S') + const tokenContract = this.sChain1.erc20.tokens[this.token.keyname] + const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, tokenContract) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return } + } } - export class TransferERC20M2S extends TransferAction { - async execute() { - this.updateState('init'); - - // check approve + approve - const checkResAllowance = await checkERC20Allowance( - this.address, - this.mainnet.erc20.address, - this.amount, - this.token, - this.sourceToken - ); - const mainnet = await this.getConnectedChain(this.mainnet.provider) as MainnetChain; - if (!checkResAllowance.res) { - this.updateState('approve'); - const approveTx = await mainnet.erc20.approve( - this.token.keyname, - MAX_APPROVE_AMOUNT, - { address: this.address } - ); - const txBlock = await mainnet.provider.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.hash, txBlock.timestamp); - } - this.updateState('transfer'); - const amountWei = toWei(this.amount, this.token.meta.decimals); - // const destTokenContract = this.sChain2.erc20.tokens[this.token.keyname]; - const balanceOnDestination = await this.sChain2.getERC20Balance( - this.destToken, - this.address - ); - const tx = await await mainnet.erc20.deposit( - this.chainName2, - this.token.keyname, - amountWei, - { address: this.address } - ); - const block = await mainnet.provider.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.hash, block.timestamp); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'deposit'); - log('TransferERC20M2S:execute - tx completed %O', tx); - await this.sChain2.waitERC20BalanceChange( - this.destToken, this.address, balanceOnDestination); - this.updateState('received'); - log('TransferERC20M2S:execute - tokens received to destination chain'); - externalEvents.transferComplete( - tx.hash, - this.chainName1, - this.chainName2, - this.token.keyname, - false - ); + async execute() { + this.updateState('init') + + // check approve + approve + const checkResAllowance = await checkERC20Allowance( + this.address, + this.mainnet.erc20.address, + this.amount, + this.token, + this.sourceToken, + ) + const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain + if (!checkResAllowance.res) { + this.updateState('approve') + const approveTx = await mainnet.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, { address: this.address }) + const txBlock = await mainnet.provider.getBlock(approveTx.blockNumber) + this.updateState('approveDone', approveTx.hash, txBlock.timestamp) } - - async preAction() { - const checkResBalance = await checkERC20Balance( - this.address, - this.amount, - this.token, - this.sourceToken - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - this.setAmountErrorMessage(null); + this.updateState('transfer') + const amountWei = toWei(this.amount, this.token.meta.decimals) + // const destTokenContract = this.sChain2.erc20.tokens[this.token.keyname]; + const balanceOnDestination = await this.sChain2.getERC20Balance(this.destToken, this.address) + const tx = await await mainnet.erc20.deposit(this.chainName2, this.token.keyname, amountWei, { + address: this.address, + }) + const block = await mainnet.provider.getBlock(tx.blockNumber) + this.updateState('transferDone', tx.hash, block.timestamp) + externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'deposit') + log('TransferERC20M2S:execute - tx completed %O', tx) + await this.sChain2.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination) + this.updateState('received') + log('TransferERC20M2S:execute - tokens received to destination chain') + externalEvents.transferComplete(tx.hash, this.chainName1, this.chainName2, this.token.keyname, false) + } + + async preAction() { + const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.sourceToken) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return } + this.setAmountErrorMessage(null) + } } - export class TransferERC20S2M extends TransferAction { - async execute() { - this.updateState('init'); - // check approve + approve - - const checkResAllowance = await checkERC20Allowance( - this.address, - this.sChain1.erc20.address, - this.amount, - this.token, - this.sourceToken - ); - const sChain = await this.getConnectedChain(this.sChain1.provider) as SChain; - if (!checkResAllowance.res) { - this.updateState('approve'); - const approveTx = await sChain.erc20.approve( - this.token.keyname, - MAX_APPROVE_AMOUNT, - sChain.erc20.address, - { address: this.address } - ); - const txBlock = await sChain.provider.getBlock(approveTx.blockNumber); - this.updateState('approveDone', approveTx.hash, txBlock.timestamp); - externalEvents.transactionCompleted( - approveTx, txBlock.timestamp, this.chainName1, 'approve'); - log('ApproveERC20S:execute - tx completed: %O', approveTx); - } - this.updateState('transfer'); - const amountWei = toWei(this.amount, this.token.meta.decimals); - const balanceOnDestination = await this.mainnet.getERC20Balance( - this.destToken, this.address); - const tx = await sChain.erc20.withdraw( - this.originAddress, - amountWei, - { address: this.address } - ); - const block = await sChain.provider.getBlock(tx.blockNumber); - this.updateState('transferDone', tx.hash, block.timestamp); - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'withdraw'); - log('TransferERC20S2M:execute - tx completed %O', tx); - this.mainnet.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination); - this.updateState('received'); - log('TransferERC20S2M:execute - tokens received to destination chain'); - externalEvents.transferComplete( - tx.hash, - this.chainName1, - this.chainName2, - this.token.keyname, - false - ); + async execute() { + this.updateState('init') + // check approve + approve + + const checkResAllowance = await checkERC20Allowance( + this.address, + this.sChain1.erc20.address, + this.amount, + this.token, + this.sourceToken, + ) + const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain + if (!checkResAllowance.res) { + this.updateState('approve') + const approveTx = await sChain.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, sChain.erc20.address, { + address: this.address, + }) + const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) + this.updateState('approveDone', approveTx.hash, txBlock.timestamp) + externalEvents.transactionCompleted(approveTx, txBlock.timestamp, this.chainName1, 'approve') + log('ApproveERC20S:execute - tx completed: %O', approveTx) } - - async preAction() { - const checkResBalance = await checkERC20Balance( - this.address, - this.amount, - this.token, - this.sourceToken - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - this.setAmountErrorMessage(null); + this.updateState('transfer') + const amountWei = toWei(this.amount, this.token.meta.decimals) + const balanceOnDestination = await this.mainnet.getERC20Balance(this.destToken, this.address) + const tx = await sChain.erc20.withdraw(this.originAddress, amountWei, { address: this.address }) + const block = await sChain.provider.getBlock(tx.blockNumber) + this.updateState('transferDone', tx.hash, block.timestamp) + externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'withdraw') + log('TransferERC20S2M:execute - tx completed %O', tx) + this.mainnet.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination) + this.updateState('received') + log('TransferERC20S2M:execute - tokens received to destination chain') + externalEvents.transferComplete(tx.hash, this.chainName1, this.chainName2, this.token.keyname, false) + } + + async preAction() { + const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.sourceToken) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return } + this.setAmountErrorMessage(null) + } } diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index 875cc23..853fe53 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -21,68 +21,55 @@ * @copyright SKALE Labs 2022-Present */ -import debug from 'debug'; - -import { - TransferERC20S2S, - WrapERC20S, - UnWrapERC20S, - TransferERC20M2S, - TransferERC20S2M -} from './erc20'; - -import { Action } from './action'; - -import { isMainnet } from '../helper'; -import { ActionType, TokenType } from '../dataclasses'; -import { - S2S_POSTFIX, - M2S_POSTFIX, - S2M_POSTFIX, -} from '../constants'; - - -debug.enable('*'); -const log = debug('metaport:actions'); - - -export function getActionName( - chainName1: string, - chainName2: string, - tokenType: TokenType -): string { - if (!chainName1 || !chainName2 || !tokenType) return; - log(`Getting action name: ${chainName1} ${chainName2} ${tokenType}`); - let postfix = S2S_POSTFIX; - if (isMainnet(chainName1)) { postfix = M2S_POSTFIX; }; - if (isMainnet(chainName2)) { postfix = S2M_POSTFIX; }; - const actionName = tokenType + '_' + postfix; - log('Action name: ' + actionName); - return actionName; +import debug from 'debug' + +import { TransferERC20S2S, WrapERC20S, UnWrapERC20S, TransferERC20M2S, TransferERC20S2M } from './erc20' + +import { Action } from './action' + +import { isMainnet } from '../helper' +import { ActionType, TokenType } from '../dataclasses' +import { S2S_POSTFIX, M2S_POSTFIX, S2M_POSTFIX } from '../constants' + +debug.enable('*') +const log = debug('metaport:actions') + +export function getActionName(chainName1: string, chainName2: string, tokenType: TokenType): string { + if (!chainName1 || !chainName2 || !tokenType) return + log(`Getting action name: ${chainName1} ${chainName2} ${tokenType}`) + let postfix = S2S_POSTFIX + if (isMainnet(chainName1)) { + postfix = M2S_POSTFIX + } + if (isMainnet(chainName2)) { + postfix = S2M_POSTFIX + } + const actionName = tokenType + '_' + postfix + log('Action name: ' + actionName) + return actionName } +export const ACTIONS: { [actionType in ActionType]: typeof Action } = { + // eth_m2s: [TransferEthM2S], + // eth_s2m: [TransferEthS2M, UnlockEthM], + // eth_s2s: [], -export const ACTIONS: { [actionType in ActionType]: typeof Action; } = { - // eth_m2s: [TransferEthM2S], - // eth_s2m: [TransferEthS2M, UnlockEthM], - // eth_s2s: [], + wrap: WrapERC20S, + unwrap: UnWrapERC20S, - wrap: WrapERC20S, - unwrap: UnWrapERC20S, + erc20_m2s: TransferERC20M2S, + erc20_s2m: TransferERC20S2M, + erc20_s2s: TransferERC20S2S, - erc20_m2s: TransferERC20M2S, - erc20_s2m: TransferERC20S2M, - erc20_s2s: TransferERC20S2S, + // erc721_m2s: [TransferERC721M2S], + // erc721_s2m: [TransferERC721S2M], + // erc721_s2s: [TransferERC721S2S], - // erc721_m2s: [TransferERC721M2S], - // erc721_s2m: [TransferERC721S2M], - // erc721_s2s: [TransferERC721S2S], + // erc721meta_m2s: [TransferERC721M2S], + // erc721meta_s2m: [TransferERC721S2M], + // erc721meta_s2s: [TransferERC721S2S], - // erc721meta_m2s: [TransferERC721M2S], - // erc721meta_s2m: [TransferERC721S2M], - // erc721meta_s2s: [TransferERC721S2S], - - // erc1155_m2s: [TransferERC1155M2S], - // erc1155_s2m: [TransferERC1155S2M], - // erc1155_s2s: [TransferERC1155S2S] -} \ No newline at end of file + // erc1155_m2s: [TransferERC1155M2S], + // erc1155_s2m: [TransferERC1155S2M], + // erc1155_s2s: [TransferERC1155S2S] +} diff --git a/src/core/chain_id.ts b/src/core/chain_id.ts index 7d3cf08..f86a271 100644 --- a/src/core/chain_id.ts +++ b/src/core/chain_id.ts @@ -21,28 +21,24 @@ * @copyright SKALE Labs 2023-Present */ -import { ethers } from 'ethers'; - +import { ethers } from 'ethers' export function remove0x(s: any) { - if (!s.startsWith('0x')) return s; - return s.slice(2); + if (!s.startsWith('0x')) return s + return s.slice(2) } - function calcChainId(chainName: string): number { - let h = ethers.solidityPackedKeccak256(['string'], [chainName]); - // let h = soliditySha3(sChainName); - h = remove0x(h).toLowerCase(); - while (h.length < 64) - h = "0" + h; - h = h.substr(0, 13); - h = h.replace(/^0+/, ''); - return ethers.getNumber("0x" + h); + let h = ethers.solidityPackedKeccak256(['string'], [chainName]) + // let h = soliditySha3(sChainName); + h = remove0x(h).toLowerCase() + while (h.length < 64) h = '0' + h + h = h.substr(0, 13) + h = h.replace(/^0+/, '') + return ethers.getNumber('0x' + h) } - export function getChainId(chainName: string): number { - // if (chainName === MAINNET_CHAIN_NAME) return CHAIN_IDS[network]; - return calcChainId(chainName); + // if (chainName === MAINNET_CHAIN_NAME) return CHAIN_IDS[network]; + return calcChainId(chainName) } diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index 958d5b2..eed6df1 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -21,7 +21,6 @@ * @copyright SKALE Labs 2023-Present */ - // import debug from 'debug'; // import { MainnetChain, SChain } from '@skalenetwork/ima-js'; @@ -34,11 +33,9 @@ // MINIMUM_RECHARGE_AMOUNT // } from './constants'; - // debug.enable('*'); // const log = debug('metaport:core:community_pool'); - // export function getEmptyCommunityPoolData(): CommunityPoolData { // return { // exitGasOk: null, @@ -50,7 +47,6 @@ // }; // } - // export async function getCommunityPoolData( // address: string, // chainName1: string, @@ -102,4 +98,4 @@ // } // log('communityPoolData:', communityPoolData); // return communityPoolData; -// } \ No newline at end of file +// } diff --git a/src/core/constants.ts b/src/core/constants.ts index 15aff4f..0c016ce 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -21,87 +21,86 @@ * @copyright SKALE Labs 2022-Present */ -export const MAINNET_CHAIN_NAME = 'mainnet'; +export const MAINNET_CHAIN_NAME = 'mainnet' -export const ETH_ERC20_ADDRESS = '0xD2Aaa00700000000000000000000000000000000'; -export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; +export const ETH_ERC20_ADDRESS = '0xD2Aaa00700000000000000000000000000000000' +export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' -export const M2S_POSTFIX = 'm2s'; -export const S2M_POSTFIX = 's2m'; -export const S2S_POSTFIX = 's2s'; -export const WRAP_ACTION = 'wrap'; -export const UNWRAP_ACTION = 'unwrap'; +export const M2S_POSTFIX = 'm2s' +export const S2M_POSTFIX = 's2m' +export const S2S_POSTFIX = 's2s' +export const WRAP_ACTION = 'wrap' +export const UNWRAP_ACTION = 'unwrap' // tslint:disable-next-line -export const MAX_APPROVE_AMOUNT = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; // (2^256 - 1 ) +export const MAX_APPROVE_AMOUNT = '115792089237316195423570985008687907853269984665640564039457584007913129639935' // (2^256 - 1 ) -export const DEFAULT_MIN_SFUEL_WEI = '21000000000000'; +export const DEFAULT_MIN_SFUEL_WEI = '21000000000000' -export const DEFAULT_ERC20_DECIMALS = '18'; -export const DEFAULT_ERROR_MSG = 'Ooops... Something went wrong...'; +export const DEFAULT_ERC20_DECIMALS = '18' +export const DEFAULT_ERROR_MSG = 'Ooops... Something went wrong...' -export const DEFAULT_MP_MARGIN = '20pt'; -export const DEFAULT_MP_Z_INDEX = 99000; +export const DEFAULT_MP_MARGIN = '20pt' +export const DEFAULT_MP_Z_INDEX = 99000 -export const HTTPS_PREFIX = 'https://'; +export const HTTPS_PREFIX = 'https://' export const MAINNET_EXPLORER_URLS: { [skaleNetwork: string]: string } = { - mainnet: 'https://etherscan.io', - staging: 'https://goerli.etherscan.io/', - legacy: 'https://goerli.etherscan.io/', - regression: 'https://goerli.etherscan.io/' -}; + mainnet: 'https://etherscan.io', + staging: 'https://goerli.etherscan.io/', + legacy: 'https://goerli.etherscan.io/', + regression: 'https://goerli.etherscan.io/', +} export const BASE_EXPLORER_URLS = { - mainnet: "explorer.mainnet.skalenodes.com", - staging: "explorer.staging-v3.skalenodes.com", - legacy: "explorer.staging-v3.skalenodes.com", - regression: "regression-explorer.skalenodes.com" -}; + mainnet: 'explorer.mainnet.skalenodes.com', + staging: 'explorer.staging-v3.skalenodes.com', + legacy: 'explorer.staging-v3.skalenodes.com', + regression: 'regression-explorer.skalenodes.com', +} // ETA constants -export const GAS_STATION_API_ENDPOINT = 'https://ethgasstation.info/api/ethgasAPI.json?'; +export const GAS_STATION_API_ENDPOINT = 'https://ethgasstation.info/api/ethgasAPI.json?' -export const IMA_M2S_WAIT = 5; -export const IMA_S2S_WAIT = 2; -export const IMA_HUB_WAIT = 5; +export const IMA_M2S_WAIT = 5 +export const IMA_S2S_WAIT = 2 +export const IMA_HUB_WAIT = 5 // ETF constants -export const COINGECKO_API_ENDPOINT = ''; +export const COINGECKO_API_ENDPOINT = '' -export const DEFAULT_FAUCET_URL = 'https://sfuel.skale.network/'; -export const SFUEL_CHEKCS_INTERVAL = 8; +export const DEFAULT_FAUCET_URL = 'https://sfuel.skale.network/' +export const SFUEL_CHEKCS_INTERVAL = 8 export const SFUEL_TEXT = { - 'sfuel': { - 'action': '', - 'warning': 'You may need sFUEL on the destination chain', - 'error': 'You need sFUEL to perform a transfer' - }, - 'gas': { - 'action': '', - 'warning': 'You may need ETH on the destination chain', - 'error': 'You need ETH to perform a transfer' - } -}; + sfuel: { + action: '', + warning: 'You may need sFUEL on the destination chain', + error: 'You need sFUEL to perform a transfer', + }, + gas: { + action: '', + warning: 'You may need ETH on the destination chain', + error: 'You need ETH to perform a transfer', + }, +} // faucet constants -export const ZERO_FUNCSIG = '0x00000000'; +export const ZERO_FUNCSIG = '0x00000000' -import faucetJson from '../metadata/faucet.json'; -export const FAUCET_DATA = faucetJson; +import faucetJson from '../metadata/faucet.json' +export const FAUCET_DATA = faucetJson // community pool -export const RECHARGE_MULTIPLIER = 1.2; -export const MINIMUM_RECHARGE_AMOUNT = 0.005; -export const COMMUNITY_POOL_WITHDRAW_GAS_LIMIT = '1500000'; -export const BALANCE_UPDATE_INTERVAL_SECONDS = 10; +export const RECHARGE_MULTIPLIER = 1.2 +export const MINIMUM_RECHARGE_AMOUNT = 0.005 +export const COMMUNITY_POOL_WITHDRAW_GAS_LIMIT = '1500000' +export const BALANCE_UPDATE_INTERVAL_SECONDS = 10 +export const SFUEL_RESERVE_AMOUNT = 0.02 -export const SFUEL_RESERVE_AMOUNT = 0.02; - -export const SUCCESS_EMOJIS = ['🎉', '👌', '✅', '🙌', '🎊']; \ No newline at end of file +export const SUCCESS_EMOJIS = ['🎉', '👌', '✅', '🙌', '🎊'] diff --git a/src/core/contracts.ts b/src/core/contracts.ts index f9e7b2c..3137e9e 100644 --- a/src/core/contracts.ts +++ b/src/core/contracts.ts @@ -21,41 +21,41 @@ * @copyright SKALE Labs 2023-Present */ -import { CustomAbiTokenType, TokenType } from './dataclasses'; +import { CustomAbiTokenType, TokenType } from './dataclasses' -import erc20Abi from '../metadata/erc20_abi.json'; -import erc721Abi from '../metadata/erc721_abi.json'; -import erc721MetaAbi from '../metadata/erc721meta_abi.json'; -import erc1155Abi from '../metadata/erc1155_abi.json'; -import erc20WrapperAbi from '../metadata/erc20_wrapper_abi.json'; -import sFuelWrapperAbi from '../metadata/sfuel_wrapper_abi.json'; +import erc20Abi from '../metadata/erc20_abi.json' +import erc721Abi from '../metadata/erc721_abi.json' +import erc721MetaAbi from '../metadata/erc721meta_abi.json' +import erc1155Abi from '../metadata/erc1155_abi.json' +import erc20WrapperAbi from '../metadata/erc20_wrapper_abi.json' +import sFuelWrapperAbi from '../metadata/sfuel_wrapper_abi.json' -import mainnetAddresses from '../metadata/addresses/mainnet.json'; -import stagingAddresses from '../metadata/addresses/staging.json'; -import legacyAddresses from '../metadata/addresses/legacy.json'; -import regressionAddresses from '../metadata/addresses/regression.json'; +import mainnetAddresses from '../metadata/addresses/mainnet.json' +import stagingAddresses from '../metadata/addresses/staging.json' +import legacyAddresses from '../metadata/addresses/legacy.json' +import regressionAddresses from '../metadata/addresses/regression.json' -import sChainAbi from '../metadata/schainAbi.json'; -import mainnetAbi from '../metadata/mainnetAbi.json'; +import sChainAbi from '../metadata/schainAbi.json' +import mainnetAbi from '../metadata/mainnetAbi.json' export const ERC_ABIS: { [tokenType in CustomAbiTokenType | TokenType]: { ['abi']: any } } = { - eth: null, - erc20: erc20Abi, - erc20wrap: erc20WrapperAbi, - sfuelwrap: sFuelWrapperAbi, - erc721: erc721Abi, - erc721meta: erc721MetaAbi, - erc1155: erc1155Abi + eth: null, + erc20: erc20Abi, + erc20wrap: erc20WrapperAbi, + sfuelwrap: sFuelWrapperAbi, + erc721: erc721Abi, + erc721meta: erc721MetaAbi, + erc1155: erc1155Abi, } export const IMA_ADDRESSES = { - mainnet: mainnetAddresses, - staging: stagingAddresses, - legacy: legacyAddresses, - regression: regressionAddresses + mainnet: mainnetAddresses, + staging: stagingAddresses, + legacy: legacyAddresses, + regression: regressionAddresses, } export const IMA_ABIS = { - mainnet: mainnetAbi, - schain: sChainAbi -} \ No newline at end of file + mainnet: mainnetAbi, + schain: sChainAbi, +} diff --git a/src/core/convertation.ts b/src/core/convertation.ts index 81ca100..bf0eea2 100644 --- a/src/core/convertation.ts +++ b/src/core/convertation.ts @@ -21,14 +21,12 @@ * @copyright SKALE Labs 2022-Present */ -import { formatUnits, parseUnits, BigNumberish } from 'ethers'; - +import { formatUnits, parseUnits, BigNumberish } from 'ethers' export function toWei(value: string, decimals: string): bigint { - return parseUnits(value, parseInt(decimals as string)); + return parseUnits(value, parseInt(decimals as string)) } - export function fromWei(value: BigNumberish, decimals: string): string { - return formatUnits(value, parseInt(decimals as string)); -} \ No newline at end of file + return formatUnits(value, parseInt(decimals as string)) +} diff --git a/src/core/dataclasses/ErrorMessage.ts b/src/core/dataclasses/ErrorMessage.ts index 3ecaff5..e916def 100644 --- a/src/core/dataclasses/ErrorMessage.ts +++ b/src/core/dataclasses/ErrorMessage.ts @@ -21,54 +21,47 @@ * @copyright SKALE Labs 2022-Present */ - - export class ErrorMessage { + icon: string + text: string + btnText?: string + fallback?: Function - icon: string - text: string - btnText?: string - fallback?: Function - - constructor(fallback?: Function) { - this.fallback = fallback - } + constructor(fallback?: Function) { + this.fallback = fallback + } } - export class NoTokenPairsMessage extends ErrorMessage { - constructor() { - super() - this.icon = 'link-off' - this.text = 'No token pairs for these chains' - } + constructor() { + super() + this.icon = 'link-off' + this.text = 'No token pairs for these chains' + } } - export class WrongNetworkMessage extends ErrorMessage { - constructor(fallback: Function) { - super(fallback) - this.icon = 'public-off' - this.text = 'Looks like you are connected to the wrong network' - this.btnText = 'Switch network' - } + constructor(fallback: Function) { + super(fallback) + this.icon = 'public-off' + this.text = 'Looks like you are connected to the wrong network' + this.btnText = 'Switch network' + } } - export class TransactionErrorMessage extends ErrorMessage { - constructor(text: string, fallback: Function) { - super(fallback) - this.icon = 'sentiment' - this.text = text - this.btnText = 'Try again' - } + constructor(text: string, fallback: Function) { + super(fallback) + this.icon = 'sentiment' + this.text = text + this.btnText = 'Try again' + } } - export class CustomErrorMessage extends ErrorMessage { - constructor(text: string) { - super(undefined) - this.icon = 'error' - this.text = text - } + constructor(text: string) { + super(undefined) + this.icon = 'error' + this.text = text + } } diff --git a/src/core/dataclasses/EthTokenData.ts b/src/core/dataclasses/EthTokenData.ts index 0b85bde..5047bfa 100644 --- a/src/core/dataclasses/EthTokenData.ts +++ b/src/core/dataclasses/EthTokenData.ts @@ -23,24 +23,23 @@ // import { ETH_ERC20_ADDRESS } from '../constants'; // import { TokenType } from './TokenType'; -import { TokenData } from './TokenData'; - +import { TokenData } from './TokenData' export default class EthTokenData extends TokenData { - // constructor(clone: boolean) { - // super( - // ETH_ERC20_ADDRESS, - // null, - // TokenType.eth, - // TokenType.eth, - // TokenType.eth, - // clone, - // null, - // null, - // TokenType.eth, - // null, - // null, - // null - // ); - // } -} \ No newline at end of file + // constructor(clone: boolean) { + // super( + // ETH_ERC20_ADDRESS, + // null, + // TokenType.eth, + // TokenType.eth, + // TokenType.eth, + // clone, + // null, + // null, + // TokenType.eth, + // null, + // null, + // null + // ); + // } +} diff --git a/src/core/dataclasses/Position.ts b/src/core/dataclasses/Position.ts index bcb5fc7..16abbf0 100644 --- a/src/core/dataclasses/Position.ts +++ b/src/core/dataclasses/Position.ts @@ -21,24 +21,22 @@ * @copyright SKALE Labs 2022-Present */ - -import { DEFAULT_MP_MARGIN } from '../constants'; - +import { DEFAULT_MP_MARGIN } from '../constants' export interface Position { - top: string; - right: string; - bottom: string; - left: string; + top: string + right: string + bottom: string + left: string } - -export interface PositionMap { [positionName: string]: Position; } - +export interface PositionMap { + [positionName: string]: Position +} export const Positions: PositionMap = { - topLeft: { top: DEFAULT_MP_MARGIN, left: DEFAULT_MP_MARGIN, right: 'auto', bottom: 'auto' }, - topRight: { top: DEFAULT_MP_MARGIN, left: 'auto', right: DEFAULT_MP_MARGIN, bottom: 'auto' }, - bottomRight: { top: 'auto', left: 'auto', right: DEFAULT_MP_MARGIN, bottom: DEFAULT_MP_MARGIN }, - bottomLeft: { top: 'auto', left: DEFAULT_MP_MARGIN, right: 'auto', bottom: DEFAULT_MP_MARGIN } + topLeft: { top: DEFAULT_MP_MARGIN, left: DEFAULT_MP_MARGIN, right: 'auto', bottom: 'auto' }, + topRight: { top: DEFAULT_MP_MARGIN, left: 'auto', right: DEFAULT_MP_MARGIN, bottom: 'auto' }, + bottomRight: { top: 'auto', left: 'auto', right: DEFAULT_MP_MARGIN, bottom: DEFAULT_MP_MARGIN }, + bottomLeft: { top: 'auto', left: DEFAULT_MP_MARGIN, right: 'auto', bottom: DEFAULT_MP_MARGIN }, } diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts index 641cb3a..f88e220 100644 --- a/src/core/dataclasses/StepMetadata.ts +++ b/src/core/dataclasses/StepMetadata.ts @@ -21,87 +21,86 @@ * @copyright SKALE Labs 2022-Present */ -import debug from 'debug'; +import debug from 'debug' -import { TokenType } from './TokenType'; -import { isMainnet } from '../helper'; -import { - S2S_POSTFIX, - M2S_POSTFIX, - S2M_POSTFIX, -} from '../constants'; - - -debug.enable('*'); -const log = debug('metaport:actions'); +import { TokenType } from './TokenType' +import { isMainnet } from '../helper' +import { S2S_POSTFIX, M2S_POSTFIX, S2M_POSTFIX } from '../constants' +debug.enable('*') +const log = debug('metaport:actions') export enum ActionType { - erc20_m2s = 'erc20_m2s', - erc20_s2m = 'erc20_s2m', - erc20_s2s = 'erc20_s2s', - wrap = 'wrap', - unwrap = 'unwrap' + erc20_m2s = 'erc20_m2s', + erc20_s2m = 'erc20_s2m', + erc20_s2s = 'erc20_s2s', + wrap = 'wrap', + unwrap = 'unwrap', } - -export function getActionType( - chainName1: string, - chainName2: string, - tokenType: TokenType -): ActionType { - if (!chainName1 || !chainName2 || !tokenType) return; - let postfix = S2S_POSTFIX; - if (isMainnet(chainName1)) { postfix = M2S_POSTFIX; }; - if (isMainnet(chainName2)) { postfix = S2M_POSTFIX; }; - const actionName = tokenType + '_' + postfix; - log('Action name: ' + actionName); - return actionName as ActionType; +export function getActionType(chainName1: string, chainName2: string, tokenType: TokenType): ActionType { + if (!chainName1 || !chainName2 || !tokenType) return + let postfix = S2S_POSTFIX + if (isMainnet(chainName1)) { + postfix = M2S_POSTFIX + } + if (isMainnet(chainName2)) { + postfix = S2M_POSTFIX + } + const actionName = tokenType + '_' + postfix + log('Action name: ' + actionName) + return actionName as ActionType } - export abstract class StepMetadata { - headline: string = ''; - text: string = ''; - btnText: string = ''; - btnLoadingText: string = ''; - - onSource: boolean = true; - - constructor(public type: ActionType, public from: string, public to: string) { } + headline: string = '' + text: string = '' + btnText: string = '' + btnLoadingText: string = '' + + onSource: boolean = true + + constructor( + public type: ActionType, + public from: string, + public to: string, + ) {} } - export class TransferStepMetadata extends StepMetadata { - headline: string = 'Transfer to'; - text: string = 'You may need to approve first.'; - btnText: string = 'Transfer'; - btnLoadingText: string = 'Transferring'; + headline: string = 'Transfer to' + text: string = 'You may need to approve first.' + btnText: string = 'Transfer' + btnLoadingText: string = 'Transferring' - onSource: boolean = false; + onSource: boolean = false } - export class WrapStepMetadata extends StepMetadata { - headline: string = 'Wrap on'; - text: string = 'Tokens should be wrapped before transferring. Approval may be required.'; - btnText: string = 'Wrap'; - btnLoadingText: string = 'Wrapping'; - - constructor(public from: string, public to: string) { - super(ActionType.wrap, from, to) - } + headline: string = 'Wrap on' + text: string = 'Tokens should be wrapped before transferring. Approval may be required.' + btnText: string = 'Wrap' + btnLoadingText: string = 'Wrapping' + + constructor( + public from: string, + public to: string, + ) { + super(ActionType.wrap, from, to) + } } - export class UnwrapStepMetadata extends StepMetadata { - headline: string = 'Unwrap on'; - text: string = 'Tokens should be unwrapped after transferring.'; - btnText: string = 'Unwrap'; - btnLoadingText: string = 'Unwrapping'; - onSource: boolean = false; - - constructor(public from: string, public to: string) { - super(ActionType.unwrap, from, to) - } -} \ No newline at end of file + headline: string = 'Unwrap on' + text: string = 'Tokens should be unwrapped after transferring.' + btnText: string = 'Unwrap' + btnLoadingText: string = 'Unwrapping' + onSource: boolean = false + + constructor( + public from: string, + public to: string, + ) { + super(ActionType.unwrap, from, to) + } +} diff --git a/src/core/dataclasses/TokenData.ts b/src/core/dataclasses/TokenData.ts index 4408b47..f5694d2 100644 --- a/src/core/dataclasses/TokenData.ts +++ b/src/core/dataclasses/TokenData.ts @@ -21,45 +21,44 @@ * @copyright SKALE Labs 2022-Present */ -import { DEFAULT_ERC20_DECIMALS } from '../constants'; -import { TokenMetadata, ConnectedChainMap } from '../interfaces'; -import { TokenType } from './TokenType'; - +import { DEFAULT_ERC20_DECIMALS } from '../constants' +import { TokenMetadata, ConnectedChainMap } from '../interfaces' +import { TokenType } from './TokenType' export class TokenData { - address: string; - keyname: string; - type: TokenType; - meta: TokenMetadata; - connections: ConnectedChainMap; - chain: string; + address: string + keyname: string + type: TokenType + meta: TokenMetadata + connections: ConnectedChainMap + chain: string - constructor( - address: string, - type: TokenType, - tokenKeyname: string, - metadata: TokenMetadata, - connections: ConnectedChainMap, - chain: string - ) { - this.address = address; - this.meta = metadata; - this.meta.decimals = this.meta.decimals ? this.meta.decimals : DEFAULT_ERC20_DECIMALS; - this.connections = connections; - this.type = type; - this.keyname = tokenKeyname; - this.chain = chain; - } + constructor( + address: string, + type: TokenType, + tokenKeyname: string, + metadata: TokenMetadata, + connections: ConnectedChainMap, + chain: string, + ) { + this.address = address + this.meta = metadata + this.meta.decimals = this.meta.decimals ? this.meta.decimals : DEFAULT_ERC20_DECIMALS + this.connections = connections + this.type = type + this.keyname = tokenKeyname + this.chain = chain + } - wrapper(destChain: string): string | undefined { - return this.connections[destChain].wrapper - } + wrapper(destChain: string): string | undefined { + return this.connections[destChain].wrapper + } - isClone(destChain: string): boolean | undefined { - return this.connections[destChain].clone - } + isClone(destChain: string): boolean | undefined { + return this.connections[destChain].clone + } - wrapsSFuel(destChain: string): boolean | undefined { - return this.connections[destChain].wrapsSFuel - } -} \ No newline at end of file + wrapsSFuel(destChain: string): boolean | undefined { + return this.connections[destChain].wrapsSFuel + } +} diff --git a/src/core/dataclasses/TokenType.ts b/src/core/dataclasses/TokenType.ts index 31acdb5..d41e9c0 100644 --- a/src/core/dataclasses/TokenType.ts +++ b/src/core/dataclasses/TokenType.ts @@ -21,17 +21,15 @@ * @copyright SKALE Labs 2022-Present */ - export enum TokenType { - eth = 'eth', - erc20 = 'erc20', - erc721 = 'erc721', - erc721meta = 'erc721meta', - erc1155 = 'erc1155' + eth = 'eth', + erc20 = 'erc20', + erc721 = 'erc721', + erc721meta = 'erc721meta', + erc1155 = 'erc1155', } - export enum CustomAbiTokenType { - erc20wrap = 'erc20wrap', - sfuelwrap = 'sfuelwrap' -} \ No newline at end of file + erc20wrap = 'erc20wrap', + sfuelwrap = 'sfuelwrap', +} diff --git a/src/core/dataclasses/TransferRequestStatus.ts b/src/core/dataclasses/TransferRequestStatus.ts index 39a4830..42c56a0 100644 --- a/src/core/dataclasses/TransferRequestStatus.ts +++ b/src/core/dataclasses/TransferRequestStatus.ts @@ -21,12 +21,11 @@ * @copyright SKALE Labs 2023-Present */ - export enum TransferRequestStatus { - NO_REQEST = 0, - RECEIVED = 1, - IN_PROGRESS = 2, - IN_PROGRESS_HUB = 3, - DONE = 4, - ERROR = 5 -} \ No newline at end of file + NO_REQEST = 0, + RECEIVED = 1, + IN_PROGRESS = 2, + IN_PROGRESS_HUB = 3, + DONE = 4, + ERROR = 5, +} diff --git a/src/core/dataclasses/View.ts b/src/core/dataclasses/View.ts index 6059ac8..b6e8d89 100644 --- a/src/core/dataclasses/View.ts +++ b/src/core/dataclasses/View.ts @@ -21,11 +21,10 @@ * @copyright SKALE Labs 2022-Present */ - export enum View { - SANDBOX = 'SANDBOX', - UNWRAP = 'UNWRAP', - TRANSFER_REQUEST_SUMMARY = 'TRANSFER_REQUEST_SUMMARY', - TRANSFER_REQUEST_STEPS = 'TRANSFER_REQUEST_STEPS', - ERROR = 'ERROR' -} \ No newline at end of file + SANDBOX = 'SANDBOX', + UNWRAP = 'UNWRAP', + TRANSFER_REQUEST_SUMMARY = 'TRANSFER_REQUEST_SUMMARY', + TRANSFER_REQUEST_STEPS = 'TRANSFER_REQUEST_STEPS', + ERROR = 'ERROR', +} diff --git a/src/core/dataclasses/index.ts b/src/core/dataclasses/index.ts index 16b8139..a077799 100644 --- a/src/core/dataclasses/index.ts +++ b/src/core/dataclasses/index.ts @@ -21,9 +21,9 @@ * @copyright SKALE Labs 2022-Present */ -export * from "./TokenType"; -export * from "./TokenData"; -export * from "./Position"; -export * from "./TransferRequestStatus"; -export * from "./StepMetadata"; -export * from "./ErrorMessage"; +export * from './TokenType' +export * from './TokenData' +export * from './Position' +export * from './TransferRequestStatus' +export * from './StepMetadata' +export * from './ErrorMessage' diff --git a/src/core/ethers.ts b/src/core/ethers.ts index 8460931..cfacbf1 100644 --- a/src/core/ethers.ts +++ b/src/core/ethers.ts @@ -6,42 +6,38 @@ import { type PublicClient, usePublicClient } from 'wagmi' import { FallbackProvider, JsonRpcProvider } from 'ethers' import { type HttpTransport } from 'viem' - export function walletClientToSigner(walletClient: WalletClient) { - const { account, transport } = walletClient - const provider = new BrowserProvider(transport as Eip1193Provider, "any") - const signer = new JsonRpcSigner(provider, account.address) - return signer + const { account, transport } = walletClient + const provider = new BrowserProvider(transport as Eip1193Provider, 'any') + const signer = new JsonRpcSigner(provider, account.address) + return signer } /** Hook to convert a viem Wallet Client to an ethers.js Signer. */ export function useEthersSigner({ chainId }: { chainId?: number } = {}) { - const { data: walletClient } = useWalletClient({ chainId }) - return React.useMemo( - () => (walletClient ? walletClientToSigner(walletClient) : undefined), - [walletClient], - ) + const { data: walletClient } = useWalletClient({ chainId }) + return React.useMemo(() => (walletClient ? walletClientToSigner(walletClient) : undefined), [walletClient]) } export function publicClientToProvider(publicClient: PublicClient) { - const { chain, transport } = publicClient - const network = { - chainId: chain.id, - name: chain.name, - ensAddress: chain.contracts?.ensRegistry?.address, - } - if (transport.type === 'fallback') { - const providers = (transport.transports as ReturnType[]).map( - ({ value }) => new JsonRpcProvider(value?.url, network), - ) - if (providers.length === 1) return providers[0] - return new FallbackProvider(providers) - } - return new JsonRpcProvider(transport.url, network) + const { chain, transport } = publicClient + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + } + if (transport.type === 'fallback') { + const providers = (transport.transports as ReturnType[]).map( + ({ value }) => new JsonRpcProvider(value?.url, network), + ) + if (providers.length === 1) return providers[0] + return new FallbackProvider(providers) + } + return new JsonRpcProvider(transport.url, network) } /** Hook to convert a viem Public Client to an ethers.js Provider. */ export function useEthersProvider({ chainId }: { chainId?: number } = {}) { - const publicClient = usePublicClient({ chainId }) - return React.useMemo(() => publicClientToProvider(publicClient), [publicClient]) -} \ No newline at end of file + const publicClient = usePublicClient({ chainId }) + return React.useMemo(() => publicClientToProvider(publicClient), [publicClient]) +} diff --git a/src/core/events.ts b/src/core/events.ts index eff409f..3f66ae1 100644 --- a/src/core/events.ts +++ b/src/core/events.ts @@ -21,181 +21,168 @@ * @copyright SKALE Labs 2022-Present */ -import debug from 'debug'; -import * as interfaces from './interfaces/index'; -import { ActionState } from './actions/actionState'; - - -debug.enable('*'); -const log = debug('metaport:core:events'); +import debug from 'debug' +import * as interfaces from './interfaces/index' +import { ActionState } from './actions/actionState' +debug.enable('*') +const log = debug('metaport:core:events') function dispatchEvent(name: string, data = {}) { - log(`dispatchEvent - sending: ${name}`); - window.dispatchEvent(new CustomEvent(name, { detail: data })); - log(`dispatchEvent - sent: ${name}`); + log(`dispatchEvent - sending: ${name}`) + window.dispatchEvent(new CustomEvent(name, { detail: data })) + log(`dispatchEvent - sent: ${name}`) } - export namespace externalEvents { - export function balance(tokenSymbol: string, schainName: string, _balance: string) { - dispatchEvent('metaport_balance', { - "tokenSymbol": tokenSymbol, - "schainName": schainName, - "balance": _balance - }); - } - - export function transferComplete( - tx: string, - chainName1: string, - chainName2: string, - tokenSymbol: string, - unwrap: boolean = false - ) { - dispatchEvent('metaport_transferComplete', { - 'tokenSymbol': tokenSymbol, - 'from': chainName1, - 'to': chainName2, - 'tx': tx, - 'unwrap': unwrap - }); - } - - export function transferRequestCompleted(transferRequest: interfaces.TransferParams) { - dispatchEvent('metaport_transferRequestCompleted', { 'transferRequest': transferRequest }); - } - - export function transactionCompleted( - txData: any, - timestamp: string | number, - chainName: string, - txName: string - ): void { - log('WARNING: Event metaport_transactionCompleted will be removed in the next version') - dispatchEvent( - 'metaport_transactionCompleted', - { - tx: { - gasUsed: txData.gasUsed, - transactionHash: txData.transactionHash - }, - timestamp, - chainName, - txName - } - ); - } - - export function actionStateUpdated( - actionName: string, - actionState: ActionState, - actionData: { - chainName1: string, - chainName2: string, - address: string, - amount: string, - amountWei: bigint, - tokenId: number - }, - transactionHash?: string, - timestamp?: string | number - ): void { - dispatchEvent( - 'metaport_actionStateUpdated', - { - actionState, - actionName, - actionData, - transactionHash, - timestamp - } - ); - } - - export function unwrapComplete( - tx: string, - chainName1: string, - tokenSymbol: string, - ) { - dispatchEvent('metaport_unwrapComplete', { - 'tokenSymbol': tokenSymbol, - 'chain': chainName1, - 'tx': tx - }); - } - - export function ethUnlocked(tx: string) { - dispatchEvent('metaport_ethUnlocked', { - 'tx': tx - }); - } - - export function connected() { - dispatchEvent('metaport_connected'); - } + export function balance(tokenSymbol: string, schainName: string, _balance: string) { + dispatchEvent('metaport_balance', { + tokenSymbol: tokenSymbol, + schainName: schainName, + balance: _balance, + }) + } + + export function transferComplete( + tx: string, + chainName1: string, + chainName2: string, + tokenSymbol: string, + unwrap: boolean = false, + ) { + dispatchEvent('metaport_transferComplete', { + tokenSymbol: tokenSymbol, + from: chainName1, + to: chainName2, + tx: tx, + unwrap: unwrap, + }) + } + + export function transferRequestCompleted(transferRequest: interfaces.TransferParams) { + dispatchEvent('metaport_transferRequestCompleted', { transferRequest: transferRequest }) + } + + export function transactionCompleted( + txData: any, + timestamp: string | number, + chainName: string, + txName: string, + ): void { + log('WARNING: Event metaport_transactionCompleted will be removed in the next version') + dispatchEvent('metaport_transactionCompleted', { + tx: { + gasUsed: txData.gasUsed, + transactionHash: txData.transactionHash, + }, + timestamp, + chainName, + txName, + }) + } + + export function actionStateUpdated( + actionName: string, + actionState: ActionState, + actionData: { + chainName1: string + chainName2: string + address: string + amount: string + amountWei: bigint + tokenId: number + }, + transactionHash?: string, + timestamp?: string | number, + ): void { + dispatchEvent('metaport_actionStateUpdated', { + actionState, + actionName, + actionData, + transactionHash, + timestamp, + }) + } + + export function unwrapComplete(tx: string, chainName1: string, tokenSymbol: string) { + dispatchEvent('metaport_unwrapComplete', { + tokenSymbol: tokenSymbol, + chain: chainName1, + tx: tx, + }) + } + + export function ethUnlocked(tx: string) { + dispatchEvent('metaport_ethUnlocked', { + tx: tx, + }) + } + + export function connected() { + dispatchEvent('metaport_connected') + } } export namespace internalEvents { - export function updateParams(params) { - dispatchEvent('_metaport_updateParams', { - 'tokens': params.tokens, - 'chains': params.chains - }); - } - - export function transfer(params: interfaces.TransferParams): void { - dispatchEvent('_metaport_transfer', { - 'params': params - }); - } - - export function wrap(params) { - dispatchEvent('_metaport_wrap', { - 'amount': params.amount, - 'chain': params.chain, - 'tokens': params.tokens - }); - } - - export function unwrap(params) { - dispatchEvent('_metaport_unwrap', { - 'amount': params.amount, - 'chain': params.chain, - 'tokens': params.tokens - }); - } - - export function swap(params) { - dispatchEvent('_metaport_swap', { - 'amount': params.amount, - 'chain': params.chain, - 'tokens': params.tokens // todo! - }); - } - - export function close() { - dispatchEvent('_metaport_close'); - } - - export function open() { - dispatchEvent('_metaport_open'); - } - - export function reset() { - dispatchEvent('_metaport_reset'); - } - - export function requestBalance(params) { - dispatchEvent('_metaport_requestBalance', { - 'schainName': params.schainName, - 'tokenSymbol': params.tokenSymbol - }); - } - - export function setTheme(theme) { - dispatchEvent('_metaport_setTheme', { - 'theme': theme - }); - } -} \ No newline at end of file + export function updateParams(params) { + dispatchEvent('_metaport_updateParams', { + tokens: params.tokens, + chains: params.chains, + }) + } + + export function transfer(params: interfaces.TransferParams): void { + dispatchEvent('_metaport_transfer', { + params: params, + }) + } + + export function wrap(params) { + dispatchEvent('_metaport_wrap', { + amount: params.amount, + chain: params.chain, + tokens: params.tokens, + }) + } + + export function unwrap(params) { + dispatchEvent('_metaport_unwrap', { + amount: params.amount, + chain: params.chain, + tokens: params.tokens, + }) + } + + export function swap(params) { + dispatchEvent('_metaport_swap', { + amount: params.amount, + chain: params.chain, + tokens: params.tokens, // todo! + }) + } + + export function close() { + dispatchEvent('_metaport_close') + } + + export function open() { + dispatchEvent('_metaport_open') + } + + export function reset() { + dispatchEvent('_metaport_reset') + } + + export function requestBalance(params) { + dispatchEvent('_metaport_requestBalance', { + schainName: params.schainName, + tokenSymbol: params.tokenSymbol, + }) + } + + export function setTheme(theme) { + dispatchEvent('_metaport_setTheme', { + theme: theme, + }) + } +} diff --git a/src/core/explorer.ts b/src/core/explorer.ts index 7e663ce..4b48e8b 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -21,29 +21,23 @@ * @copyright SKALE Labs 2023-Present */ -import { - HTTPS_PREFIX, - MAINNET_CHAIN_NAME, - MAINNET_EXPLORER_URLS, - BASE_EXPLORER_URLS -} from './constants'; -import { SkaleNetwork } from './interfaces'; - +import { HTTPS_PREFIX, MAINNET_CHAIN_NAME, MAINNET_EXPLORER_URLS, BASE_EXPLORER_URLS } from './constants' +import { SkaleNetwork } from './interfaces' function getMainnetExplorerUrl(skaleNetwork: string) { - return MAINNET_EXPLORER_URLS[skaleNetwork]; + return MAINNET_EXPLORER_URLS[skaleNetwork] } function getSChainExplorerUrl(skaleNetwork: string) { - return BASE_EXPLORER_URLS[skaleNetwork]; + return BASE_EXPLORER_URLS[skaleNetwork] } export function getExplorerUrl(skaleNetwork: SkaleNetwork, chainName: string): string { - if (chainName === MAINNET_CHAIN_NAME) return getMainnetExplorerUrl(skaleNetwork); - return HTTPS_PREFIX + chainName + '.' + getSChainExplorerUrl(skaleNetwork); + if (chainName === MAINNET_CHAIN_NAME) return getMainnetExplorerUrl(skaleNetwork) + return HTTPS_PREFIX + chainName + '.' + getSChainExplorerUrl(skaleNetwork) } export function getTxUrl(chainName: string, skaleNetwork: SkaleNetwork, txHash: string): string { - const explorerUrl = getExplorerUrl(skaleNetwork, chainName); - return `${explorerUrl}/tx/${txHash}`; + const explorerUrl = getExplorerUrl(skaleNetwork, chainName) + return `${explorerUrl}/tx/${txHash}` } diff --git a/src/core/faucet.ts b/src/core/faucet.ts index 738a246..6ad0d9e 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -21,12 +21,10 @@ * @copyright SKALE Labs 2023-Present */ - // TODO! // import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants'; - // function getAddress(chainName: string, skaleNetwork: string) { // if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS; // const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; @@ -50,4 +48,4 @@ // const functionSig = getFunc(chainName, skaleNetwork); // const functionParam = web3.eth.abi.encodeParameter('address', address); // return { to: faucetAddress, data: functionSig + functionParam.slice(2) }; -// } \ No newline at end of file +// } diff --git a/src/core/fee_calculator.ts b/src/core/fee_calculator.ts index c60c91d..c14cd63 100644 --- a/src/core/fee_calculator.ts +++ b/src/core/fee_calculator.ts @@ -21,33 +21,31 @@ * @copyright SKALE Labs 2022-Present */ -import { fromWei as _fromWei, toBN } from 'web3-utils'; -import debug from 'debug'; +import { fromWei as _fromWei, toBN } from 'web3-utils' +import debug from 'debug' -import { CoinGeckoClient } from 'coingecko-api-v3'; -import * as interfaces from './interfaces/index'; +import { CoinGeckoClient } from 'coingecko-api-v3' +import * as interfaces from './interfaces/index' -debug.enable('*'); -const log = debug('metaport:components:fee_calculator'); +debug.enable('*') +const log = debug('metaport:components:fee_calculator') -export async function getTransactionFee( - transferRequest: interfaces.TransferParams -): Promise { - // todo: get actual gas limit for transfer - // todo: get actual gas price - log(transferRequest); - const gasLimit = toBN('250000'); - const gasPrice = toBN('10000000000'); +export async function getTransactionFee(transferRequest: interfaces.TransferParams): Promise { + // todo: get actual gas limit for transfer + // todo: get actual gas price + log(transferRequest) + const gasLimit = toBN('250000') + const gasPrice = toBN('10000000000') - const amountWei = gasLimit.mul(gasPrice); - const amountEth = _fromWei(amountWei); + const amountWei = gasLimit.mul(gasPrice) + const amountEth = _fromWei(amountWei) - const client = new CoinGeckoClient({ - timeout: 10000, - autoRetry: true, - }); - const res = await client.simplePrice({ ids: 'ethereum', vs_currencies: 'usd' }); - const ethToUsdRate = res.ethereum.usd; - const amountUSD = Number(amountEth) * ethToUsdRate; - return amountUSD; + const client = new CoinGeckoClient({ + timeout: 10000, + autoRetry: true, + }) + const res = await client.simplePrice({ ids: 'ethereum', vs_currencies: 'usd' }) + const ethToUsdRate = res.ethereum.usd + const amountUSD = Number(amountEth) * ethToUsdRate + return amountUSD } diff --git a/src/core/gas_station.ts b/src/core/gas_station.ts index 3dcf233..d9242cd 100644 --- a/src/core/gas_station.ts +++ b/src/core/gas_station.ts @@ -21,12 +21,11 @@ * @copyright SKALE Labs 2022-Present */ -import { GAS_STATION_API_ENDPOINT } from './constants'; - +import { GAS_STATION_API_ENDPOINT } from './constants' export async function getAvgWaitTime() { - const response = await fetch(GAS_STATION_API_ENDPOINT); - if (!response.ok) return 0; - const data = await response.json(); - return data.avgWait; + const response = await fetch(GAS_STATION_API_ENDPOINT) + if (!response.ok) return 0 + const data = await response.json() + return data.avgWait } diff --git a/src/core/helper.ts b/src/core/helper.ts index 062769b..ca8499d 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -21,85 +21,79 @@ * @copyright SKALE Labs 2022-Present */ -import { getAddress } from 'ethers'; +import { getAddress } from 'ethers' -import { MAINNET_CHAIN_NAME } from './constants'; +import { MAINNET_CHAIN_NAME } from './constants' // import utils from 'web3-utils'; -import { TransferRequestStatus } from './dataclasses'; -import { SkaleNetwork } from './interfaces'; - -import mainnetMeta from '../meta/mainnet/chains.json'; -import stagingMeta from '../meta/staging/chains.json'; -import legacyMeta from '../meta/legacy/chains.json'; -import regressionMeta from '../meta/regression/chains.json'; +import { TransferRequestStatus } from './dataclasses' +import { SkaleNetwork } from './interfaces' +import mainnetMeta from '../meta/mainnet/chains.json' +import stagingMeta from '../meta/staging/chains.json' +import legacyMeta from '../meta/legacy/chains.json' +import regressionMeta from '../meta/regression/chains.json' export const CHAINS_META = { - 'mainnet': mainnetMeta, - 'staging': stagingMeta, - 'legacy': legacyMeta, - 'regression': regressionMeta + mainnet: mainnetMeta, + staging: stagingMeta, + legacy: legacyMeta, + regression: regressionMeta, } - export function cls(...args: any): string { - const filteredArgs = args.map((clsName: any) => { - if (typeof clsName === 'string') return clsName; - if (Array.isArray(clsName) && clsName.length === 2 && clsName[1]) return clsName[0]; - }); - return filteredArgs.join(' '); + const filteredArgs = args.map((clsName: any) => { + if (typeof clsName === 'string') return clsName + if (Array.isArray(clsName) && clsName.length === 2 && clsName[1]) return clsName[0] + }) + return filteredArgs.join(' ') } - export function eqArrays(arr1, arr2) { - return JSON.stringify(arr1) === JSON.stringify(arr2); + return JSON.stringify(arr1) === JSON.stringify(arr2) } - export function isMainnet(chainName: string): boolean { - return chainName === MAINNET_CHAIN_NAME; + return chainName === MAINNET_CHAIN_NAME } - export function addressesEqual(address1: string, address2: string): boolean { - return getAddress(address1) === getAddress(address2); + return getAddress(address1) === getAddress(address2) } - export default function isTransferRequestActive(transferRequestStatus: TransferRequestStatus) { - return transferRequestStatus === TransferRequestStatus.IN_PROGRESS || - transferRequestStatus === TransferRequestStatus.IN_PROGRESS_HUB; + return ( + transferRequestStatus === TransferRequestStatus.IN_PROGRESS || + transferRequestStatus === TransferRequestStatus.IN_PROGRESS_HUB + ) } export function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)) } - export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { - if (chainName === MAINNET_CHAIN_NAME) { - if (skaleNetwork != MAINNET_CHAIN_NAME) { - const network = skaleNetwork === 'staging' ? 'Goerli' : skaleNetwork; - return `Ethereum (${network})`; - } - return 'Ethereum'; + if (chainName === MAINNET_CHAIN_NAME) { + if (skaleNetwork != MAINNET_CHAIN_NAME) { + const network = skaleNetwork === 'staging' ? 'Goerli' : skaleNetwork + return `Ethereum (${network})` } - if (CHAINS_META[skaleNetwork] && CHAINS_META[skaleNetwork][chainName]) { - if (app && CHAINS_META[skaleNetwork][chainName].apps && - CHAINS_META[skaleNetwork][chainName].apps[app]) { - return CHAINS_META[skaleNetwork][chainName].apps[app].alias; - } - return CHAINS_META[skaleNetwork][chainName].alias; + return 'Ethereum' + } + if (CHAINS_META[skaleNetwork] && CHAINS_META[skaleNetwork][chainName]) { + if (app && CHAINS_META[skaleNetwork][chainName].apps && CHAINS_META[skaleNetwork][chainName].apps[app]) { + return CHAINS_META[skaleNetwork][chainName].apps[app].alias } - return chainName; + return CHAINS_META[skaleNetwork][chainName].alias + } + return chainName } export function getChainAppsMeta(chainName: string, skaleNetwork: SkaleNetwork) { - if (CHAINS_META[skaleNetwork][chainName] && CHAINS_META[skaleNetwork][chainName].apps) { - return CHAINS_META[skaleNetwork][chainName].apps; - } + if (CHAINS_META[skaleNetwork][chainName] && CHAINS_META[skaleNetwork][chainName].apps) { + return CHAINS_META[skaleNetwork][chainName].apps + } } export function getRandom(list: Array) { - return list[Math.floor((Math.random() * list.length))]; -} \ No newline at end of file + return list[Math.floor(Math.random() * list.length)] +} diff --git a/src/core/interfaces/ChainsMetadata.ts b/src/core/interfaces/ChainsMetadata.ts index d13d14f..f67b6dd 100644 --- a/src/core/interfaces/ChainsMetadata.ts +++ b/src/core/interfaces/ChainsMetadata.ts @@ -21,19 +21,19 @@ * @copyright SKALE Labs 2022-Present */ - export interface ChainMetadata { - alias?: string; - minSfuelWei?: string; - faucetUrl?: string; - apps?: { - [appName: string]: { - alias: string; - background: string; - url: string; - }; + alias?: string + minSfuelWei?: string + faucetUrl?: string + apps?: { + [appName: string]: { + alias: string + background: string + url: string } + } } - -export interface ChainsMetadataMap { [chainName: string]: ChainMetadata; } +export interface ChainsMetadataMap { + [chainName: string]: ChainMetadata +} diff --git a/src/core/interfaces/CheckRes.ts b/src/core/interfaces/CheckRes.ts index 1833b90..63f4304 100644 --- a/src/core/interfaces/CheckRes.ts +++ b/src/core/interfaces/CheckRes.ts @@ -21,9 +21,8 @@ * @copyright SKALE Labs 2022-Present */ - export interface CheckRes { - res: boolean; - approved?: boolean; - msg?: string; -} \ No newline at end of file + res: boolean + approved?: boolean + msg?: string +} diff --git a/src/core/interfaces/CommunityPoolData.ts b/src/core/interfaces/CommunityPoolData.ts index bed8636..cdd7b69 100644 --- a/src/core/interfaces/CommunityPoolData.ts +++ b/src/core/interfaces/CommunityPoolData.ts @@ -21,12 +21,11 @@ * @copyright SKALE Labs 2023-Present */ - export interface CommunityPoolData { - exitGasOk: boolean - isActive: boolean - balance: string - accountBalance: string - recommendedRechargeAmount: string - originalRecommendedRechargeAmount: string -} \ No newline at end of file + exitGasOk: boolean + isActive: boolean + balance: string + accountBalance: string + recommendedRechargeAmount: string + originalRecommendedRechargeAmount: string +} diff --git a/src/core/interfaces/Config.ts b/src/core/interfaces/Config.ts index fc784c4..49202fe 100644 --- a/src/core/interfaces/Config.ts +++ b/src/core/interfaces/Config.ts @@ -21,22 +21,22 @@ * @copyright SKALE Labs 2022-Present */ -import { TokenConnectionsMap, TokenMetadataMap, MetaportTheme } from '.'; +import { TokenConnectionsMap, TokenMetadataMap, MetaportTheme } from '.' -export type SkaleNetwork = 'mainnet' | 'staging' | 'legacy' | 'regression'; +export type SkaleNetwork = 'mainnet' | 'staging' | 'legacy' | 'regression' export interface MetaportConfig { - openOnLoad?: boolean; - openButton?: boolean; - autoLookup?: boolean; - debug?: boolean; + openOnLoad?: boolean + openButton?: boolean + autoLookup?: boolean + debug?: boolean - skaleNetwork: SkaleNetwork; - mainnetEndpoint?: string; - chains?: string[]; + skaleNetwork: SkaleNetwork + mainnetEndpoint?: string + chains?: string[] - tokens: TokenMetadataMap; - connections: TokenConnectionsMap; + tokens: TokenMetadataMap + connections: TokenConnectionsMap - theme?: MetaportTheme; + theme?: MetaportTheme } diff --git a/src/core/interfaces/RouteParams.ts b/src/core/interfaces/RouteParams.ts index 180be63..b7db5cf 100644 --- a/src/core/interfaces/RouteParams.ts +++ b/src/core/interfaces/RouteParams.ts @@ -21,11 +21,10 @@ * @copyright SKALE Labs 2022-Present */ -import { TokenType } from '../dataclasses/TokenType'; - +import { TokenType } from '../dataclasses/TokenType' export interface RouteParams { - hub: string; - tokenKeyname: string; - tokenType: TokenType; -} \ No newline at end of file + hub: string + tokenKeyname: string + tokenType: TokenType +} diff --git a/src/core/interfaces/Theme.ts b/src/core/interfaces/Theme.ts index 161dddd..af1b59c 100644 --- a/src/core/interfaces/Theme.ts +++ b/src/core/interfaces/Theme.ts @@ -21,16 +21,14 @@ * @copyright SKALE Labs 2022-Present */ -import { Position } from '../dataclasses/Position'; - - -export type PaletteMode = 'light' | 'dark'; +import { Position } from '../dataclasses/Position' +export type PaletteMode = 'light' | 'dark' export interface MetaportTheme { - mode: PaletteMode | string; - primary?: string; - background?: string; - position?: Position; - zIndex?: number; -} \ No newline at end of file + mode: PaletteMode | string + primary?: string + background?: string + position?: Position + zIndex?: number +} diff --git a/src/core/interfaces/TokenDataMap.ts b/src/core/interfaces/TokenDataMap.ts index 693b85a..2aba755 100644 --- a/src/core/interfaces/TokenDataMap.ts +++ b/src/core/interfaces/TokenDataMap.ts @@ -21,23 +21,29 @@ * @copyright SKALE Labs 2022-Present */ -import { TokenData } from '../../core/dataclasses/TokenData'; -import EthTokenData from '../../core/dataclasses/EthTokenData'; -import { TokenType } from '../../core/dataclasses/TokenType'; -import { Contract } from "ethers"; - - -export interface TokenDataMap { [tokenSymbol: string]: TokenData; } -export interface EthTokenDataMap { [tokenSymbol: string]: EthTokenData; } +import { TokenData } from '../../core/dataclasses/TokenData' +import EthTokenData from '../../core/dataclasses/EthTokenData' +import { TokenType } from '../../core/dataclasses/TokenType' +import { Contract } from 'ethers' +export interface TokenDataMap { + [tokenSymbol: string]: TokenData +} +export interface EthTokenDataMap { + [tokenSymbol: string]: EthTokenData +} export type TokenDataTypesMap = { - [TokenType.eth]: EthTokenDataMap - [TokenType.erc20]: TokenDataMap - [TokenType.erc721]: TokenDataMap - [TokenType.erc721meta]: TokenDataMap - [TokenType.erc1155]: TokenDataMap + [TokenType.eth]: EthTokenDataMap + [TokenType.erc20]: TokenDataMap + [TokenType.erc721]: TokenDataMap + [TokenType.erc721meta]: TokenDataMap + [TokenType.erc1155]: TokenDataMap } -export interface TokenContractsMap { [tokenKeyname: string]: Contract; }; -export interface TokenBalancesMap { [tokenKeyname: string]: bigint; }; \ No newline at end of file +export interface TokenContractsMap { + [tokenKeyname: string]: Contract +} +export interface TokenBalancesMap { + [tokenKeyname: string]: bigint +} diff --git a/src/core/interfaces/TokenMetadata.ts b/src/core/interfaces/TokenMetadata.ts index f9cdb74..241305f 100644 --- a/src/core/interfaces/TokenMetadata.ts +++ b/src/core/interfaces/TokenMetadata.ts @@ -22,10 +22,12 @@ */ export interface TokenMetadata { - symbol: string, - name?: string, - iconUrl?: string, - decimals?: string + symbol: string + name?: string + iconUrl?: string + decimals?: string } -export interface TokenMetadataMap { [tokenName: string]: TokenMetadata; } \ No newline at end of file +export interface TokenMetadataMap { + [tokenName: string]: TokenMetadata +} diff --git a/src/core/interfaces/Tokens.ts b/src/core/interfaces/Tokens.ts index 3695d2e..262870e 100644 --- a/src/core/interfaces/Tokens.ts +++ b/src/core/interfaces/Tokens.ts @@ -22,23 +22,31 @@ */ export interface EthToken { - chains: ConnectedChainMap + chains: ConnectedChainMap } export interface Token { - address?: string, - chains: ConnectedChainMap + address?: string + chains: ConnectedChainMap } export interface ConnectedChain { - hub?: string - wrapper?: string - wrapsSFuel?: boolean - clone?: boolean + hub?: string + wrapper?: string + wrapsSFuel?: boolean + clone?: boolean } -export interface ConnectedChainMap { [chainName: string]: ConnectedChain; } -export interface ChainTokensMap { [tokenSymbol: string]: Token; } +export interface ConnectedChainMap { + [chainName: string]: ConnectedChain +} +export interface ChainTokensMap { + [tokenSymbol: string]: Token +} // export interface TokenTypeMap { [tokenType: string]: EthToken | ChainTokensMap; } -export interface TokenTypeMap { [tokenType: string]: ChainTokensMap; } -export interface TokenConnectionsMap { [chainName: string]: TokenTypeMap; } \ No newline at end of file +export interface TokenTypeMap { + [tokenType: string]: ChainTokensMap +} +export interface TokenConnectionsMap { + [chainName: string]: TokenTypeMap +} diff --git a/src/core/interfaces/TransactionHistory.ts b/src/core/interfaces/TransactionHistory.ts index 4a30ac4..0b03e6a 100644 --- a/src/core/interfaces/TransactionHistory.ts +++ b/src/core/interfaces/TransactionHistory.ts @@ -1,4 +1,3 @@ - /** * @license * SKALE Metaport @@ -22,16 +21,14 @@ * @copyright SKALE Labs 2023-Present */ - interface TxData { - gasUsed: number; - transactionHash: string; + gasUsed: number + transactionHash: string } - export interface TransactionHistory { - tx: TxData; - timestamp: number; - chainName: string; - txName: string; -} \ No newline at end of file + tx: TxData + timestamp: number + chainName: string + txName: string +} diff --git a/src/core/interfaces/TransferParams.ts b/src/core/interfaces/TransferParams.ts index a2a2b24..5287232 100644 --- a/src/core/interfaces/TransferParams.ts +++ b/src/core/interfaces/TransferParams.ts @@ -21,19 +21,18 @@ * @copyright SKALE Labs 2022-Present */ -import { TokenType } from '../dataclasses/TokenType'; -import { RouteParams } from './RouteParams'; - +import { TokenType } from '../dataclasses/TokenType' +import { RouteParams } from './RouteParams' export interface TransferParams { - tokenKeyname: string; - tokenType: TokenType; - amount?: string; - tokenId?: number; - chains?: string[]; - lockValue?: boolean; - route?: RouteParams; - text?: string; - fromApp?: string; - toApp?: string; -} \ No newline at end of file + tokenKeyname: string + tokenType: TokenType + amount?: string + tokenId?: number + chains?: string[] + lockValue?: boolean + route?: RouteParams + text?: string + fromApp?: string + toApp?: string +} diff --git a/src/core/interfaces/index.ts b/src/core/interfaces/index.ts index 945ebdf..bb0ca49 100644 --- a/src/core/interfaces/index.ts +++ b/src/core/interfaces/index.ts @@ -21,13 +21,13 @@ * @copyright SKALE Labs 2022-Present */ -export * from "./Config"; -export * from "./ChainsMetadata"; -export * from "./Theme"; -export * from "./Tokens"; -export * from "./TokenDataMap"; -export * from "./TransferParams"; -export * from "./CheckRes"; -export * from "./TransactionHistory"; -export * from "./CommunityPoolData"; -export * from "./TokenMetadata"; +export * from './Config' +export * from './ChainsMetadata' +export * from './Theme' +export * from './Tokens' +export * from './TokenDataMap' +export * from './TransferParams' +export * from './CheckRes' +export * from './TransactionHistory' +export * from './CommunityPoolData' +export * from './TokenMetadata' diff --git a/src/core/metadata.ts b/src/core/metadata.ts index e150fda..ae784c6 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -21,56 +21,51 @@ * @copyright SKALE Labs 2023-Present */ -import { TokenData } from './dataclasses'; -import { SkaleNetwork } from './interfaces'; -import { MAINNET_CHAIN_NAME } from './constants'; +import { TokenData } from './dataclasses' +import { SkaleNetwork } from './interfaces' +import { MAINNET_CHAIN_NAME } from './constants' -import * as MAINNET_CHAIN_ICONS from '../meta/mainnet/icons'; -import * as STAGING_CHAIN_ICONS from '../meta/staging/icons'; -import * as LEGACY_CHAIN_ICONS from '../meta/legacy/icons'; -import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons'; - -import * as icons from '../icons'; +import * as MAINNET_CHAIN_ICONS from '../meta/mainnet/icons' +import * as STAGING_CHAIN_ICONS from '../meta/staging/icons' +import * as LEGACY_CHAIN_ICONS from '../meta/legacy/icons' +import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons' +import * as icons from '../icons' const CHAIN_ICONS = { - 'mainnet': MAINNET_CHAIN_ICONS, - 'staging': STAGING_CHAIN_ICONS, - 'legacy': LEGACY_CHAIN_ICONS, - 'regression': REGRESSION_CHAIN_ICONS + mainnet: MAINNET_CHAIN_ICONS, + staging: STAGING_CHAIN_ICONS, + legacy: LEGACY_CHAIN_ICONS, + regression: REGRESSION_CHAIN_ICONS, } - export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: string) { - if (!name) return; - let filename = name.toLowerCase() - if (app) filename += `-${app}`; - if (name === MAINNET_CHAIN_NAME) { - return CHAIN_ICONS[skaleNetwork]['mainnet']; - } - filename = filename.replace(/-([a-z])/g, (_, g) => g.toUpperCase()) - if (CHAIN_ICONS[skaleNetwork][filename]) { - return CHAIN_ICONS[skaleNetwork][filename]; - } + if (!name) return + let filename = name.toLowerCase() + if (app) filename += `-${app}` + if (name === MAINNET_CHAIN_NAME) { + return CHAIN_ICONS[skaleNetwork]['mainnet'] + } + filename = filename.replace(/-([a-z])/g, (_, g) => g.toUpperCase()) + if (CHAIN_ICONS[skaleNetwork][filename]) { + return CHAIN_ICONS[skaleNetwork][filename] + } } - export function tokenIcon(name: string) { - if (!name) return; - const key = name.toLowerCase() - if (icons[key]) { - return icons[key]; - } else { - return icons['eth']; - } + if (!name) return + const key = name.toLowerCase() + if (icons[key]) { + return icons[key] + } else { + return icons['eth'] + } } - export function tokenIconPath(token: TokenData) { - return token.meta.iconUrl ?? tokenIcon(token.meta.symbol); + return token.meta.iconUrl ?? tokenIcon(token.meta.symbol) } - export function getTokenName(token: TokenData): string { - return token.meta.name ?? token.meta.symbol; -} \ No newline at end of file + return token.meta.name ?? token.meta.symbol +} diff --git a/src/core/metaport.ts b/src/core/metaport.ts index 8da24ec..8eba3a8 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -21,217 +21,170 @@ * @copyright SKALE Labs 2023-Present */ -import { Provider, JsonRpcProvider, Contract } from "ethers"; +import { Provider, JsonRpcProvider, Contract } from 'ethers' -import { MetaportConfig, TokenDataTypesMap, Token, TokenContractsMap, TokenBalancesMap } from './interfaces'; -import { TokenType, TokenData, CustomAbiTokenType } from './dataclasses'; +import { MetaportConfig, TokenDataTypesMap, Token, TokenContractsMap, TokenBalancesMap } from './interfaces' +import { TokenType, TokenData, CustomAbiTokenType } from './dataclasses' -import { getEmptyTokenDataMap } from './tokens/helper'; -import { getChainEndpoint, initIMA, initMainnet, initSChain } from './network'; -import { ERC_ABIS } from './contracts'; +import { getEmptyTokenDataMap } from './tokens/helper' +import { getChainEndpoint, initIMA, initMainnet, initSChain } from './network' +import { ERC_ABIS } from './contracts' +import debug from 'debug' +import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import debug from 'debug'; -import { MainnetChain, SChain } from "@skalenetwork/ima-js"; - - - -const log = debug('ima:test:MainnetChain'); - +const log = debug('ima:test:MainnetChain') export const createTokenData = ( - tokenKeyname: string, - chainName: string, - tokenType: TokenType, - config: MetaportConfig + tokenKeyname: string, + chainName: string, + tokenType: TokenType, + config: MetaportConfig, ): TokenData => { - const configToken: Token = config.connections[chainName][tokenType][tokenKeyname]; - return new TokenData( - configToken.address, - tokenType, - tokenKeyname, - config.tokens[tokenKeyname], - configToken.chains, - chainName - ); + const configToken: Token = config.connections[chainName][tokenType][tokenKeyname] + return new TokenData( + configToken.address, + tokenType, + tokenKeyname, + config.tokens[tokenKeyname], + configToken.chains, + chainName, + ) } - export const addTokenData = ( - tokenKeyname: string, - chainName: string, - tokenType: TokenType, - config: MetaportConfig, - tokens: TokenDataTypesMap + tokenKeyname: string, + chainName: string, + tokenType: TokenType, + config: MetaportConfig, + tokens: TokenDataTypesMap, ) => { - tokens[tokenType][tokenKeyname] = createTokenData( - tokenKeyname, - chainName, - tokenType, - config - ) + tokens[tokenType][tokenKeyname] = createTokenData(tokenKeyname, chainName, tokenType, config) } export const createTokensMap = ( - chainName1: string, - chainName2: string | null | undefined, - config: MetaportConfig + chainName1: string, + chainName2: string | null | undefined, + config: MetaportConfig, ): TokenDataTypesMap => { - const tokens = getEmptyTokenDataMap(); - log(`updating tokens map for ${chainName1} -> ${chainName2}`); - if (chainName1) { - Object.values(TokenType).forEach(tokenType => { - if (config.connections[chainName1][tokenType]) { - Object.keys(config.connections[chainName1][tokenType]).forEach(tokenKeyname => { - const tokenInfo = config.connections[chainName1][tokenType][tokenKeyname]; - if (!chainName2 || (chainName2 && tokenInfo.chains.hasOwnProperty(chainName2))) { - addTokenData(tokenKeyname, chainName1, tokenType as TokenType, config, tokens); - } - }); - } - }); - } - return tokens; + const tokens = getEmptyTokenDataMap() + log(`updating tokens map for ${chainName1} -> ${chainName2}`) + if (chainName1) { + Object.values(TokenType).forEach((tokenType) => { + if (config.connections[chainName1][tokenType]) { + Object.keys(config.connections[chainName1][tokenType]).forEach((tokenKeyname) => { + const tokenInfo = config.connections[chainName1][tokenType][tokenKeyname] + if (!chainName2 || (chainName2 && tokenInfo.chains.hasOwnProperty(chainName2))) { + addTokenData(tokenKeyname, chainName1, tokenType as TokenType, config, tokens) + } + }) + } + }) + } + return tokens } - export default class MetaportCore { - - private _config: MetaportConfig - - constructor(config: MetaportConfig) { - this._config = config; - } - - get config(): MetaportConfig { - return this._config; + private _config: MetaportConfig + + constructor(config: MetaportConfig) { + this._config = config + } + + get config(): MetaportConfig { + return this._config + } + + /** + * Generates available tokens for a given chain or a pair of the chains. + * + * @param {string} from - Source chain name. + * @param {string | null} [to] - Destination chain name. + * + * @returns {TokenDataTypesMap} - Returns a map of token data types for the given chains. + * + * @example + * + * // To get tokens for 'a' -> 'b' + * const tokens = mpc.tokens('a', 'b'); + * + * // To get all tokens from 'a' + * const tokens = mpc.tokens('a'); + */ + tokens(from: string, to?: string | null): TokenDataTypesMap { + if (from === undefined || from === null || from === '') return getEmptyTokenDataMap() + return createTokensMap(from, to, this._config) + } + + async tokenBalance(tokenContract: Contract, address: string): Promise { + return await tokenContract.balanceOf(address) + } + + async tokenBalances(tokenContracts: TokenContractsMap, address: string): Promise { + const balances: TokenBalancesMap = {} + const tokenKeynames = Object.keys(tokenContracts) + for (const tokenKeyname of tokenKeynames) { + balances[tokenKeyname] = await tokenContracts[tokenKeyname].balanceOf(address) } + return balances + } - /** - * Generates available tokens for a given chain or a pair of the chains. - * - * @param {string} from - Source chain name. - * @param {string | null} [to] - Destination chain name. - * - * @returns {TokenDataTypesMap} - Returns a map of token data types for the given chains. - * - * @example - * - * // To get tokens for 'a' -> 'b' - * const tokens = mpc.tokens('a', 'b'); - * - * // To get all tokens from 'a' - * const tokens = mpc.tokens('a'); - */ - tokens( - from: string, - to?: string | null, - ): TokenDataTypesMap { - if (from === undefined || from === null || from === '') return getEmptyTokenDataMap(); - return createTokensMap(from, to, this._config); - } - - async tokenBalance( - tokenContract: Contract, - address: string - ): Promise { - return await tokenContract.balanceOf(address); - } - - async tokenBalances( - tokenContracts: TokenContractsMap, - address: string - ): Promise { - const balances: TokenBalancesMap = {}; - const tokenKeynames = Object.keys(tokenContracts); - for (const tokenKeyname of tokenKeynames) { - balances[tokenKeyname] = await tokenContracts[tokenKeyname].balanceOf(address); - } - return balances; - } - - tokenContracts( - tokens: TokenDataTypesMap, - tokenType: TokenType, - chainName: string, - provider: Provider - ): TokenContractsMap { - const contracts: TokenContractsMap = {}; - if (tokens[tokenType]) { - Object.keys(tokens[tokenType]).forEach(tokenKeyname => { - contracts[tokenKeyname] = this.tokenContract( - chainName, - tokenKeyname, - tokenType, - provider - ) - }); - } - return contracts; - } - - tokenContract( - chainName: string, - tokenKeyname: string, - tokenType: TokenType, - provider: Provider, - customAbiTokenType?: CustomAbiTokenType, - destChainName?: string - ): Contract { - const token = this._config.connections[chainName][tokenType][tokenKeyname]; - const abi = customAbiTokenType ? ERC_ABIS[customAbiTokenType].abi : ERC_ABIS[tokenType].abi; - const address = customAbiTokenType ? token.chains[destChainName].wrapper : token.address; - // TODO: add sFUEL address support! - return new Contract(address, abi, provider); + tokenContracts( + tokens: TokenDataTypesMap, + tokenType: TokenType, + chainName: string, + provider: Provider, + ): TokenContractsMap { + const contracts: TokenContractsMap = {} + if (tokens[tokenType]) { + Object.keys(tokens[tokenType]).forEach((tokenKeyname) => { + contracts[tokenKeyname] = this.tokenContract(chainName, tokenKeyname, tokenType, provider) + }) } + return contracts + } - originAddress( - chainName1: string, - chainName2: string, - tokenKeyname: string, - tokenType: TokenType - ) { - let token = this._config.connections[chainName1][tokenType][tokenKeyname]; - const isClone = token.chains[chainName2].clone; - if (isClone) { - token = this._config.connections[chainName2][tokenType][tokenKeyname]; - } - return token.chains[isClone ? chainName1 : chainName2].wrapper ?? token.address; + tokenContract( + chainName: string, + tokenKeyname: string, + tokenType: TokenType, + provider: Provider, + customAbiTokenType?: CustomAbiTokenType, + destChainName?: string, + ): Contract { + const token = this._config.connections[chainName][tokenType][tokenKeyname] + const abi = customAbiTokenType ? ERC_ABIS[customAbiTokenType].abi : ERC_ABIS[tokenType].abi + const address = customAbiTokenType ? token.chains[destChainName].wrapper : token.address + // TODO: add sFUEL address support! + return new Contract(address, abi, provider) + } + + originAddress(chainName1: string, chainName2: string, tokenKeyname: string, tokenType: TokenType) { + let token = this._config.connections[chainName1][tokenType][tokenKeyname] + const isClone = token.chains[chainName2].clone + if (isClone) { + token = this._config.connections[chainName2][tokenType][tokenKeyname] } + return token.chains[isClone ? chainName1 : chainName2].wrapper ?? token.address + } - endpoint( - chainName: string - ): string { - return getChainEndpoint( - this._config.mainnetEndpoint, - this._config.skaleNetwork, - chainName - ); - } + endpoint(chainName: string): string { + return getChainEndpoint(this._config.mainnetEndpoint, this._config.skaleNetwork, chainName) + } - ima(chainName: string): MainnetChain | SChain { - return initIMA( - this._config.mainnetEndpoint, - this._config.skaleNetwork, - chainName - ); - } + ima(chainName: string): MainnetChain | SChain { + return initIMA(this._config.mainnetEndpoint, this._config.skaleNetwork, chainName) + } - mainnet(): MainnetChain { - return initMainnet( - this._config.mainnetEndpoint, - this._config.skaleNetwork, - ) - } + mainnet(): MainnetChain { + return initMainnet(this._config.mainnetEndpoint, this._config.skaleNetwork) + } - schain(chainName: string): SChain { - return initSChain( - this._config.skaleNetwork, - chainName - ) - } + schain(chainName: string): SChain { + return initSChain(this._config.skaleNetwork, chainName) + } - provider(chainName: string): Provider { - return new JsonRpcProvider(this.endpoint(chainName)); - } + provider(chainName: string): Provider { + return new JsonRpcProvider(this.endpoint(chainName)) + } } diff --git a/src/core/network.ts b/src/core/network.ts index b9a9363..545df6c 100644 --- a/src/core/network.ts +++ b/src/core/network.ts @@ -21,87 +21,68 @@ * @copyright SKALE Labs 2023-Present */ -import { MainnetChain, SChain } from '@skalenetwork/ima-js'; -import { JsonRpcProvider } from "ethers"; - - -import proxyEndpoints from '../metadata/proxy.json'; -import { MAINNET_CHAIN_NAME } from './constants'; -import { IMA_ADDRESSES, IMA_ABIS } from './contracts'; -import { SkaleNetwork } from './interfaces'; +import { MainnetChain, SChain } from '@skalenetwork/ima-js' +import { JsonRpcProvider } from 'ethers' +import proxyEndpoints from '../metadata/proxy.json' +import { MAINNET_CHAIN_NAME } from './constants' +import { IMA_ADDRESSES, IMA_ABIS } from './contracts' +import { SkaleNetwork } from './interfaces' const PROTOCOL: { [protocol in 'http' | 'ws']: string } = { - 'http': 'https://', - 'ws': 'wss://' + http: 'https://', + ws: 'wss://', } export const CHAIN_IDS: { [network in SkaleNetwork]: number } = { - 'staging': 5, - 'legacy': 5, - 'regression': 5, - 'mainnet': 5 + staging: 5, + legacy: 5, + regression: 5, + mainnet: 5, } export function isMainnetChainId(chainId: number | BigInt, skaleNetwork: SkaleNetwork): boolean { - return Number(chainId) === CHAIN_IDS[skaleNetwork]; + return Number(chainId) === CHAIN_IDS[skaleNetwork] } -export function getChainEndpoint( - mainnetEndpoint: string, - network: SkaleNetwork, - chainName: string -): string { - if (chainName === MAINNET_CHAIN_NAME) return mainnetEndpoint; - return getSChainEndpoint(network, chainName); +export function getChainEndpoint(mainnetEndpoint: string, network: SkaleNetwork, chainName: string): string { + if (chainName === MAINNET_CHAIN_NAME) return mainnetEndpoint + return getSChainEndpoint(network, chainName) } -export function getSChainEndpoint( - network: SkaleNetwork, - sChainName: string, - protocol: 'http' | 'ws' = 'http' -): string { - return PROTOCOL[protocol] + getProxyEndpoint(network) + '/v1/' + (protocol === 'ws' ? 'ws/' : '') + sChainName; +export function getSChainEndpoint(network: SkaleNetwork, sChainName: string, protocol: 'http' | 'ws' = 'http'): string { + return PROTOCOL[protocol] + getProxyEndpoint(network) + '/v1/' + (protocol === 'ws' ? 'ws/' : '') + sChainName } function getProxyEndpoint(network: SkaleNetwork) { - return proxyEndpoints[network]; + return proxyEndpoints[network] } export function getMainnetAbi(network: string) { - if (network === 'staging') { - return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.staging } - } - if (network === 'legacy') { - return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.legacy } - } - if (network === 'regression') { - return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.regression } - } - return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.mainnet } + if (network === 'staging') { + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.staging } + } + if (network === 'legacy') { + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.legacy } + } + if (network === 'regression') { + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.regression } + } + return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.mainnet } } - -export function initIMA( - mainnetEndpoint: string, - network: SkaleNetwork, - chainName: string -): MainnetChain | SChain { - if (chainName === MAINNET_CHAIN_NAME) return initMainnet(mainnetEndpoint, network); - return initSChain(network, chainName); +export function initIMA(mainnetEndpoint: string, network: SkaleNetwork, chainName: string): MainnetChain | SChain { + if (chainName === MAINNET_CHAIN_NAME) return initMainnet(mainnetEndpoint, network) + return initSChain(network, chainName) } export function initMainnet(mainnetEndpoint: string, network: string): MainnetChain { - const provider = new JsonRpcProvider(mainnetEndpoint); - return new MainnetChain(provider, getMainnetAbi(network)); + const provider = new JsonRpcProvider(mainnetEndpoint) + return new MainnetChain(provider, getMainnetAbi(network)) } export function initSChain(network: SkaleNetwork, chainName: string): SChain { - const endpoint = getChainEndpoint( - null, - network, - chainName - ); - const provider = new JsonRpcProvider(endpoint); - return new SChain(provider, IMA_ABIS.schain); + const endpoint = getChainEndpoint(null, network, chainName) + const provider = new JsonRpcProvider(endpoint) + return new SChain(provider, IMA_ABIS.schain) } diff --git a/src/core/sfuel.ts b/src/core/sfuel.ts index c2c6f84..6b93762 100644 --- a/src/core/sfuel.ts +++ b/src/core/sfuel.ts @@ -1,4 +1,3 @@ - /** * @license * SKALE Metaport @@ -29,17 +28,14 @@ // import { getFuncData, isFaucetAvailable } from '../core/faucet'; // import { DEFAULT_MIN_SFUEL_WEI, DEFAULT_FAUCET_URL, MAINNET_CHAIN_NAME } from '../core/constants'; - // debug.enable('*'); // const log = debug('metaport:Widget'); - // function getFaucetUrl(chainsMetadata: object, chainName: string): string { // if (chainsMetadata && chainsMetadata[chainName]) return chainsMetadata[chainName].faucetUrl; // return DEFAULT_FAUCET_URL; // } - // function getMinSfuelWei(chainName: string, chainsMetadata?: object): string { // if (chainsMetadata && chainsMetadata[chainName] && chainsMetadata[chainName].minSfuelWei) { // return chainsMetadata[chainName].minSfuelWei; @@ -48,7 +44,6 @@ // } // } - // async function getSfuelBalance(web3: any, address: string): Promise { // //return await provider.getBalance(address); // // TODO! @@ -56,7 +51,6 @@ // return ''; // } - // export interface StationData { // faucetUrl: string; // minSfuelWei: string; @@ -64,13 +58,11 @@ // ok: boolean; // } - // export interface StationPowRes { // message: string; // ok: boolean; // } - // export class Station { // endpoint: string; diff --git a/src/core/themes.ts b/src/core/themes.ts index 729de92..02c6844 100644 --- a/src/core/themes.ts +++ b/src/core/themes.ts @@ -21,49 +21,44 @@ * @copyright SKALE Labs 2022-Present */ -import { Positions } from './dataclasses/Position'; -import { MetaportTheme } from './interfaces/Theme'; -import { DEFAULT_MP_Z_INDEX } from './constants'; - +import { Positions } from './dataclasses/Position' +import { MetaportTheme } from './interfaces/Theme' +import { DEFAULT_MP_Z_INDEX } from './constants' const defaultThemes = { - 'dark': { - primary: '#29FF94', - background: '#000000', - mode: 'dark', - position: Positions.bottomRight, - zIndex: DEFAULT_MP_Z_INDEX - }, - 'light': { - primary: '#173CFF', - background: '#EFEFEF', - mode: 'light', - position: Positions.bottomRight, - zIndex: DEFAULT_MP_Z_INDEX - } + dark: { + primary: '#29FF94', + background: '#000000', + mode: 'dark', + position: Positions.bottomRight, + zIndex: DEFAULT_MP_Z_INDEX, + }, + light: { + primary: '#173CFF', + background: '#EFEFEF', + mode: 'light', + position: Positions.bottomRight, + zIndex: DEFAULT_MP_Z_INDEX, + }, } // warning: order is important here -const MUI_ELEMENTS = ['mobileStepper', 'fab', 'speedDial', 'appBar', 'drawer', 'modal', - 'snackbar', 'tooltip']; - - -const INDEX_STEP = 50; +const MUI_ELEMENTS = ['mobileStepper', 'fab', 'speedDial', 'appBar', 'drawer', 'modal', 'snackbar', 'tooltip'] +const INDEX_STEP = 50 export function getWidgetTheme(theme: MetaportTheme | null): MetaportTheme { - if (!theme) return defaultThemes.dark as MetaportTheme; - if (theme.mode && Object.keys(theme).length === 1) { - return defaultThemes[theme.mode] as MetaportTheme;; - } - if (theme.position === undefined) theme.position = Positions.bottomRight; - if (theme.zIndex === undefined) theme.zIndex = DEFAULT_MP_Z_INDEX; - if (theme.background === undefined) theme.background = defaultThemes[theme.mode].background; - if (theme.primary === undefined) theme.primary = defaultThemes[theme.mode].primary; - return theme; + if (!theme) return defaultThemes.dark as MetaportTheme + if (theme.mode && Object.keys(theme).length === 1) { + return defaultThemes[theme.mode] as MetaportTheme + } + if (theme.position === undefined) theme.position = Positions.bottomRight + if (theme.zIndex === undefined) theme.zIndex = DEFAULT_MP_Z_INDEX + if (theme.background === undefined) theme.background = defaultThemes[theme.mode].background + if (theme.primary === undefined) theme.primary = defaultThemes[theme.mode].primary + return theme } - export function getMuiZIndex(theme: MetaportTheme): object { - return MUI_ELEMENTS.reduce((x, y, i) => (x[y] = theme.zIndex + ((i + 1) * INDEX_STEP), x), {}); -} \ No newline at end of file + return MUI_ELEMENTS.reduce((x, y, i) => ((x[y] = theme.zIndex + (i + 1) * INDEX_STEP), x), {}) +} diff --git a/src/core/tokens/helper.ts b/src/core/tokens/helper.ts index e10604a..7ee3652 100644 --- a/src/core/tokens/helper.ts +++ b/src/core/tokens/helper.ts @@ -21,41 +21,37 @@ * @copyright SKALE Labs 2022-Present */ -import { TokenData } from '../dataclasses/TokenData'; -import * as interfaces from '../interfaces/index'; - -import { eqArrays } from '../helper'; +import { TokenData } from '../dataclasses/TokenData' +import * as interfaces from '../interfaces/index' +import { eqArrays } from '../helper' export function getEmptyTokenDataMap(): interfaces.TokenDataTypesMap { - return { eth: {}, erc20: {}, erc721: {}, erc721meta: {}, erc1155: {} }; + return { eth: {}, erc20: {}, erc721: {}, erc721meta: {}, erc1155: {} } } - export function getAvailableTokenNumers(availableTokens): number[] { - return Object.entries(availableTokens).map(([_key, value]) => Object.entries(value).length); + return Object.entries(availableTokens).map(([_key, value]) => Object.entries(value).length) } - export function getAvailableTokensTotal(availableTokens): number { - return getAvailableTokenNumers(availableTokens).reduce((a, b) => a + b, 0); + return getAvailableTokenNumers(availableTokens).reduce((a, b) => a + b, 0) } - export function getDefaultToken(availableTokens: interfaces.TokenDataTypesMap): TokenData { - if (availableTokens === undefined) return; - const availableTokenNumers = getAvailableTokenNumers(availableTokens); - // if (eqArrays(availableTokenNumers, [1, 0, 0, 0, 0])) return availableTokens.eth.eth; - if (eqArrays(availableTokenNumers, [0, 1, 0, 0, 0])) { - return Object.values(availableTokens.erc20)[0]; - } - if (eqArrays(availableTokenNumers, [0, 0, 1, 0, 0])) { - return Object.values(availableTokens.erc721)[0]; - } - if (eqArrays(availableTokenNumers, [0, 0, 0, 1, 0])) { - return Object.values(availableTokens.erc721meta)[0]; - } - if (eqArrays(availableTokenNumers, [0, 0, 0, 0, 1])) { - return Object.values(availableTokens.erc1155)[0]; - } + if (availableTokens === undefined) return + const availableTokenNumers = getAvailableTokenNumers(availableTokens) + // if (eqArrays(availableTokenNumers, [1, 0, 0, 0, 0])) return availableTokens.eth.eth; + if (eqArrays(availableTokenNumers, [0, 1, 0, 0, 0])) { + return Object.values(availableTokens.erc20)[0] + } + if (eqArrays(availableTokenNumers, [0, 0, 1, 0, 0])) { + return Object.values(availableTokens.erc721)[0] + } + if (eqArrays(availableTokenNumers, [0, 0, 0, 1, 0])) { + return Object.values(availableTokens.erc721meta)[0] + } + if (eqArrays(availableTokenNumers, [0, 0, 0, 0, 1])) { + return Object.values(availableTokens.erc1155)[0] + } } diff --git a/src/core/transfer_steps.ts b/src/core/transfer_steps.ts index 3e35b60..757888d 100644 --- a/src/core/transfer_steps.ts +++ b/src/core/transfer_steps.ts @@ -21,72 +21,54 @@ * @copyright SKALE Labs 2023-Present */ - -import debug from 'debug'; +import debug from 'debug' import { - TokenData, - WrapStepMetadata, - UnwrapStepMetadata, - TransferStepMetadata, - StepMetadata, - getActionType -} from './dataclasses'; - -import { MetaportConfig } from './interfaces/index'; - -import { MAINNET_CHAIN_NAME } from './constants'; + TokenData, + WrapStepMetadata, + UnwrapStepMetadata, + TransferStepMetadata, + StepMetadata, + getActionType, +} from './dataclasses' +import { MetaportConfig } from './interfaces/index' -debug.enable('*'); -const log = debug('metaport:core:transferSteps'); +import { MAINNET_CHAIN_NAME } from './constants' +debug.enable('*') +const log = debug('metaport:core:transferSteps') -export function getStepsMetadata( - config: MetaportConfig, - token: TokenData, - to: string -): StepMetadata[] { - const steps: StepMetadata[] = []; - if (token === undefined || token === null || to === null || to === '') return steps; +export function getStepsMetadata(config: MetaportConfig, token: TokenData, to: string): StepMetadata[] { + const steps: StepMetadata[] = [] + if (token === undefined || token === null || to === null || to === '') return steps - const toChain = token.connections[to].hub ?? to; - const hubTokenOptions = config.connections[toChain][token.type][token.keyname].chains[token.chain]; - const destTokenOptions = config.connections[to][token.type][token.keyname].chains[token.chain]; - const isCloneToClone = token.isClone(to) && destTokenOptions.clone; + const toChain = token.connections[to].hub ?? to + const hubTokenOptions = config.connections[toChain][token.type][token.keyname].chains[token.chain] + const destTokenOptions = config.connections[to][token.type][token.keyname].chains[token.chain] + const isCloneToClone = token.isClone(to) && destTokenOptions.clone - log(`Setting toChain: ${toChain}`); + log(`Setting toChain: ${toChain}`) - if (token.connections[toChain].wrapper) { - steps.push(new WrapStepMetadata( - token.chain, - to - )) - } - steps.push(new TransferStepMetadata( - getActionType(token.chain, toChain, token.type), - token.chain, - toChain - )); - if (hubTokenOptions.wrapper && !isCloneToClone) { - steps.push(new UnwrapStepMetadata(token.chain, toChain)); - } - if (token.connections[to].hub) { - const tokenOptionsHub = config.connections[toChain][token.type][token.keyname].chains[to]; - if (tokenOptionsHub.wrapper && !isCloneToClone) { - steps.push(new WrapStepMetadata(toChain, to)); - } - steps.push(new TransferStepMetadata( - getActionType(toChain, to, token.type), - toChain, - to - )); - } - if (to === MAINNET_CHAIN_NAME && token.keyname === 'eth') { - // todo: add unlock step! + if (token.connections[toChain].wrapper) { + steps.push(new WrapStepMetadata(token.chain, to)) + } + steps.push(new TransferStepMetadata(getActionType(token.chain, toChain, token.type), token.chain, toChain)) + if (hubTokenOptions.wrapper && !isCloneToClone) { + steps.push(new UnwrapStepMetadata(token.chain, toChain)) + } + if (token.connections[to].hub) { + const tokenOptionsHub = config.connections[toChain][token.type][token.keyname].chains[to] + if (tokenOptionsHub.wrapper && !isCloneToClone) { + steps.push(new WrapStepMetadata(toChain, to)) } + steps.push(new TransferStepMetadata(getActionType(toChain, to, token.type), toChain, to)) + } + if (to === MAINNET_CHAIN_NAME && token.keyname === 'eth') { + // todo: add unlock step! + } - log(`Action steps metadata:`); - log(steps); - return steps; -} \ No newline at end of file + log(`Action steps metadata:`) + log(steps) + return steps +} diff --git a/src/core/views.ts b/src/core/views.ts index aa33581..73363ee 100644 --- a/src/core/views.ts +++ b/src/core/views.ts @@ -21,20 +21,16 @@ * @copyright SKALE Labs 2023-Present */ - -import { View } from './dataclasses/View'; - +import { View } from './dataclasses/View' export function isTransferRequestView(view: View) { - return view === View.TRANSFER_REQUEST_SUMMARY || view === View.TRANSFER_REQUEST_STEPS; + return view === View.TRANSFER_REQUEST_SUMMARY || view === View.TRANSFER_REQUEST_STEPS } - export function isTransferRequestSummary(view: View) { - return view === View.TRANSFER_REQUEST_SUMMARY + return view === View.TRANSFER_REQUEST_SUMMARY } - export function isStepsMetadata(view: View) { - return view === View.TRANSFER_REQUEST_STEPS + return view === View.TRANSFER_REQUEST_STEPS } diff --git a/src/core/wagmi_network.ts b/src/core/wagmi_network.ts index fef4d33..9bf72b9 100644 --- a/src/core/wagmi_network.ts +++ b/src/core/wagmi_network.ts @@ -23,43 +23,41 @@ import { Chain } from 'wagmi' -import { getSChainEndpoint } from './network'; -import { getExplorerUrl } from './explorer'; -import { getChainAlias } from './helper'; -import { getChainId } from './chain_id'; - -import { SkaleNetwork } from './interfaces'; +import { getSChainEndpoint } from './network' +import { getExplorerUrl } from './explorer' +import { getChainAlias } from './helper' +import { getChainId } from './chain_id' +import { SkaleNetwork } from './interfaces' export function constructWagmiChain(network: SkaleNetwork, chainName: string): Chain { - const endpointHttp = getSChainEndpoint(network, chainName); - const endpointWs = getSChainEndpoint(network, chainName, 'ws'); - const explorerUrl = getExplorerUrl(network, chainName); - const name = getChainAlias(network, chainName); - const chainId = getChainId(chainName); - return { - id: chainId, - name: name, - network: `skale-${chainName}`, - nativeCurrency: { - decimals: 18, - name: 'sFUEL', - symbol: 'sFUEL', - }, - rpcUrls: { - public: { http: [endpointHttp], webSocket: [endpointWs] }, - default: { http: [endpointHttp], webSocket: [endpointWs] }, - }, - blockExplorers: { - etherscan: { name: 'SKALE Explorer', url: explorerUrl }, - default: { name: 'SKALE Explorer', url: explorerUrl }, - }, - contracts: {} - } as const satisfies Chain + const endpointHttp = getSChainEndpoint(network, chainName) + const endpointWs = getSChainEndpoint(network, chainName, 'ws') + const explorerUrl = getExplorerUrl(network, chainName) + const name = getChainAlias(network, chainName) + const chainId = getChainId(chainName) + return { + id: chainId, + name: name, + network: `skale-${chainName}`, + nativeCurrency: { + decimals: 18, + name: 'sFUEL', + symbol: 'sFUEL', + }, + rpcUrls: { + public: { http: [endpointHttp], webSocket: [endpointWs] }, + default: { http: [endpointHttp], webSocket: [endpointWs] }, + }, + blockExplorers: { + etherscan: { name: 'SKALE Explorer', url: explorerUrl }, + default: { name: 'SKALE Explorer', url: explorerUrl }, + }, + contracts: {}, + } as const satisfies Chain } - export function getWebSocketUrl(chain: Chain): string { - // return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : ''; - return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : 'wss://goerli-light.eth.linkpool.io/ws'; // TODO - IP! -} \ No newline at end of file + // return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : ''; + return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : 'wss://goerli-light.eth.linkpool.io/ws' // TODO - IP! +} diff --git a/src/index.ts b/src/index.ts index b3a2dee..ef8ee23 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,3 @@ -export { Metaport, ChainIcon, WidgetUI, interfaces, dataclasses } from "./Metaport"; +export { Metaport, ChainIcon, WidgetUI, interfaces, dataclasses } from './Metaport' +export { useMetaportStore } from './store/MetaportState'; +export { useUIStore, useCollapseStore } from './store/Store'; \ No newline at end of file diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index a2bbc3a..bebb62c 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -21,306 +21,286 @@ * @copyright SKALE Labs 2023-Present */ -import debug from 'debug'; +import debug from 'debug' import { MainnetChain, SChain } from '@skalenetwork/ima-js' import { create } from 'zustand' import MetaportCore from '../core/metaport' -import * as interfaces from '../core/interfaces'; -import * as dataclasses from '../core/dataclasses'; -import { getEmptyTokenDataMap } from '../core/tokens/helper'; -import { MAINNET_CHAIN_NAME, DEFAULT_ERROR_MSG } from '../core/constants'; -import { getStepsMetadata } from '../core/transfer_steps'; -import { ACTIONS } from '../core/actions'; -import { WalletClient } from 'viem'; - - -debug.enable('*'); -const log = debug('metaport:state'); +import * as interfaces from '../core/interfaces' +import * as dataclasses from '../core/dataclasses' +import { getEmptyTokenDataMap } from '../core/tokens/helper' +import { MAINNET_CHAIN_NAME, DEFAULT_ERROR_MSG } from '../core/constants' +import { getStepsMetadata } from '../core/transfer_steps' +import { ACTIONS } from '../core/actions' +import { WalletClient } from 'viem' +debug.enable('*') +const log = debug('metaport:state') interface MetaportState { - mainnetChain: MainnetChain - setMainnetChain: (mainnet: MainnetChain) => void - sChain1: SChain - setSChain1: (schain: SChain) => void - sChain2: SChain - setSChain2: (schain: SChain) => void + mainnetChain: MainnetChain + setMainnetChain: (mainnet: MainnetChain) => void + sChain1: SChain + setSChain1: (schain: SChain) => void + sChain2: SChain + setSChain2: (schain: SChain) => void - mpc: MetaportCore, - setMpc: (mpc: MetaportCore) => void + mpc: MetaportCore + setMpc: (mpc: MetaportCore) => void - amount: string - setAmount: (amount: string, address: `0x${string}`) => void + amount: string + setAmount: (amount: string, address: `0x${string}`) => void - tokenId: number - setTokenId: (tokenId: number) => void + tokenId: number + setTokenId: (tokenId: number) => void - execute: ( - address: string, - switchNetwork: (chainId: number) => void, - walletClient: WalletClient - ) => void - check: (amount: string, address: `0x${string}`) => void + execute: (address: string, switchNetwork: (chainId: number) => void, walletClient: WalletClient) => void + check: (amount: string, address: `0x${string}`) => void - currentStep: number - setCurrentStep: (currentStep: number) => void + currentStep: number + setCurrentStep: (currentStep: number) => void - stepsMetadata: dataclasses.StepMetadata[] - setStepsMetadata: (steps: dataclasses.StepMetadata[]) => void + stepsMetadata: dataclasses.StepMetadata[] + setStepsMetadata: (steps: dataclasses.StepMetadata[]) => void - chainName1: string, - chainName2: string, + chainName1: string + chainName2: string - setChainName1: (name: string) => void - setChainName2: (name: string) => void + setChainName1: (name: string) => void + setChainName2: (name: string) => void - tokens: interfaces.TokenDataTypesMap + tokens: interfaces.TokenDataTypesMap - token: dataclasses.TokenData - setToken: (token: dataclasses.TokenData) => void + token: dataclasses.TokenData + setToken: (token: dataclasses.TokenData) => void - tokenContracts: interfaces.TokenContractsMap - tokenBalances: interfaces.TokenBalancesMap - updateTokenBalances: (address: string) => Promise + tokenContracts: interfaces.TokenContractsMap + tokenBalances: interfaces.TokenBalancesMap + updateTokenBalances: (address: string) => Promise - amountErrorMessage: string - setAmountErrorMessage: (amountErrorMessage: string) => void + amountErrorMessage: string + setAmountErrorMessage: (amountErrorMessage: string) => void - errorMessage: dataclasses.ErrorMessage - setErrorMessage: (amountErrorMessage: dataclasses.ErrorMessage) => void + errorMessage: dataclasses.ErrorMessage + setErrorMessage: (amountErrorMessage: dataclasses.ErrorMessage) => void - actionBtnDisabled: boolean - setActionBtnDisabled: (actionBtnDisabled: boolean) => void + actionBtnDisabled: boolean + setActionBtnDisabled: (actionBtnDisabled: boolean) => void - loading: boolean - setLoading: (loading: boolean) => void + loading: boolean + setLoading: (loading: boolean) => void - transferInProgress: boolean - setTransferInProgress: (loading: boolean) => void + transferInProgress: boolean + setTransferInProgress: (loading: boolean) => void - btnText: string - setBtnText: (btnText: string) => void + btnText: string + setBtnText: (btnText: string) => void - errorMessageClosedFallback: () => void - startOver: () => void + errorMessageClosedFallback: () => void + startOver: () => void } - export const useMetaportStore = create()((set, get) => ({ - mainnetChain: null, - setMainnetChain: (mainnet: MainnetChain) => set(() => ({ mainnetChain: mainnet })), + mainnetChain: null, + setMainnetChain: (mainnet: MainnetChain) => set(() => ({ mainnetChain: mainnet })), - sChain1: null, - setSChain1: (schain: SChain) => set(() => ({ sChain1: schain })), + sChain1: null, + setSChain1: (schain: SChain) => set(() => ({ sChain1: schain })), - sChain2: null, - setSChain2: (schain: SChain) => set(() => ({ sChain2: schain })), + sChain2: null, + setSChain2: (schain: SChain) => set(() => ({ sChain2: schain })), - mpc: null, - setMpc: (mpc: MetaportCore) => set(() => ({ mpc: mpc })), + mpc: null, + setMpc: (mpc: MetaportCore) => set(() => ({ mpc: mpc })), - tokenId: null, - setTokenId: (tokenId: number) => set(() => { - return { - tokenId: tokenId - } + tokenId: null, + setTokenId: (tokenId: number) => + set(() => { + return { + tokenId: tokenId, + } }), - amount: '', - setAmount: (amount: string, address: `0x${string}`) => set((state) => { - state.check(amount, address); - return { - amount: amount - } + amount: '', + setAmount: (amount: string, address: `0x${string}`) => + set((state) => { + state.check(amount, address) + return { + amount: amount, + } }), - - execute: async (address: string, switchNetwork: any, walletClient: WalletClient) => { - log('Running execute'); - if (get().stepsMetadata[get().currentStep]) { - set({ - loading: true, - transferInProgress: true - }) - try { - const stepMetadata = get().stepsMetadata[get().currentStep]; - const actionClass = ACTIONS[stepMetadata.type]; - await new actionClass( - get().mpc, - stepMetadata.from, - stepMetadata.to, - address, - get().amount, - get().tokenId, - get().token, - get().setAmountErrorMessage, - get().setBtnText, - switchNetwork, - walletClient - ).execute(); - } catch (err) { - console.error(err); - const msg = err.message ? err.message : DEFAULT_ERROR_MSG; - set({ - errorMessage: new dataclasses.TransactionErrorMessage( - msg, - get().errorMessageClosedFallback - ) - }); - return; - } finally { - set({ loading: false }); - } - set({ - transferInProgress: get().currentStep + 1 !== get().stepsMetadata.length, - currentStep: get().currentStep + 1, - - }); - } - }, - - errorMessageClosedFallback() { - set({ - loading: false, - errorMessage: undefined, - transferInProgress: get().currentStep !== 0 - }); - }, - - startOver() { + execute: async (address: string, switchNetwork: any, walletClient: WalletClient) => { + log('Running execute') + if (get().stepsMetadata[get().currentStep]) { + set({ + loading: true, + transferInProgress: true, + }) + try { + const stepMetadata = get().stepsMetadata[get().currentStep] + const actionClass = ACTIONS[stepMetadata.type] + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + get().amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + switchNetwork, + walletClient, + ).execute() + } catch (err) { + console.error(err) + const msg = err.message ? err.message : DEFAULT_ERROR_MSG set({ - loading: false, - errorMessage: undefined, - amount: '', - tokenId: null, - currentStep: 0, - transferInProgress: false - }); - }, - - check: async (amount: string, address: string) => { - if (get().stepsMetadata[get().currentStep]) { - set({ - loading: true, - btnText: 'Checking balance...' - }); - const stepMetadata = get().stepsMetadata[get().currentStep]; - const actionClass = ACTIONS[stepMetadata.type]; - await new actionClass( - get().mpc, - stepMetadata.from, - stepMetadata.to, - address, - amount, - get().tokenId, - get().token, - get().setAmountErrorMessage, - get().setBtnText, - null, - null - ).preAction(); - // console.log(); - // console.log('going to check amount!!!'); - } - set({ loading: false }); - }, - - currentStep: 0, - setCurrentStep: (currentStep: number) => set(() => ({ currentStep: currentStep })), - - stepsMetadata: [], - setStepsMetadata: (steps: dataclasses.StepMetadata[]) => set(() => ({ stepsMetadata: steps })), - - chainName1: '', - chainName2: '', - - setChainName1: (name: string) => set((state) => { - // updateState( - // name, - // state.chainName2 - // ) - - const updState = {}; - if (name === MAINNET_CHAIN_NAME) { - updState['mainnetChain'] = state.mpc.mainnet(); - } else { - updState['sChain1'] = state.mpc.schain(name); - } - const provider = updState['mainnetChain'] ? updState['mainnetChain'].provider : updState['sChain1'].provider; - const tokens = state.mpc.tokens(name); - const tokenContracts = state.mpc.tokenContracts( - tokens, - dataclasses.TokenType.erc20, - name, - provider - ); - return { - currentStep: 0, - token: null, - chainName1: name, - tokens: tokens, - tokenContracts: tokenContracts, - ...updState - } + errorMessage: new dataclasses.TransactionErrorMessage(msg, get().errorMessageClosedFallback), + }) + return + } finally { + set({ loading: false }) + } + set({ + transferInProgress: get().currentStep + 1 !== get().stepsMetadata.length, + currentStep: get().currentStep + 1, + }) + } + }, + + errorMessageClosedFallback() { + set({ + loading: false, + errorMessage: undefined, + transferInProgress: get().currentStep !== 0, + }) + }, + + startOver() { + set({ + loading: false, + errorMessage: undefined, + amount: '', + tokenId: null, + currentStep: 0, + transferInProgress: false, + }) + }, + + check: async (amount: string, address: string) => { + if (get().stepsMetadata[get().currentStep]) { + set({ + loading: true, + btnText: 'Checking balance...', + }) + const stepMetadata = get().stepsMetadata[get().currentStep] + const actionClass = ACTIONS[stepMetadata.type] + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + null, + null, + ).preAction() + // console.log(); + // console.log('going to check amount!!!'); + } + set({ loading: false }) + }, + + currentStep: 0, + setCurrentStep: (currentStep: number) => set(() => ({ currentStep: currentStep })), + + stepsMetadata: [], + setStepsMetadata: (steps: dataclasses.StepMetadata[]) => set(() => ({ stepsMetadata: steps })), + + chainName1: '', + chainName2: '', + + setChainName1: (name: string) => + set((state) => { + // updateState( + // name, + // state.chainName2 + // ) + + const updState = {} + if (name === MAINNET_CHAIN_NAME) { + updState['mainnetChain'] = state.mpc.mainnet() + } else { + updState['sChain1'] = state.mpc.schain(name) + } + const provider = updState['mainnetChain'] ? updState['mainnetChain'].provider : updState['sChain1'].provider + const tokens = state.mpc.tokens(name) + const tokenContracts = state.mpc.tokenContracts(tokens, dataclasses.TokenType.erc20, name, provider) + return { + currentStep: 0, + token: null, + chainName1: name, + tokens: tokens, + tokenContracts: tokenContracts, + ...updState, + } }), - setChainName2: (name: string) => set((state) => { - const updState = {}; - if (name === MAINNET_CHAIN_NAME) { - updState['mainnetChain'] = state.mpc.mainnet(); - } else { - updState['sChain2'] = state.mpc.schain(name); - } - return { - currentStep: 0, - token: null, - chainName2: name, - tokens: state.mpc.tokens(state.chainName1, name), - stepsMetadata: getStepsMetadata( - get().mpc.config, - get().token, - name - ), - ...updState - } + setChainName2: (name: string) => + set((state) => { + const updState = {} + if (name === MAINNET_CHAIN_NAME) { + updState['mainnetChain'] = state.mpc.mainnet() + } else { + updState['sChain2'] = state.mpc.schain(name) + } + return { + currentStep: 0, + token: null, + chainName2: name, + tokens: state.mpc.tokens(state.chainName1, name), + stepsMetadata: getStepsMetadata(get().mpc.config, get().token, name), + ...updState, + } }), - tokens: getEmptyTokenDataMap(), + tokens: getEmptyTokenDataMap(), - token: null, - setToken: (token: dataclasses.TokenData) => set(() => ({ - token: token, - stepsMetadata: getStepsMetadata( - get().mpc.config, - token, - get().chainName2 - ) + token: null, + setToken: (token: dataclasses.TokenData) => + set(() => ({ + token: token, + stepsMetadata: getStepsMetadata(get().mpc.config, token, get().chainName2), })), - tokenContracts: {}, - tokenBalances: {}, + tokenContracts: {}, + tokenBalances: {}, - updateTokenBalances: async (address: string) => { - const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address); - set({ tokenBalances: tokenBalances }); - }, + updateTokenBalances: async (address: string) => { + const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address) + set({ tokenBalances: tokenBalances }) + }, - amountErrorMessage: null, - setAmountErrorMessage: (em: string) => set(() => ({ amountErrorMessage: em })), + amountErrorMessage: null, + setAmountErrorMessage: (em: string) => set(() => ({ amountErrorMessage: em })), - errorMessage: null, - setErrorMessage: (em: dataclasses.ErrorMessage) => set(() => ({ errorMessage: em })), + errorMessage: null, + setErrorMessage: (em: dataclasses.ErrorMessage) => set(() => ({ errorMessage: em })), - actionBtnDisabled: false, - setActionBtnDisabled: (disabled: boolean) => set(() => ({ actionBtnDisabled: disabled })), + actionBtnDisabled: false, + setActionBtnDisabled: (disabled: boolean) => set(() => ({ actionBtnDisabled: disabled })), - loading: false, - setLoading: (loading: boolean) => set(() => ({ loading: loading })), + loading: false, + setLoading: (loading: boolean) => set(() => ({ loading: loading })), - transferInProgress: false, - setTransferInProgress: (inProgress: boolean) => set(() => ({ transferInProgress: inProgress })), + transferInProgress: false, + setTransferInProgress: (inProgress: boolean) => set(() => ({ transferInProgress: inProgress })), - btnText: null, - setBtnText: (btnText: string) => set(() => ({ btnText: btnText })) -})); + btnText: null, + setBtnText: (btnText: string) => set(() => ({ btnText: btnText })), +})) diff --git a/src/store/Store.ts b/src/store/Store.ts index 59e4aa2..43b0952 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -23,53 +23,52 @@ import { create } from 'zustand' -import * as interfaces from '../core/interfaces'; +import * as interfaces from '../core/interfaces' interface UIState { - theme: interfaces.MetaportTheme - setTheme: (theme: interfaces.MetaportTheme) => void - open: boolean, - setOpen: (isOpen: boolean) => void + theme: interfaces.MetaportTheme + setTheme: (theme: interfaces.MetaportTheme) => void + open: boolean + setOpen: (isOpen: boolean) => void } - export const useUIStore = create()((set) => ({ - theme: null, - setTheme: (theme: interfaces.MetaportTheme) => set(() => ({ theme: theme })), - open: false, - setOpen: (isOpen: boolean) => set(() => ({ open: isOpen })), -})); - + theme: null, + setTheme: (theme: interfaces.MetaportTheme) => set(() => ({ theme: theme })), + open: false, + setOpen: (isOpen: boolean) => set(() => ({ open: isOpen })), +})) interface CollapseState { - expandedFrom: string | false, - setExpandedFrom: (expanded: string | false) => void, - expandedTo: string | false, - setExpandedTo: (expanded: string | false) => void + expandedFrom: string | false + setExpandedFrom: (expanded: string | false) => void + expandedTo: string | false + setExpandedTo: (expanded: string | false) => void - expandedTokens: string | false, - setExpandedTokens: (expanded: string | false) => void + expandedTokens: string | false + setExpandedTokens: (expanded: string | false) => void } - export const useCollapseStore = create()((set) => ({ - expandedFrom: false, - setExpandedFrom: (expanded: string | false) => set(() => ({ - expandedFrom: expanded, - expandedTo: false, - expandedTokens: false + expandedFrom: false, + setExpandedFrom: (expanded: string | false) => + set(() => ({ + expandedFrom: expanded, + expandedTo: false, + expandedTokens: false, })), - expandedTo: false, - setExpandedTo: (expanded: string | false) => set(() => ({ - expandedTo: expanded, - expandedFrom: false, - expandedTokens: false + expandedTo: false, + setExpandedTo: (expanded: string | false) => + set(() => ({ + expandedTo: expanded, + expandedFrom: false, + expandedTokens: false, })), - expandedTokens: false, - setExpandedTokens: (expanded: string | false) => set(() => ({ - expandedTokens: expanded, - expandedFrom: false, - expandedTo: false - })) -})); - + expandedTokens: false, + setExpandedTokens: (expanded: string | false) => + set(() => ({ + expandedTokens: expanded, + expandedFrom: false, + expandedTo: false, + })), +})) diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 9c9c4e8..c1c95ed 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -1,37 +1,32 @@ -declare module "*.json" { - const value: any; - export default value; +declare module '*.json' { + const value: any + export default value } -declare module "*.svg" { - const content: any; - export default content; +declare module '*.svg' { + const content: any + export default content } -declare module "*.png" { - const content: any; - export default content; +declare module '*.png' { + const content: any + export default content } -declare module '*.scss'; +declare module '*.scss' declare module 'node' { - interface NodeRequire { - context: ( - directory: string, - useSubdirectories: boolean, - regExp: RegExp, - mode?: string - ) => any; - } + interface NodeRequire { + context: (directory: string, useSubdirectories: boolean, regExp: RegExp, mode?: string) => any + } } declare namespace NodeJS { - interface Global { - require: NodeRequire; - } + interface Global { + require: NodeRequire + } } interface NodeRequire { - context: (directory: string, useSubdirectories: boolean, regExp: RegExp, mode?: string) => any; + context: (directory: string, useSubdirectories: boolean, regExp: RegExp, mode?: string) => any } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 8b50155..164e434 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,7 +1,7 @@ -export {}; +export {} declare global { interface Window { - ethereum: any; + ethereum: any } -} \ No newline at end of file +} diff --git a/src/types/react-app-env.d.ts b/src/types/react-app-env.d.ts index 0e18010..24feb0b 100644 --- a/src/types/react-app-env.d.ts +++ b/src/types/react-app-env.d.ts @@ -1,4 +1,3 @@ - interface Window { - ethereum: any -} \ No newline at end of file + ethereum: any +} From 1bee1af947f50a6c118443fe7f199173f1e884df Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Aug 2023 22:28:17 +0100 Subject: [PATCH 016/110] Move theming logic to metaport provider --- src/Metaport.tsx | 10 ++- src/components/Metaport/Metaport.tsx | 72 ++--------------- .../MetaportProvider.tsx} | 58 +++++++++---- src/components/MetaportProvider/index.ts | 1 + src/components/Widget/Widget.mdx | 23 ------ src/components/Widget/Widget.stories.tsx | 21 ----- src/components/Widget/index.ts | 1 - src/components/WidgetUI/WidgetUI.tsx | 81 +++++-------------- src/index.ts | 2 +- src/styles/styles.module.scss | 2 + 10 files changed, 80 insertions(+), 191 deletions(-) rename src/components/{Widget/Widget.tsx => MetaportProvider/MetaportProvider.tsx} (74%) create mode 100644 src/components/MetaportProvider/index.ts delete mode 100644 src/components/Widget/Widget.mdx delete mode 100644 src/components/Widget/Widget.stories.tsx delete mode 100644 src/components/Widget/index.ts diff --git a/src/Metaport.tsx b/src/Metaport.tsx index e85d3a5..4c52c2a 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -25,7 +25,6 @@ import React from 'react' // import { createRoot } from 'react-dom/client'; -import Widget from './components/Widget' import { internalEvents } from './core/events' import * as interfaces from './core/interfaces/index' @@ -41,6 +40,15 @@ export { WidgetUI } import Metaport from './components/Metaport' export { Metaport } +import MetaportProvider from './components/MetaportProvider'; +export { MetaportProvider } + +import SkPaper from './components/SkPaper'; +export { SkPaper } + +import SkConnect from './components/SkConnect'; +export { SkConnect } + // export * as sfuel from './core/sfuel'; export class InjectedMetaport { diff --git a/src/components/Metaport/Metaport.tsx b/src/components/Metaport/Metaport.tsx index 8540da0..fc9863f 100644 --- a/src/components/Metaport/Metaport.tsx +++ b/src/components/Metaport/Metaport.tsx @@ -20,77 +20,15 @@ * @copyright SKALE Labs 2023-Present */ -import { RainbowKitProvider } from '@rainbow-me/rainbowkit' -import { configureChains, createConfig, WagmiConfig } from 'wagmi' -import { mainnet, goerli } from 'wagmi/chains' -import { jsonRpcProvider } from 'wagmi/providers/jsonRpc' -import { connectorsForWallets } from '@rainbow-me/rainbowkit' - -import { injectedWallet, coinbaseWallet, metaMaskWallet } from '@rainbow-me/rainbowkit/wallets' - import { MetaportConfig } from '../../core/interfaces' import WidgetUI from '../WidgetUI' +import MetaportProvider from '../MetaportProvider/MetaportProvider' -import '@rainbow-me/rainbowkit/styles.css' - -import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network' - -const { chains, webSocketPublicClient } = configureChains( - [ - mainnet, - goerli, - constructWagmiChain('staging', 'staging-legal-crazy-castor'), - constructWagmiChain('staging', 'staging-utter-unripe-menkar'), - constructWagmiChain('staging', 'staging-faint-slimy-achird'), - constructWagmiChain('staging', 'staging-perfect-parallel-gacrux'), - constructWagmiChain('staging', 'staging-severe-violet-wezen'), - constructWagmiChain('staging', 'staging-weepy-fitting-caph'), - - constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), - constructWagmiChain('mainnet', 'elated-tan-skat'), - constructWagmiChain('mainnet', 'affectionate-immediate-pollux'), - ], - [ - jsonRpcProvider({ - rpc: (chain) => ({ - http: chain.rpcUrls.default.http[0], - webSocket: getWebSocketUrl(chain), - }), - }), - ], -) - -const connectors = connectorsForWallets([ - { - groupName: 'Supported Wallets', - wallets: [ - metaMaskWallet({ chains, projectId: '' }), - injectedWallet({ chains }), - coinbaseWallet({ chains, appName: 'TEST' }), - ], - }, -]) - -const wagmiConfig = createConfig({ - autoConnect: true, - connectors, - publicClient: webSocketPublicClient, -}) - -export default function Widget(props: { config: MetaportConfig }) { +export default function Metaport(props: { config: MetaportConfig }) { return ( - - - - - + + + ) } diff --git a/src/components/Widget/Widget.tsx b/src/components/MetaportProvider/MetaportProvider.tsx similarity index 74% rename from src/components/Widget/Widget.tsx rename to src/components/MetaportProvider/MetaportProvider.tsx index 12fbec2..c16b054 100644 --- a/src/components/Widget/Widget.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -16,32 +16,37 @@ * along with this program. If not, see . */ /** - * @file Widget.ts + * @file MetaportProvider.ts * @copyright SKALE Labs 2023-Present */ -import React, { useEffect } from 'react' +import { ReactElement, useEffect } from 'react' -import { RainbowKitProvider, darkTheme, lightTheme } from '@rainbow-me/rainbowkit' +import { RainbowKitProvider } from '@rainbow-me/rainbowkit' import { configureChains, createConfig, WagmiConfig } from 'wagmi' import { mainnet, goerli } from 'wagmi/chains' import { jsonRpcProvider } from 'wagmi/providers/jsonRpc' import { connectorsForWallets } from '@rainbow-me/rainbowkit' +import { PaletteMode } from '@mui/material' import { injectedWallet, coinbaseWallet, metaMaskWallet } from '@rainbow-me/rainbowkit/wallets' import { MetaportConfig } from '../../core/interfaces' -import WidgetUI from '../WidgetUI' -import { useUIStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' -import { getWidgetTheme } from '../../core/themes' -import MetaportCore from '../../core/metaport' +import { StyledEngineProvider } from '@mui/material/styles' +import { createTheme, ThemeProvider } from '@mui/material/styles' import '@rainbow-me/rainbowkit/styles.css' import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network' +import { getWidgetTheme, getMuiZIndex } from '../../core/themes' + +import { useUIStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' +import MetaportCore from '../../core/metaport' + + const { chains, webSocketPublicClient } = configureChains( [ mainnet, @@ -84,16 +89,18 @@ const wagmiConfig = createConfig({ publicClient: webSocketPublicClient, }) -export default function Widget(props: { config: MetaportConfig }) { +export default function MetaportProvider(props: { + config: MetaportConfig, + className?: string + children?: ReactElement | ReactElement[] +}) { + const widgetTheme = getWidgetTheme(props.config.theme) - const theme = widgetTheme.mode === 'dark' ? darkTheme() : lightTheme() const setTheme = useUIStore((state) => state.setTheme) const setMpc = useMetaportStore((state) => state.setMpc) const setOpen = useUIStore((state) => state.setOpen) - - theme.colors.connectButtonInnerBackground = widgetTheme.background - theme.colors.connectButtonBackground = widgetTheme.background + const metaportTheme = useUIStore((state) => state.theme) useEffect(() => { setOpen(props.config.openOnLoad) @@ -107,6 +114,24 @@ export default function Widget(props: { config: MetaportConfig }) { setMpc(new MetaportCore(props.config)) }, [setMpc]) + let theme = createTheme({ + zIndex: getMuiZIndex(widgetTheme), + palette: { + mode: widgetTheme.mode as PaletteMode, + background: { + paper: widgetTheme.background, + }, + primary: { + main: widgetTheme.primary, + }, + secondary: { + main: widgetTheme.background, + }, + }, + }) + + if (!metaportTheme) return
+ return ( - + + + {props.children} + + ) diff --git a/src/components/MetaportProvider/index.ts b/src/components/MetaportProvider/index.ts new file mode 100644 index 0000000..c947cbc --- /dev/null +++ b/src/components/MetaportProvider/index.ts @@ -0,0 +1 @@ +export { default } from './MetaportProvider' diff --git a/src/components/Widget/Widget.mdx b/src/components/Widget/Widget.mdx deleted file mode 100644 index 425c0a4..0000000 --- a/src/components/Widget/Widget.mdx +++ /dev/null @@ -1,23 +0,0 @@ -import { useState } from 'react' - -import { Canvas, Story, ArgsTable } from '@storybook/blocks' - -import Grid from '@mui/material/Grid' -import TextField from '@mui/material/TextField' -import Button from '@mui/material/Button' - -import { internalEvents } from '../../core/events' -import * as WidgetStories from './Widget.stories' - -# Functional Metaport Demo - - - - ## Sandbox mode - You can try functional Metaport demo in a Sandbox mode here. - - - - - - diff --git a/src/components/Widget/Widget.stories.tsx b/src/components/Widget/Widget.stories.tsx deleted file mode 100644 index 1d9405d..0000000 --- a/src/components/Widget/Widget.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import Widget from './Widget' -// import { storyDecorator } from "../WidgetUI/StorybookHelper"; - -const METAPORT_CONFIG = require('../../metadata/metaportConfigStaging.json') -METAPORT_CONFIG.mainnetEndpoint = process.env.STORYBOOK_MAINNET_ENDPOINT - -const meta: Meta = { - title: 'Functional/Widget', - component: Widget, - // decorators: [storyDecorator], -} - -export default meta -type Story = StoryObj - -export const WidgetDemo: Story = { - args: { - config: METAPORT_CONFIG, - }, -} diff --git a/src/components/Widget/index.ts b/src/components/Widget/index.ts deleted file mode 100644 index 81db243..0000000 --- a/src/components/Widget/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Widget' diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index ea7402a..443533c 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -29,9 +29,6 @@ import Collapse from '@mui/material/Collapse' import Fab from '@mui/material/Fab' import CloseIcon from '@mui/icons-material/Close' -import { createTheme, ThemeProvider } from '@mui/material/styles' -import { getMuiZIndex } from '../../core/themes' - import skaleLogo from './skale_logo_short.svg' import { useUIStore } from '../../store/Store' @@ -44,58 +41,22 @@ import { cls } from '../../core/helper' import styles from '../../styles/styles.module.scss' import common from '../../styles/common.module.scss' -import { PaletteMode } from '@mui/material' - -import { getWidgetTheme } from '../../core/themes' import SkConnect from '../SkConnect' import ErrorMessage from '../ErrorMessage' import { MetaportConfig } from '../../core/interfaces' -import MetaportCore from '../../core/metaport' + export function WidgetUI(props: { config: MetaportConfig }) { - const widgetTheme = getWidgetTheme(props.config.theme) - const setTheme = useUIStore((state) => state.setTheme) - const setMpc = useMetaportStore((state) => state.setMpc) + const metaportTheme = useUIStore((state) => state.theme) + const isOpen = useUIStore((state) => state.open) const setOpen = useUIStore((state) => state.setOpen) - useEffect(() => { - setOpen(props.config.openOnLoad) - }, []) - - useEffect(() => { - setTheme(widgetTheme) - }, [setTheme]) - - useEffect(() => { - setMpc(new MetaportCore(props.config)) - }, [setMpc]) - const { address } = useAccount() - const metaportTheme = useUIStore((state) => state.theme) - const isOpen = useUIStore((state) => state.open) - const errorMessage = useMetaportStore((state) => state.errorMessage) - if (!metaportTheme) return
- - let theme = createTheme({ - zIndex: getMuiZIndex(metaportTheme), - palette: { - mode: metaportTheme.mode as PaletteMode, - background: { - paper: metaportTheme.background, - }, - primary: { - main: metaportTheme.primary, - }, - secondary: { - main: metaportTheme.background, - }, - }, - }) const handleClick = (_: React.MouseEvent) => { setOpen(isOpen ? false : true) @@ -137,27 +98,23 @@ export function WidgetUI(props: { config: MetaportConfig }) { ) return ( - - -
-
{fabTop ? fabButton : null}
- - - - - - - - {address ? :
}
-
+
+
{fabTop ? fabButton : null}
+ + + + + + -
{fabTop ? null : fabButton}
-
- - + {address ? :
}
+ +
+
{fabTop ? null : fabButton}
+
) } diff --git a/src/index.ts b/src/index.ts index ef8ee23..0499aae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,3 @@ -export { Metaport, ChainIcon, WidgetUI, interfaces, dataclasses } from './Metaport' +export { SkConnect, SkPaper, MetaportProvider, Metaport, ChainIcon, WidgetUI, interfaces, dataclasses } from './Metaport' export { useMetaportStore } from './store/MetaportState'; export { useUIStore, useCollapseStore } from './store/Store'; \ No newline at end of file diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index b55885c..b61a340 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -214,6 +214,8 @@ button { padding-top: 0.8em !important; padding-bottom: 0.8em !important; min-height: $sk-btn-height !important; + border-radius: $sk-border-radius !important; + box-shadow: none !important; } .btnChain { From 4c3f9d1c8529bc05e3c1989d8ca4d6d29b2fa1c5 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 15 Aug 2023 16:26:05 +0100 Subject: [PATCH 017/110] Make tokenIcon exportable, add external interfaces --- .../MetaportProvider/MetaportProvider.tsx | 12 +++++- src/components/SkConnect/SkConnect.tsx | 4 +- src/components/TokenIcon/TokenIcon.tsx | 15 ++++--- src/components/TokenList/TokenList.tsx | 2 +- .../TokenListSection/TokenListSection.tsx | 5 ++- src/components/WidgetUI/WidgetUI.tsx | 5 +-- src/core/metadata.ts | 10 ++--- src/index.ts | 39 +++++++++++++++++-- 8 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index c16b054..c30ff69 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -42,10 +42,15 @@ import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network' import { getWidgetTheme, getMuiZIndex } from '../../core/themes' +import { cls } from '../../core/helper' + import { useUIStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' import MetaportCore from '../../core/metaport' +import styles from '../../styles/styles.module.scss' +import common from '../../styles/common.module.scss' + const { chains, webSocketPublicClient } = configureChains( [ @@ -102,6 +107,9 @@ export default function MetaportProvider(props: { const setOpen = useUIStore((state) => state.setOpen) const metaportTheme = useUIStore((state) => state.theme) + const themeCls = widgetTheme.mode === 'dark' ? styles.darkTheme : styles.lightTheme + const commonThemeCls = widgetTheme.mode === 'dark' ? common.darkTheme : common.lightTheme + useEffect(() => { setOpen(props.config.openOnLoad) }, []) @@ -144,7 +152,9 @@ export default function MetaportProvider(props: { > - {props.children} +
+ {props.children} +
diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index a286ead..27c097b 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -32,7 +32,7 @@ import { cls } from '../../core/helper' import styles from '../../styles/styles.module.scss' import common from '../../styles/common.module.scss' -// import skaleLogoFull from '../WidgetUI/skale_logo.svg'; +import skaleLogoFull from '../WidgetUI/skale_logo.svg'; import { useMetaportStore } from '../../store/MetaportState' import ChainIcon from '../ChainIcon' @@ -63,7 +63,7 @@ export default function SkConnect() { return (
- {/* */} +
} - - const iconPath = tokenIconPath(props.token) + const iconPath = props.iconUrl ?? tokenIcon(props.tokenSymbol) if (iconPath.default) { return } diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index 3f0d7ee..c938cc7 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -89,7 +89,7 @@ export default function TokenList() { >
- +

- +

{fabTop ? fabButton : null}
- diff --git a/src/core/metadata.ts b/src/core/metadata.ts index ae784c6..4086aa1 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -52,9 +52,9 @@ export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: st } } -export function tokenIcon(name: string) { - if (!name) return - const key = name.toLowerCase() +export function tokenIcon(tokenSymbol: string) { + if (!tokenSymbol) return + const key = tokenSymbol.toLowerCase() if (icons[key]) { return icons[key] } else { @@ -62,10 +62,6 @@ export function tokenIcon(name: string) { } } -export function tokenIconPath(token: TokenData) { - return token.meta.iconUrl ?? tokenIcon(token.meta.symbol) -} - export function getTokenName(token: TokenData): string { return token.meta.name ?? token.meta.symbol } diff --git a/src/index.ts b/src/index.ts index 0499aae..9fad3a7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,36 @@ -export { SkConnect, SkPaper, MetaportProvider, Metaport, ChainIcon, WidgetUI, interfaces, dataclasses } from './Metaport' -export { useMetaportStore } from './store/MetaportState'; -export { useUIStore, useCollapseStore } from './store/Store'; \ No newline at end of file +export { interfaces, dataclasses } from './Metaport' + +export { useMetaportStore } from './store/MetaportState' +export { useUIStore, useCollapseStore } from './store/Store' + +import Metaport from './components/Metaport' +import MetaportProvider from './components/MetaportProvider' + +import SkConnect from './components/SkConnect' +import SkPaper from './components/SkPaper' + +import ChainIcon from './components/ChainIcon' +import TokenIcon from './components/TokenIcon' + +import ChainsList from './components/ChainsList' +import TokenList from './components/TokenList' +import AmountInput from './components/AmountInput' + +import { cls } from './core/helper' +import styles from './styles/styles.module.scss' +import common from './styles/common.module.scss' + +export { + Metaport, + MetaportProvider, + SkPaper, + SkConnect, + ChainIcon, + TokenIcon, + ChainsList, + TokenList, + AmountInput, + cls, + styles, + common +} \ No newline at end of file From 035c1334205efbb4b7e0190518a29eb79e605d9c Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 15 Aug 2023 16:44:57 +0100 Subject: [PATCH 018/110] Fix storybook build - add react-dom --- .storybook/preview.ts | 1 - package.json | 2 ++ src/Metaport.tsx | 6 ++--- .../MetaportProvider/MetaportProvider.tsx | 8 ++---- src/components/SkConnect/SkConnect.tsx | 2 +- src/components/TokenIcon/TokenIcon.tsx | 5 ++-- src/components/TokenList/TokenList.tsx | 2 +- .../TokenListSection/TokenListSection.tsx | 5 +--- src/components/WidgetUI/WidgetUI.tsx | 4 --- src/index.ts | 26 +++++++++---------- 10 files changed, 25 insertions(+), 36 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 76fc315..f3f8a70 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,5 +1,4 @@ import type { Preview } from '@storybook/react' -import '../src/lib/tailwind/theme.css' const preview: Preview = { parameters: { diff --git a/package.json b/package.json index 05d80c2..a3477ad 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,8 @@ "postcss": "8.4.27", "prettier": "3.0.1", "prop-types": "15.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", "sass": "^1.65.1", "storybook": "7.2.2", "typescript": "5.1.6", diff --git a/src/Metaport.tsx b/src/Metaport.tsx index 4c52c2a..97751fd 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -40,13 +40,13 @@ export { WidgetUI } import Metaport from './components/Metaport' export { Metaport } -import MetaportProvider from './components/MetaportProvider'; +import MetaportProvider from './components/MetaportProvider' export { MetaportProvider } -import SkPaper from './components/SkPaper'; +import SkPaper from './components/SkPaper' export { SkPaper } -import SkConnect from './components/SkConnect'; +import SkConnect from './components/SkConnect' export { SkConnect } // export * as sfuel from './core/sfuel'; diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index c30ff69..f28e7be 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -51,7 +51,6 @@ import MetaportCore from '../../core/metaport' import styles from '../../styles/styles.module.scss' import common from '../../styles/common.module.scss' - const { chains, webSocketPublicClient } = configureChains( [ mainnet, @@ -95,11 +94,10 @@ const wagmiConfig = createConfig({ }) export default function MetaportProvider(props: { - config: MetaportConfig, + config: MetaportConfig className?: string children?: ReactElement | ReactElement[] }) { - const widgetTheme = getWidgetTheme(props.config.theme) const setTheme = useUIStore((state) => state.setTheme) @@ -152,9 +150,7 @@ export default function MetaportProvider(props: { > -
- {props.children} -
+
{props.children}
diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index 27c097b..50df7f7 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -32,7 +32,7 @@ import { cls } from '../../core/helper' import styles from '../../styles/styles.module.scss' import common from '../../styles/common.module.scss' -import skaleLogoFull from '../WidgetUI/skale_logo.svg'; +import skaleLogoFull from '../WidgetUI/skale_logo.svg' import { useMetaportStore } from '../../store/MetaportState' import ChainIcon from '../ChainIcon' diff --git a/src/components/TokenIcon/TokenIcon.tsx b/src/components/TokenIcon/TokenIcon.tsx index 1c22a05..6f980a3 100644 --- a/src/components/TokenIcon/TokenIcon.tsx +++ b/src/components/TokenIcon/TokenIcon.tsx @@ -27,10 +27,9 @@ import { tokenIcon } from '../../core/metadata' import styles from '../../styles/styles.module.scss' - export default function TokenIcon(props: { - tokenSymbol: string | undefined | null; - iconUrl?: string | undefined | null, + tokenSymbol: string | undefined | null + iconUrl?: string | undefined | null size?: 'xs' | 'sm' | 'md' | 'lg' }) { const size = props.size ?? 'sm' diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index c938cc7..ac83c71 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -89,7 +89,7 @@ export default function TokenList() { >
- +

- +

state.theme) const isOpen = useUIStore((state) => state.open) const setOpen = useUIStore((state) => state.setOpen) @@ -57,12 +55,10 @@ export function WidgetUI(props: { config: MetaportConfig }) { const errorMessage = useMetaportStore((state) => state.errorMessage) - const handleClick = (_: React.MouseEvent) => { setOpen(isOpen ? false : true) } - let fabTop: boolean = false let fabLeft: boolean = false if (metaportTheme) { diff --git a/src/index.ts b/src/index.ts index 9fad3a7..ee53b61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,16 +21,16 @@ import styles from './styles/styles.module.scss' import common from './styles/common.module.scss' export { - Metaport, - MetaportProvider, - SkPaper, - SkConnect, - ChainIcon, - TokenIcon, - ChainsList, - TokenList, - AmountInput, - cls, - styles, - common -} \ No newline at end of file + Metaport, + MetaportProvider, + SkPaper, + SkConnect, + ChainIcon, + TokenIcon, + ChainsList, + TokenList, + AmountInput, + cls, + styles, + common, +} From b8d45b4b93e850db6d9a0ec7d65f89424187cfd4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 15 Aug 2023 16:46:10 +0100 Subject: [PATCH 019/110] Update vercel build command --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index 9bd088d..202e3db 100644 --- a/vercel.json +++ b/vercel.json @@ -2,7 +2,7 @@ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "yarn build-storybook", "devCommand": "yarn storybook", - "installCommand": "bash prepare_meta.sh && yarn install", + "installCommand": "bash prepare_meta.sh && yarn install && yarn build", "framework": null, "outputDirectory": "./storybook-static" } \ No newline at end of file From 2c4c4fe35880b0ec01c1da8dbfc9dfddc3c27b22 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 16 Aug 2023 13:00:42 +0100 Subject: [PATCH 020/110] Remove reduntant scss ts modules, add transfer ETA and ETF modules --- .../AmountErrorMessage/AmountErrorMessage.tsx | 2 +- .../AmountInput/AmountInput.module.scss | 2 +- .../AmountInput/AmountInput.module.scss.d.ts | 9 --- src/components/AmountInput/AmountInput.tsx | 4 +- src/components/Metaport/Metaport.stories.tsx | 26 +++++++++ .../MetaportProvider/MetaportProvider.tsx | 2 +- src/components/Stepper/SkStepper.module.scss | 4 +- .../Stepper/SkStepper.module.scss.d.ts | 16 ------ src/components/Stepper/SkStepper.tsx | 8 +-- src/components/TokenList/TokenList.scss | 4 +- .../TokenListSection/TokenListSection.scss | 4 +- src/components/TransferETA/TransferETA.tsx | 53 ++++++++++++++++++ src/components/TransferETA/index.ts | 1 + src/components/TransferETF/TransferETF.tsx | 56 +++++++++++++++++++ src/components/TransferETF/index.ts | 1 + src/components/WidgetBody/WidgetBody.tsx | 11 +--- src/core/constants.ts | 2 +- src/core/fee_calculator.ts | 3 +- src/core/gas_station.ts | 31 ---------- src/core/metadata.ts | 2 + src/core/themes.ts | 2 +- src/index.ts | 14 +++++ src/styles/styles.module.scss | 37 ++++++------ 23 files changed, 193 insertions(+), 101 deletions(-) delete mode 100644 src/components/AmountInput/AmountInput.module.scss.d.ts create mode 100644 src/components/Metaport/Metaport.stories.tsx delete mode 100644 src/components/Stepper/SkStepper.module.scss.d.ts create mode 100644 src/components/TransferETA/TransferETA.tsx create mode 100644 src/components/TransferETA/index.ts create mode 100644 src/components/TransferETF/TransferETF.tsx create mode 100644 src/components/TransferETF/index.ts delete mode 100644 src/core/gas_station.ts diff --git a/src/components/AmountErrorMessage/AmountErrorMessage.tsx b/src/components/AmountErrorMessage/AmountErrorMessage.tsx index 40b2c67..e6315f3 100644 --- a/src/components/AmountErrorMessage/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage/AmountErrorMessage.tsx @@ -9,7 +9,7 @@ import { useMetaportStore } from '../../store/MetaportState' export default function AmountErrorMessage() { const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage) return ( - +

+

diff --git a/src/components/Metaport/Metaport.stories.tsx b/src/components/Metaport/Metaport.stories.tsx new file mode 100644 index 0000000..2b8f557 --- /dev/null +++ b/src/components/Metaport/Metaport.stories.tsx @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Metaport from "./Metaport"; + +import * as interfaces from '../../core/interfaces' +// import { storyDecorator } from "../WidgetUI/StorybookHelper"; + +import METAPORT_CONFIG from '../../metadata/metaportConfigStaging.json'; +const config = METAPORT_CONFIG as interfaces.MetaportConfig; + +// METAPORT_CONFIG.mainnetEndpoint = process.env.STORYBOOK_MAINNET_ENDPOINT; + +const meta: Meta = { + title: "Functional/Metaport", + component: Metaport, + // decorators: [storyDecorator], +}; + +export default meta; +type Story = StoryObj; + + +export const WidgetDemo: Story = { + args: { + config: config + } +}; \ No newline at end of file diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index f28e7be..54af185 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -150,7 +150,7 @@ export default function MetaportProvider(props: { > -
{props.children}
+
{props.children}
diff --git a/src/components/Stepper/SkStepper.module.scss b/src/components/Stepper/SkStepper.module.scss index 0196a89..14213ba 100644 --- a/src/components/Stepper/SkStepper.module.scss +++ b/src/components/Stepper/SkStepper.module.scss @@ -1,4 +1,4 @@ -.mp__labelStep { +.labelStep { // font-weight: 600 !important; text-transform: uppercase !important; font-size: 0.6525rem !important; @@ -6,7 +6,7 @@ letter-spacing: 0.02857em !important; } -.mp__stepper { +.stepper { :global span { font-weight: 600 !important; diff --git a/src/components/Stepper/SkStepper.module.scss.d.ts b/src/components/Stepper/SkStepper.module.scss.d.ts deleted file mode 100644 index 06fc8b2..0000000 --- a/src/components/Stepper/SkStepper.module.scss.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import globalClassNames from '../../style.d' -declare const classNames: typeof globalClassNames & { - readonly mp__labelStep: 'mp__labelStep' - readonly mp__stepper: 'mp__stepper' - readonly 'MuiStepConnector-line': 'MuiStepConnector-line' - readonly 'MuiBox-root': 'MuiBox-root' - readonly 'MuiSvgIcon-root': 'MuiSvgIcon-root' - readonly 'MuiStepContent-root': 'MuiStepContent-root' - readonly 'MuiChip-icon': 'MuiChip-icon' - readonly 'MuiStepLabel-root': 'MuiStepLabel-root' - readonly 'MuiStepLabel-iconContainer': 'MuiStepLabel-iconContainer' - readonly 'MuiStepLabel-labelContainer': 'MuiStepLabel-labelContainer' - readonly 'MuiStepLabel-label': 'MuiStepLabel-label' - readonly 'Mui-active': 'Mui-active' -} -export = classNames diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 2047ed1..50beacd 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -13,7 +13,6 @@ import common from '../../styles/common.module.scss' import styles from '../../styles/styles.module.scss' import localStyles from './SkStepper.module.scss' import ChainIcon from '../ChainIcon' -import SkPaper from '../SkPaper' import { useMetaportStore } from '../../store/MetaportState' import { Collapse } from '@mui/material' @@ -50,13 +49,13 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { if (stepsMetadata.length === 0) return
return ( - + - + {stepsMetadata.map((step, i) => ( - +

{step.headline}

@@ -145,7 +144,6 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) {
)} - ) } diff --git a/src/components/TokenList/TokenList.scss b/src/components/TokenList/TokenList.scss index 53c672c..a93d6e6 100644 --- a/src/components/TokenList/TokenList.scss +++ b/src/components/TokenList/TokenList.scss @@ -1,11 +1,11 @@ -.mp__iconToken { +.iconToken { width: 22px; height: 22px; } -.default-mp__iconToken { +.default-iconToken { color: black; background-color: #dbe106; padding: 4px; diff --git a/src/components/TokenListSection/TokenListSection.scss b/src/components/TokenListSection/TokenListSection.scss index 0822c74..0377d29 100644 --- a/src/components/TokenListSection/TokenListSection.scss +++ b/src/components/TokenListSection/TokenListSection.scss @@ -1,13 +1,13 @@ -.mp__iconToken { +.iconToken { width: 21px; height: 21px; margin-left: 2px; margin-right: 2px; } -.default-mp__iconToken { +.default-iconToken { color: black; background-color: #dbe106; padding: 4px; diff --git a/src/components/TransferETA/TransferETA.tsx b/src/components/TransferETA/TransferETA.tsx new file mode 100644 index 0000000..c2ea774 --- /dev/null +++ b/src/components/TransferETA/TransferETA.tsx @@ -0,0 +1,53 @@ +import React, { useEffect } from 'react'; +import Tooltip from '@mui/material/Tooltip'; +import InfoIcon from '@mui/icons-material/Info'; +import Skeleton from '@mui/material/Skeleton'; + +import { isMainnet, cls } from '../../core/helper'; +import { IMA_M2S_WAIT, IMA_S2S_WAIT, IMA_HUB_WAIT } from '../../core/constants'; +// import { getAvgWaitTime } from '../../core/gas_station'; +import common from '../../styles/common.module.scss'; +import { TokenData } from '../../core/dataclasses'; + +export default function TransferETA(props: { token: TokenData, toChain: string }) { + const [eta, setEta] = React.useState(); + const [isLoaded, setIsLoaded] = React.useState(false); + + async function calcETA() { + setIsLoaded(false); + let baseETA = 0; + const fromMainnet = isMainnet(props.token.chain); + const toMainnet = isMainnet(props.toChain); + baseETA += fromMainnet || toMainnet ? IMA_M2S_WAIT : IMA_S2S_WAIT; + if (props.token.connections[props.toChain] && props.token.connections[props.toChain].hub) { + baseETA += IMA_HUB_WAIT; + } + setEta(baseETA) + setIsLoaded(true); + } + + useEffect(() => { + if (props.token && props.toChain) calcETA(); + }, [props.token, props.toChain]); + + const tooltipText = 'Estimated transfer time (may vary depending on the network load)'; + + return ( + +
+
+

+ ETA +

+ +
+ {isLoaded ? ( +

+ ~{eta}-{eta + 1} min +

) : ( + + )} +
+
+ ) +} \ No newline at end of file diff --git a/src/components/TransferETA/index.ts b/src/components/TransferETA/index.ts new file mode 100644 index 0000000..0e4694c --- /dev/null +++ b/src/components/TransferETA/index.ts @@ -0,0 +1 @@ +export { default } from './TransferETA' diff --git a/src/components/TransferETF/TransferETF.tsx b/src/components/TransferETF/TransferETF.tsx new file mode 100644 index 0000000..7f2fdb7 --- /dev/null +++ b/src/components/TransferETF/TransferETF.tsx @@ -0,0 +1,56 @@ +import React, { useEffect } from 'react'; +import Tooltip from '@mui/material/Tooltip'; +import InfoIcon from '@mui/icons-material/Info'; +import Skeleton from '@mui/material/Skeleton'; + +import { isMainnet, cls } from '../../core/helper'; +// import { getTransactionFee } from '../../core/fee_calculator'; + +import common from '../../styles/common.module.scss'; + + +function roundDown(number, decimals) { + decimals = decimals || 0; + return (Math.floor(number * Math.pow(10, decimals)) / Math.pow(10, decimals)); +} + + +export default function TransferETF(props: { fromChain: string }) { + const [etf, setEtf] = React.useState(); + const [isLoaded, setIsLoaded] = React.useState(false); + + async function calcETF() { + setIsLoaded(false); + const fromMainnet = isMainnet(props.fromChain); + let baseETF = 0; + // if (fromMainnet) baseETF = await getTransactionFee(); + if (fromMainnet) baseETF = 2.5 + setEtf(baseETF) + setIsLoaded(true); + } + + useEffect(() => { + if (props.fromChain) calcETF(); + }, [props.fromChain]); + + const tooltipText = 'Estimated transaction fee (You pay only for Mainnet transactions, all transfers within SKALE are free)'; + const etfText = (etf === 0) ? 'Free' : `~${roundDown(etf, 3)} USD` + + return ( + +
+
+

+ Estimated Transaction Fee +

+ +
+ {isLoaded ? ( +

+ {etfText}

) : ( + + )} +
+
+ ) +} \ No newline at end of file diff --git a/src/components/TransferETF/index.ts b/src/components/TransferETF/index.ts new file mode 100644 index 0000000..4879f38 --- /dev/null +++ b/src/components/TransferETF/index.ts @@ -0,0 +1 @@ +export { default } from './TransferETF' diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index fa7f668..88d8ec2 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -9,6 +9,8 @@ import SkStepper from '../Stepper' import SkPaper from '../SkPaper' import AmountErrorMessage from '../AmountErrorMessage' import SwitchDirection from '../SwitchDirection' +import TransferETF from '../TransferETF' +import TransferETA from '../TransferETA' import common from '../../styles/common.module.scss' import { cls } from '../../core/helper' @@ -68,15 +70,6 @@ export function WidgetBody(props) { disabledChain={chainName1} disabled={transferInProgress} /> - - {/*
-
- -
-
- {token ? : null} -
-
*/} diff --git a/src/core/constants.ts b/src/core/constants.ts index 0c016ce..39746ed 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -61,7 +61,7 @@ export const BASE_EXPLORER_URLS = { // ETA constants -export const GAS_STATION_API_ENDPOINT = 'https://ethgasstation.info/api/ethgasAPI.json?' +// export const GAS_STATION_API_ENDPOINT = 'https://ethgasstation.info/api/ethgasAPI.json?' export const IMA_M2S_WAIT = 5 export const IMA_S2S_WAIT = 2 diff --git a/src/core/fee_calculator.ts b/src/core/fee_calculator.ts index c14cd63..f8e2f90 100644 --- a/src/core/fee_calculator.ts +++ b/src/core/fee_calculator.ts @@ -30,10 +30,9 @@ import * as interfaces from './interfaces/index' debug.enable('*') const log = debug('metaport:components:fee_calculator') -export async function getTransactionFee(transferRequest: interfaces.TransferParams): Promise { +export async function getTransactionFee(): Promise { // todo: get actual gas limit for transfer // todo: get actual gas price - log(transferRequest) const gasLimit = toBN('250000') const gasPrice = toBN('10000000000') diff --git a/src/core/gas_station.ts b/src/core/gas_station.ts deleted file mode 100644 index d9242cd..0000000 --- a/src/core/gas_station.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file gas_station.ts - * @copyright SKALE Labs 2022-Present - */ - -import { GAS_STATION_API_ENDPOINT } from './constants' - -export async function getAvgWaitTime() { - const response = await fetch(GAS_STATION_API_ENDPOINT) - if (!response.ok) return 0 - const data = await response.json() - return data.avgWait -} diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 4086aa1..4deb5a1 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -32,6 +32,8 @@ import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons' import * as icons from '../icons' +// const icons = { eth: { default: '' } }; // TODO: fix for storybook + const CHAIN_ICONS = { mainnet: MAINNET_CHAIN_ICONS, staging: STAGING_CHAIN_ICONS, diff --git a/src/core/themes.ts b/src/core/themes.ts index 02c6844..da8fdf8 100644 --- a/src/core/themes.ts +++ b/src/core/themes.ts @@ -47,7 +47,7 @@ const MUI_ELEMENTS = ['mobileStepper', 'fab', 'speedDial', 'appBar', 'drawer', ' const INDEX_STEP = 50 -export function getWidgetTheme(theme: MetaportTheme | null): MetaportTheme { +export function getWidgetTheme(theme: MetaportTheme | null | undefined): MetaportTheme { if (!theme) return defaultThemes.dark as MetaportTheme if (theme.mode && Object.keys(theme).length === 1) { return defaultThemes[theme.mode] as MetaportTheme diff --git a/src/index.ts b/src/index.ts index ee53b61..1b7c83d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,11 +15,19 @@ import TokenIcon from './components/TokenIcon' import ChainsList from './components/ChainsList' import TokenList from './components/TokenList' import AmountInput from './components/AmountInput' +import SwitchDirection from './components/SwitchDirection' +import SkStepper from './components/Stepper' +import TransferETF from './components/TransferETF' +import TransferETA from './components/TransferETA' import { cls } from './core/helper' import styles from './styles/styles.module.scss' import common from './styles/common.module.scss' +import { getWidgetTheme as getMetaportTheme } from './core/themes'; + +import { useAccount as useWagmiAccount } from 'wagmi' + export { Metaport, MetaportProvider, @@ -30,7 +38,13 @@ export { ChainsList, TokenList, AmountInput, + SwitchDirection, + SkStepper, + TransferETF, + TransferETA, cls, styles, common, + getMetaportTheme, + useWagmiAccount } diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index b61a340..d76e4f0 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -20,13 +20,6 @@ padding: 10pt; border-radius: $sk-border-radius-outter; border: 1px rgba(127, 127, 127, .2705882353) solid; - - :global(.Mui-disabled) { - :global(.MuiAccordionSummary-expandIconWrapper) { - display: none !important; - } - opacity: 1 !important; - } } .darkTheme { @@ -115,9 +108,7 @@ button { } -.imaWidgetBody { - border-radius: $sk-border-radius-outter !important; - position: fixed; +.metaport { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif !important; -webkit-font-smoothing: antialiased; @@ -126,15 +117,29 @@ button { background-color: transparent !important; } - :global(.MuiAccordionDetails-root) { - padding: 0 !important; - } - :global(.MuiButton-root) { font-weight: 600 !important; box-shadow: none !important; border-radius: $sk-border-radius !important; } + + :global(.Mui-disabled) { + :global(.MuiAccordionSummary-expandIconWrapper) { + display: none !important; + } + + opacity: 1 !important; + } + + :global(.MuiAccordionDetails-root) { + padding: 0 !important; + } +} + + +.imaWidgetBody { + border-radius: $sk-border-radius-outter !important; + position: fixed; } .chainIconxs { @@ -187,8 +192,8 @@ button { .sk__btnSwitch { width: 100%; text-align: center; - margin-top: -13pt; - margin-bottom: -13pt; + margin-top: -13pt !important; + margin-bottom: -13pt !important; button { border: 13px #1a1a1a solid; From de8a4c129c842f426c3f680000415c162f59e69a Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 16 Aug 2023 19:25:35 +0100 Subject: [PATCH 021/110] New amount input, export amount error module --- src/components/AmountInput/AmountInput.tsx | 24 +++++++++---- src/components/Stepper/SkStepper.tsx | 7 ++-- src/components/TokenList/TokenList.tsx | 38 +++++++++++++-------- src/components/WidgetBody/WidgetBody.tsx | 39 +++++++++++++++------- src/core/metadata.ts | 2 +- src/index.ts | 2 ++ src/styles/common.module.scss | 5 +++ src/styles/styles.module.scss | 4 +++ 8 files changed, 85 insertions(+), 36 deletions(-) diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index e7523fa..ad30f9d 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -7,7 +7,10 @@ import { cls } from '../../core/helper' import common from '../../styles/common.module.scss' import localStyles from './AmountInput.module.scss' +import TokenList from '../TokenList' import { useMetaportStore } from '../../store/MetaportState' +import { useCollapseStore } from '../../store/Store' + export default function AmountInput() { const { address } = useAccount() @@ -16,6 +19,7 @@ export default function AmountInput() { const transferInProgress = useMetaportStore((state) => state.transferInProgress) const setAmount = useMetaportStore((state) => state.setAmount) const amount = useMetaportStore((state) => state.amount) + const expandedTokens = useCollapseStore((state) => state.expandedTokens) const handleChange = (event: React.ChangeEvent) => { if (parseFloat(event.target.value) < 0) { @@ -41,10 +45,15 @@ export default function AmountInput() { // } // } - if (!token) return + // if (!token) return return (
-
+ + {expandedTokens ? null :
-
+ + {/*
- {token.meta.symbol} -
+ {token ? token.meta.symbol : 'ETH'} +
*/} {/* {props.maxBtn ?
) diff --git a/src/components/TokenList/index.ts b/src/components/TokenList/index.ts index ba9b61b..a555ff1 100644 --- a/src/components/TokenList/index.ts +++ b/src/components/TokenList/index.ts @@ -1 +1,4 @@ export { default } from './TokenList' + +import TokenBalance from './TokenBalance' +export { TokenBalance } diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index e07d68e..3001eb2 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -71,7 +71,11 @@ export default function TokenListSection(props: { {getTokenName(props.tokens[key])}

- +
diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index cf266cf..d94d5e8 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -2,15 +2,14 @@ import React, { useEffect } from 'react' import { useCollapseStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' -import TokenList from '../TokenList' import ChainsList from '../ChainsList' import AmountInput from '../AmountInput' import SkStepper from '../Stepper' import SkPaper from '../SkPaper' import AmountErrorMessage from '../AmountErrorMessage' import SwitchDirection from '../SwitchDirection' -import TransferETF from '../TransferETF' -import TransferETA from '../TransferETA' +import { TokenBalance } from '../TokenList' +import DestTokenBalance from '../DestTokenBalance' import common from '../../styles/common.module.scss' import { cls } from '../../core/helper' @@ -33,6 +32,7 @@ export function WidgetBody(props) { const mpc = useMetaportStore((state) => state.mpc) const tokens = useMetaportStore((state) => state.tokens) const setToken = useMetaportStore((state) => state.setToken) + const tokenBalances = useMetaportStore((state) => state.tokenBalances) const transferInProgress = useMetaportStore((state) => state.transferInProgress) @@ -51,6 +51,16 @@ export function WidgetBody(props) {
+
+

From

+
+ {token ? : null} +
+
+
+

To

+ +
Promise + destTokenContract: Contract + destTokenBalance: bigint + updateDestTokenBalance: (address: string) => Promise + amountErrorMessage: string setAmountErrorMessage: (amountErrorMessage: string) => void @@ -272,15 +278,32 @@ export const useMetaportStore = create()((set, get) => ({ tokens: getEmptyTokenDataMap(), token: null, - setToken: (token: dataclasses.TokenData) => - set(() => ({ + + setToken: async (token: dataclasses.TokenData) => { + const provider = get().chainName2 === MAINNET_CHAIN_NAME ? get().mainnetChain.provider : get().sChain2.provider + const destTokenContract = get().mpc.tokenContract( + get().chainName2, + token.keyname, + token.type, + provider + ) + set({ token: token, stepsMetadata: getStepsMetadata(get().mpc.config, token, get().chainName2), - })), + destTokenContract: destTokenContract + }) + }, tokenContracts: {}, tokenBalances: {}, + destTokenContract: null, + destTokenBalance: null, + updateDestTokenBalance: async (address: string) => { + const balance = await get().mpc.tokenBalance(get().destTokenContract, address) + set({ destTokenBalance: balance }) + }, + updateTokenBalances: async (address: string) => { const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address) set({ tokenBalances: tokenBalances }) diff --git a/src/styles/common.module.scss b/src/styles/common.module.scss index 0cf9d38..15dba77 100644 --- a/src/styles/common.module.scss +++ b/src/styles/common.module.scss @@ -137,6 +137,10 @@ padding-top: 10px !important; } +.paddTop20 { + padding-top: 20px !important; +} + .paddBott10 { padding-bottom: 10px !important; } From b3333d6bf8c51dd9d64625603334f851f0a574c0 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 17 Aug 2023 18:34:14 +0100 Subject: [PATCH 023/110] Fix dest token balance, export proxy endpoints --- src/components/Metaport/Metaport.stories.tsx | 10 +- src/components/WidgetBody/WidgetBody.tsx | 115 +++++++++++------- src/core/network.ts | 2 + src/index.ts | 7 +- ...gStaging.json => metaportConfigStaging.ts} | 47 ++++--- src/store/MetaportState.ts | 40 +++--- tsconfig.json | 2 +- 7 files changed, 139 insertions(+), 84 deletions(-) rename src/metadata/{metaportConfigStaging.json => metaportConfigStaging.ts} (84%) diff --git a/src/components/Metaport/Metaport.stories.tsx b/src/components/Metaport/Metaport.stories.tsx index 2b8f557..34fbb2e 100644 --- a/src/components/Metaport/Metaport.stories.tsx +++ b/src/components/Metaport/Metaport.stories.tsx @@ -1,13 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react'; import Metaport from "./Metaport"; -import * as interfaces from '../../core/interfaces' -// import { storyDecorator } from "../WidgetUI/StorybookHelper"; -import METAPORT_CONFIG from '../../metadata/metaportConfigStaging.json'; -const config = METAPORT_CONFIG as interfaces.MetaportConfig; - -// METAPORT_CONFIG.mainnetEndpoint = process.env.STORYBOOK_MAINNET_ENDPOINT; +import { METAPORT_CONFIG } from '../../metadata/metaportConfigStaging'; +METAPORT_CONFIG.mainnetEndpoint = import.meta.env.VITE_MAINNET_ENDPOINT; const meta: Meta = { title: "Functional/Metaport", @@ -21,6 +17,6 @@ type Story = StoryObj; export const WidgetDemo: Story = { args: { - config: config + config: METAPORT_CONFIG } }; \ No newline at end of file diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index d94d5e8..bdf4005 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -10,6 +10,7 @@ import AmountErrorMessage from '../AmountErrorMessage' import SwitchDirection from '../SwitchDirection' import { TokenBalance } from '../TokenList' import DestTokenBalance from '../DestTokenBalance' +import ErrorMessage from '../ErrorMessage' import common from '../../styles/common.module.scss' import { cls } from '../../core/helper' @@ -22,6 +23,8 @@ export function WidgetBody(props) { const expandedTo = useCollapseStore((state) => state.expandedTo) const setExpandedTo = useCollapseStore((state) => state.setExpandedTo) + const expandedTokens = useCollapseStore((state) => state.expandedTokens) + const token = useMetaportStore((state) => state.token) const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) @@ -34,6 +37,8 @@ export function WidgetBody(props) { const setToken = useMetaportStore((state) => state.setToken) const tokenBalances = useMetaportStore((state) => state.tokenBalances) + const errorMessage = useMetaportStore((state) => state.errorMessage) + const transferInProgress = useMetaportStore((state) => state.transferInProgress) useEffect(() => { @@ -47,61 +52,87 @@ export function WidgetBody(props) { } }, [tokens]); + const showFrom = !expandedTo && !expandedTokens && !errorMessage + const showTo = !expandedFrom && !expandedTokens && !errorMessage + const showInput = !expandedFrom && !expandedTo && !errorMessage + const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage + const showStepper = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage + const showError = !!errorMessage; + return (
+ + + -
-

From

-
- {token ? : null} + +
+

From

+
+ {token ? : null} +
+ +
+ + + + + + + + + + + + + + +
+

To

+
- {/* - - */}
+
- {/* */} - - - - {/* */} - - - -
-

To

- -
- -
- - - + + + + + + +
) } diff --git a/src/core/network.ts b/src/core/network.ts index 545df6c..3f07564 100644 --- a/src/core/network.ts +++ b/src/core/network.ts @@ -29,6 +29,8 @@ import { MAINNET_CHAIN_NAME } from './constants' import { IMA_ADDRESSES, IMA_ABIS } from './contracts' import { SkaleNetwork } from './interfaces' +export { proxyEndpoints as PROXY_ENDPOINTS } + const PROTOCOL: { [protocol in 'http' | 'ws']: string } = { http: 'https://', ws: 'wss://', diff --git a/src/index.ts b/src/index.ts index 36dbe1f..400926d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,7 @@ import TransferETF from './components/TransferETF' import TransferETA from './components/TransferETA' import AmountErrorMessage from './components/AmountErrorMessage' import DestTokenBalance from './components/DestTokenBalance' +import ErrorMessage from './components/ErrorMessage' import { cls } from './core/helper' import styles from './styles/styles.module.scss' @@ -30,6 +31,8 @@ import { getWidgetTheme as getMetaportTheme } from './core/themes'; import { useAccount as useWagmiAccount } from 'wagmi' +import { PROXY_ENDPOINTS } from './core/network' + export { Metaport, MetaportProvider, @@ -47,9 +50,11 @@ export { AmountErrorMessage, TokenBalance, DestTokenBalance, + ErrorMessage, cls, styles, common, getMetaportTheme, - useWagmiAccount + useWagmiAccount, + PROXY_ENDPOINTS } diff --git a/src/metadata/metaportConfigStaging.json b/src/metadata/metaportConfigStaging.ts similarity index 84% rename from src/metadata/metaportConfigStaging.json rename to src/metadata/metaportConfigStaging.ts index eaa3f7a..f0e57a8 100644 --- a/src/metadata/metaportConfigStaging.json +++ b/src/metadata/metaportConfigStaging.ts @@ -1,16 +1,18 @@ -{ +import * as interfaces from '../core/interfaces' + +export const METAPORT_CONFIG: interfaces.MetaportConfig = { "skaleNetwork": "staging", "openOnLoad": true, "openButton": true, "debug": false, "chains": [ "mainnet", - "staging-legal-crazy-castor", - "staging-utter-unripe-menkar", - "staging-faint-slimy-achird", - "staging-perfect-parallel-gacrux", - "staging-severe-violet-wezen", - "staging-weepy-fitting-caph" + "staging-legal-crazy-castor", // Europa + "staging-utter-unripe-menkar", // Calypso + "staging-faint-slimy-achird", // Nebula + "staging-perfect-parallel-gacrux", // Test Chain 1 + "staging-severe-violet-wezen", // Test Chain 2 + "staging-weepy-fitting-caph" // Tank War Zone ], "tokens": { "eth": { @@ -108,7 +110,10 @@ "usdc": { "address": "0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9", "chains": { - "staging-legal-crazy-castor": {} + "staging-legal-crazy-castor": {}, + "staging-utter-unripe-menkar": { + "hub": "staging-legal-crazy-castor" + } } }, "wbtc": { @@ -135,7 +140,7 @@ } } }, - "staging-utter-unripe-menkar": { + "staging-utter-unripe-menkar": { // Calypso connections "erc20": { "skl": { "address": "0x7E1B8750C21AebC3bb2a0bDf40be104C609a9852", @@ -152,6 +157,18 @@ "clone": true } } + }, + "usdc": { + "address": "0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6", + "chains": { + "staging-legal-crazy-castor": { + "clone": true + }, + "mainnet": { + "hub": "staging-legal-crazy-castor", + "clone": true + } + } } } }, @@ -175,7 +192,7 @@ } } }, - "staging-legal-crazy-castor": { + "staging-legal-crazy-castor": { // Europa connections "erc20": { "skl": { "address": "0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6", @@ -228,6 +245,9 @@ "chains": { "mainnet": { "clone": true + }, + "staging-utter-unripe-menkar": { + "wrapper": "0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0" } } }, @@ -242,11 +262,8 @@ } }, "staging-perfect-parallel-gacrux": { - "erc20": { - }, - "erc721": { - - }, + "erc20": {}, + "erc721": {}, "erc1155": { "skaliens": { "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index e2c3ea9..37f75ae 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -193,6 +193,7 @@ export const useMetaportStore = create()((set, get) => ({ tokenId: null, currentStep: 0, transferInProgress: false, + destTokenBalance: null }) }, @@ -254,26 +255,28 @@ export const useMetaportStore = create()((set, get) => ({ chainName1: name, tokens: tokens, tokenContracts: tokenContracts, + tokenBalances: {}, ...updState, } }), - setChainName2: (name: string) => - set((state) => { - const updState = {} - if (name === MAINNET_CHAIN_NAME) { - updState['mainnetChain'] = state.mpc.mainnet() - } else { - updState['sChain2'] = state.mpc.schain(name) - } - return { - currentStep: 0, - token: null, - chainName2: name, - tokens: state.mpc.tokens(state.chainName1, name), - stepsMetadata: getStepsMetadata(get().mpc.config, get().token, name), - ...updState, - } - }), + setChainName2: (name: string) => { + const updState = {} + if (name === MAINNET_CHAIN_NAME) { + updState['mainnetChain'] = get().mpc.mainnet() + } else { + updState['sChain2'] = get().mpc.schain(name) + } + set({ + currentStep: 0, + token: null, + destTokenBalance: null, + destTokenContract: null, + chainName2: name, + tokens: get().mpc.tokens(get().chainName1, name), + stepsMetadata: getStepsMetadata(get().mpc.config, get().token, name), + ...updState, + }) + }, tokens: getEmptyTokenDataMap(), @@ -290,7 +293,8 @@ export const useMetaportStore = create()((set, get) => ({ set({ token: token, stepsMetadata: getStepsMetadata(get().mpc.config, token, get().chainName2), - destTokenContract: destTokenContract + destTokenContract: destTokenContract, + destTokenBalance: null }) }, diff --git a/tsconfig.json b/tsconfig.json index 99dcac8..ad5b433 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "typeRoots": ["./src/types", "./node_modules/@types"], + "typeRoots": ["./src/vite-env.d.ts", "./src/types", "./node_modules/@types"], }, "include": ["./src/**/*.ts", "./src/**/*.tsx"], "exclude": ["./src/**/*.stories.*"] From 90078c7632bae275297048d846f3d7e1a9f6b8b2 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 18 Aug 2023 17:06:14 +0100 Subject: [PATCH 024/110] Update dest chains filtering logic, update config --- src/components/AmountInput/AmountInput.tsx | 49 +------ src/components/ChainsList/ChainsList.tsx | 3 +- src/components/TokenList/TokenList.tsx | 143 ++++++++++----------- src/components/WidgetBody/WidgetBody.tsx | 10 +- src/core/metadata.ts | 2 +- src/metadata/metaportConfigStaging.ts | 27 ++-- src/store/MetaportState.ts | 19 +-- src/styles/styles.module.scss | 2 +- 8 files changed, 108 insertions(+), 147 deletions(-) diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index ad30f9d..fb58d67 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -29,23 +29,6 @@ export default function AmountInput() { setAmount(event.target.value, address) } - // const setMaxAmount = () => { - // if (token && !token.clone && - // (token.wrapsSFuel || token.type === TokenType.eth)) { - // const adjustedAmount = Number(token.balance) - SFUEL_RESERVE_AMOUNT; - // if (adjustedAmount > 0) { - // props.setAmount(adjustedAmount.toString()); - // } - // } else { - // if (token && !token.clone && token.unwrappedBalance) { - // props.setAmount(token.unwrappedBalance); - // } else { - // props.setAmount(token.balance); - // } - // } - // } - - // if (!token) return return (
@@ -63,36 +46,10 @@ export default function AmountInput() { disabled={transferInProgress} />
- } - - - {/*
- {token ? token.meta.symbol : 'ETH'} -
*/} - - {/* {props.maxBtn ?
- -
: null} */} +
+ +
) } diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index 04cf519..7964326 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -22,6 +22,7 @@ export default function ChainsList(props: { setExpanded: (expanded: string | false) => void setChain: (chain: string) => void chain: string + chains: string[] disabledChain: string from?: boolean disabled?: boolean @@ -32,7 +33,7 @@ export default function ChainsList(props: { const schainNames = [] - for (let chain of props.config.chains) { + for (let chain of props.chains) { if (chain != props.disabledChain && chain != props.chain) { schainNames.push(chain) } diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index a5da607..c82f659 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -68,93 +68,88 @@ export default function TokenList() { setExpandedTokens(isExpanded ? panel : false) } - // if (noTokens) { - // return - // } - let tokensText = token ? token.meta.symbol : 'TOKEN'; if (noTokens) { - tokensText = 'ETH' + tokensText = 'N/A' } return ( -
- + } + aria-controls="panel1bh-content" + id="panel1bh-header" + className={styles.accordionSummaryTokens} + > - } - aria-controls="panel1bh-content" - id="panel1bh-header" - className={styles.accordionSummaryTokens} - - > -
-
- -
-

- {tokensText} -

- - {/*
+
+
+ +
+

+ {tokensText} +

+ + {/*
{token ? : null}
*/} -
- +
+ - {expandedTokens ? - {/* + {/* */} - - - - - : null} - - -
+ + + + + : null} + +
) } diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index bdf4005..30db64b 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -25,6 +25,8 @@ export function WidgetBody(props) { const expandedTokens = useCollapseStore((state) => state.expandedTokens) + const destChains = useMetaportStore((state) => state.destChains) + const token = useMetaportStore((state) => state.token) const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) @@ -47,7 +49,7 @@ export function WidgetBody(props) { }, []); useEffect(() => { - if (tokens && tokens.erc20) { + if (tokens && tokens.erc20 && Object.values(tokens.erc20)[0]) { setToken(Object.values(tokens.erc20)[0]) } }, [tokens]); @@ -89,6 +91,7 @@ export function WidgetBody(props) { expanded={expandedFrom} setExpanded={setExpandedFrom} chain={chainName1} + chains={props.config.chains} setChain={setChainName1} disabledChain={chainName2} disabled={transferInProgress} @@ -118,16 +121,15 @@ export function WidgetBody(props) { expanded={expandedTo} setExpanded={setExpandedTo} chain={chainName2} + chains={destChains} setChain={setChainName2} disabledChain={chainName1} disabled={transferInProgress} /> - - - + diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 428da54..2684b85 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -32,7 +32,7 @@ import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons' import * as icons from '../icons' -// const icons = { eth: { default: '' } }; // TODO: storybook fix +// const icons = { eth: { default: '' }, skl: { default: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIj48Y2lyY2xlIGZpbGw9IiMwMDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPjxnIGZpbGw9IiNGRkYiPjxwYXRoIGQ9Ik0yMi41MTQgOC40OTJ2Ljk5MUg5LjgxdjEzLjAzNGgxMi43MDRWMjQuNWwtNy40Mi0uMDU3LTcuNDUtLjA4NS0uMDg2LTguNDQzTDcuNSA3LjVoMTUuMDE0eiIvPjxwYXRoIGQ9Ik0yMy42OTggMTAuOWMxLjEyNi4zMTIgMi4xMDggMS4xOSAyLjQyNSAyLjE4Mi4xNzMuNTk1LjA4Ny42NTEtLjkyNC42NTEtLjc4IDAtMS4yMTItLjE3LTEuNDcyLS41NjYtLjQzMy0uNzA5LTIuMzk3LS43OTQtMi45NzQtLjExNC0uNjM1Ljc2NS4wNTggMS4zMzIgMi4zMSAxLjg0MiAxLjEyNi4yNTUgMi4zMS42OCAyLjYyNy45NjMgMS40NDQgMS4yNzUuODY2IDQuMDgtMS4wMSA0Ljg0NS0xLjI3LjUxLTMuMzUuNTEtNC42MiAwLS44NjYtLjM2OC0xLjg3Ny0xLjY0My0xLjg3Ny0yLjQzNiAwLS41MSAxLjg3Ny0uMzEyIDIuMzY4LjI4MyAxLjA0IDEuMTYyIDMuNDY0Ljk5MiAzLjYzOC0uMjU1LjE0NC0uOTYzLS40MDUtMS4zODgtMi4wNS0xLjU4Ny0yLjY4NS0uMzY4LTMuNjY3LTEuMTktMy42NjctMy4wNiAwLTIuMjEgMi40MjUtMy41MTMgNS4yMjYtMi43NDh6Ii8+PC9nPjwvZz48L3N2Zz4=' } }; // TODO: storybook fix const CHAIN_ICONS = { mainnet: MAINNET_CHAIN_ICONS, diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index f0e57a8..a65c6fd 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -261,22 +261,25 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { } } }, + "staging-severe-violet-wezen": { + "erc20": {} + }, "staging-perfect-parallel-gacrux": { "erc20": {}, "erc721": {}, "erc1155": { - "skaliens": { - "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", - "chains": {} - }, - "medals": { - "address": "0x5D8bD602dC5468B3998e8514A1851bd5888E9639", - "chains": {} - }, - "_ANIMALS_0xDf87EEF0977148129969b01b329379b17756cdDE": { - "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", - "chains": {} - } + // "skaliens": { + // "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", + // "chains": {} + // }, + // "medals": { + // "address": "0x5D8bD602dC5468B3998e8514A1851bd5888E9639", + // "chains": {} + // }, + // "_ANIMALS_0xDf87EEF0977148129969b01b329379b17756cdDE": { + // "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", + // "chains": {} + // } } } }, diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 37f75ae..02aa95d 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -69,6 +69,8 @@ interface MetaportState { chainName1: string chainName2: string + destChains: string[] + setChainName1: (name: string) => void setChainName2: (name: string) => void @@ -233,13 +235,10 @@ export const useMetaportStore = create()((set, get) => ({ chainName1: '', chainName2: '', + destChains: [], + setChainName1: (name: string) => set((state) => { - // updateState( - // name, - // state.chainName2 - // ) - const updState = {} if (name === MAINNET_CHAIN_NAME) { updState['mainnetChain'] = state.mpc.mainnet() @@ -256,6 +255,7 @@ export const useMetaportStore = create()((set, get) => ({ tokens: tokens, tokenContracts: tokenContracts, tokenBalances: {}, + destChains: get().mpc.config.chains, ...updState, } }), @@ -294,7 +294,8 @@ export const useMetaportStore = create()((set, get) => ({ token: token, stepsMetadata: getStepsMetadata(get().mpc.config, token, get().chainName2), destTokenContract: destTokenContract, - destTokenBalance: null + destTokenBalance: null, + destChains: Object.keys(token.connections) }) }, @@ -304,8 +305,10 @@ export const useMetaportStore = create()((set, get) => ({ destTokenContract: null, destTokenBalance: null, updateDestTokenBalance: async (address: string) => { - const balance = await get().mpc.tokenBalance(get().destTokenContract, address) - set({ destTokenBalance: balance }) + if (get().destTokenContract) { + const balance = await get().mpc.tokenBalance(get().destTokenContract, address) + set({ destTokenBalance: balance }) + } }, updateTokenBalances: async (address: string) => { diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index 9cc7238..d47c11b 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -15,7 +15,7 @@ } .popper { - width: 390px; + max-width: 390px; max-height: calc(100vh - 110pt); padding: 10pt; border-radius: $sk-border-radius-outter; From c2546e48da3c8629541d7aa736cd41b1126bb7d8 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 22 Aug 2023 13:28:35 +0100 Subject: [PATCH 025/110] Update styling system, add community pool management --- package.json | 2 +- .../AmountErrorMessage/AmountErrorMessage.tsx | 20 +- src/components/AmountInput/AmountInput.tsx | 35 +- src/components/ChainApps/ChainApps.tsx | 21 +- src/components/ChainsList/ChainsList.tsx | 62 +-- .../CommunityPool/CommunityPool.tsx | 238 +++++++++ src/components/CommunityPool/index.ts | 1 + .../DestTokenBalance/DestTokenBalance.tsx | 10 +- src/components/ErrorMessage/ErrorMessage.tsx | 42 +- src/components/Metaport/Metaport.stories.tsx | 24 +- .../MetaportProvider/MetaportProvider.tsx | 4 +- src/components/SkConnect/SkConnect.tsx | 48 +- src/components/SkPaper/SkPaper.tsx | 4 +- src/components/Stepper/SkStepper.tsx | 163 +++--- .../SwitchDirection/SwitchDirection.tsx | 12 +- src/components/TokenList/TokenBalance.tsx | 52 +- src/components/TokenList/TokenList.tsx | 93 ++-- .../TokenListSection/TokenListSection.tsx | 37 +- src/components/TransferETA/TransferETA.tsx | 57 ++- src/components/TransferETF/TransferETF.tsx | 53 +- src/components/WidgetBody/WidgetBody.tsx | 67 +-- src/components/WidgetUI/WidgetUI.tsx | 16 +- src/core/actions/action.ts | 6 - src/core/community_pool.ts | 229 ++++++--- src/core/constants.ts | 2 +- src/core/interfaces/CommunityPoolData.ts | 8 +- src/core/metadata.ts | 10 +- src/index.ts | 10 +- src/metadata/metaportConfigStaging.ts | 464 +++++++++--------- src/store/CommunityPoolStore.ts | 72 +++ src/store/MetaportState.ts | 15 +- src/store/Store.ts | 14 + .../{common.module.scss => cmn.module.scss} | 96 +--- src/styles/common.module.scss.d.ts | 55 --- src/styles/styles.module.scss | 18 + 35 files changed, 1150 insertions(+), 910 deletions(-) create mode 100644 src/components/CommunityPool/CommunityPool.tsx create mode 100644 src/components/CommunityPool/index.ts create mode 100644 src/store/CommunityPoolStore.ts rename src/styles/{common.module.scss => cmn.module.scss} (70%) delete mode 100644 src/styles/common.module.scss.d.ts diff --git a/package.json b/package.json index a3477ad..579c0f6 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@mui/lab": "^5.0.0-alpha.88", "@mui/material": "^5.8.1", "@rainbow-me/rainbowkit": "^1.0.8", - "@skalenetwork/ima-js": "2.0.0-experimental.1", + "@skalenetwork/ima-js": "2.0.0-develop.3", "@skaleproject/pow-ethers": "0.3.2", "coingecko-api-v3": "^0.0.28", "react-jazzicon": "^1.0.4", diff --git a/src/components/AmountErrorMessage/AmountErrorMessage.tsx b/src/components/AmountErrorMessage/AmountErrorMessage.tsx index e6315f3..b0ed862 100644 --- a/src/components/AmountErrorMessage/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage/AmountErrorMessage.tsx @@ -2,7 +2,7 @@ import React from 'react' import Collapse from '@mui/material/Collapse' import { cls } from '../../core/helper' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import { useMetaportStore } from '../../store/MetaportState' @@ -12,15 +12,15 @@ export default function AmountErrorMessage() {

🔴 {amountErrorMessage} diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index fb58d67..1318898 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -4,14 +4,13 @@ import { useAccount } from 'wagmi' import TextField from '@mui/material/TextField' import { cls } from '../../core/helper' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import localStyles from './AmountInput.module.scss' import TokenList from '../TokenList' import { useMetaportStore } from '../../store/MetaportState' import { useCollapseStore } from '../../store/Store' - export default function AmountInput() { const { address } = useAccount() @@ -30,24 +29,20 @@ export default function AmountInput() { } return ( -

- - {expandedTokens ? null :
- -
- } -
+
+ {expandedTokens ? null : ( +
+ +
+ )} +
diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index ba9356a..ea454c9 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -2,7 +2,7 @@ import React from 'react' import { cls, getChainAppsMeta, getChainAlias } from '../../core/helper' import styles from '../../styles/styles.module.scss' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import { SkaleNetwork } from '../../core/interfaces' import ChainIcon from '../ChainIcon' @@ -12,27 +12,18 @@ export default function ChainApps(props: { skaleNetwork: SkaleNetwork; chain: st if (!apps || !Object.keys(apps) || Object.keys(apps).length === 0) return
return ( -
-
+
+
{Object.keys(apps).map((key, _) => ( -
+
-

+

{getChainAlias(props.skaleNetwork, props.chain, key)}

diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index 7964326..7218df9 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -13,7 +13,7 @@ import ChainIcon from '../ChainIcon' import { MetaportConfig } from '../../core/interfaces' import { cls, getChainAlias } from '../../core/helper' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import styles from '../../styles/styles.module.scss' export default function ChainsList(props: { @@ -59,17 +59,17 @@ export default function ChainsList(props: { className={styles.accordionSummary} > {props.chain ? ( -
-
+
+
-

+

{getChainAlias(props.config.skaleNetwork, props.chain)}

-
- {/*
+
+ {/*
*/}
) : ( -
-
+
+
-

+

Transfer {props.from ? 'from' : 'to'}...

)} -
+
{schainNames.map((name) => ( - -
+
diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx new file mode 100644 index 0000000..ad43b12 --- /dev/null +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -0,0 +1,238 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file CommunityPool.ts + * @copyright SKALE Labs 2023-Present + */ + +import React, { useEffect } from 'react' + +import { useAccount, useWalletClient, useSwitchNetwork } from 'wagmi' + +import Accordion from '@mui/material/Accordion' +import AccordionSummary from '@mui/material/AccordionSummary' +import AccordionDetails from '@mui/material/AccordionDetails' +import Grid from '@mui/material/Grid' +import TextField from '@mui/material/TextField' + +import localStyles from '../AmountInput/AmountInput.module.scss' + +import SkPaper from '../SkPaper/SkPaper' +import { TokenBalance } from '../TokenList' + +import Button from '@mui/material/Button' + +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' + +import CheckCircleIcon from '@mui/icons-material/CheckCircle' +import ErrorIcon from '@mui/icons-material/Error' + +import { fromWei } from '../../core/convertation' +import { withdraw } from '../../core/community_pool' +import { DEFAULT_ERC20_DECIMALS } from '../../core/constants' + +import { cls } from '../../core/helper' +import cmn from '../../styles/cmn.module.scss' +import styles from '../../styles/styles.module.scss' + +import { useCPStore } from '../../store/CommunityPoolStore' +import { useCollapseStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' + +// import { getChainIcon } from '../ChainsList/helper'; + +export default function CommunityPool(props: { + // communityPoolData: CommunityPoolData + // loading: string | false + // rechargeAmount: string + // setRechargeAmount: (amount: string) => {} + // expanded: string | false + // setExpanded: (expanded: string | false) => {} + // recharge: () => {} + // withdraw: () => {} + // marg: boolean +}) { + const { data: walletClient } = useWalletClient() + const { switchNetworkAsync } = useSwitchNetwork() + + const cpData = useCPStore((state) => state.cpData) + const loading = useCPStore((state) => state.loading) + const setLoading = useCPStore((state) => state.setLoading) + const amount = useCPStore((state) => state.amount) + const setAmount = useCPStore((state) => state.setAmount) + const updateCPData = useCPStore((state) => state.updateCPData) + + const chainName1 = useMetaportStore((state) => state.chainName1) + const chainName2 = useMetaportStore((state) => state.chainName2) + const mpc = useMetaportStore((state) => state.mpc) + const setErrorMessage = useMetaportStore((state) => state.setErrorMessage) + + const expandedCP = useCollapseStore((state) => state.expandedCP) + const setExpandedCP = useCollapseStore((state) => state.setExpandedCP) + + const { address } = useAccount() + + const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { + setExpandedCP(isExpanded ? panel : false) + } + + const handleAmountChange = (event: React.ChangeEvent) => { + if (parseFloat(event.target.value) < 0) { + setAmount('') + return + } + setAmount(event.target.value) + } + + useEffect(() => { + updateCPData(address, chainName1, chainName2, mpc) + const intervalId = setInterval(() => { + updateCPData(address, chainName1, chainName2, mpc) + }, 10000) // Fetch users every 10 seconds + + return () => { + clearInterval(intervalId) // Clear interval on component unmount + } + }, [chainName1, chainName2, address]) + + const text = cpData.exitGasOk ? 'Exit gas wallet OK' : 'Recharge exit gas wallet' + const icon = cpData.exitGasOk ? : + const accountBalanceEther = cpData.accountBalance ? fromWei(cpData.accountBalance, DEFAULT_ERC20_DECIMALS) : null + + function getRechargeBtnText() { + if (loading === 'recharge') return 'Recharging...' + if (loading === 'activate') return 'Activating account...' + if (Number(amount) > Number(accountBalanceEther)) return 'Insufficient ETH balance' + if (amount === '' || amount === '0' || !amount) return 'Enter an amount' + return 'Recharge exit gas wallet' + } + + function getWithdrawBtnText() { + if (loading === 'withdraw') return 'Withdrawing...' + return 'Withdraw all' + } + + function withdrawCP() { + withdraw( + mpc, + walletClient, + chainName1, + cpData.balance, + address, + switchNetworkAsync, + setLoading, + setErrorMessage, + async () => { + setLoading(false) + setErrorMessage(null) + }, + ) + } + + return ( +
+ + } + aria-controls="panel1a-content" + id="panel1a-header" + > +
+
{icon}
+

{text}

+
+
+ + +

+ This wallet is used to pay for Ethereum gas fees from your transactions to the Ethereum Mainnet. You may + withdraw funds from your SKALE Gas Wallet at anytime. +

+
+

ETH Balance

+
+ +
+
+
+

Exit wallet Balance

+
+ +
+
+ + + +
+
+ +
+

+ ETH +

+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ ) +} diff --git a/src/components/CommunityPool/index.ts b/src/components/CommunityPool/index.ts new file mode 100644 index 0000000..759a246 --- /dev/null +++ b/src/components/CommunityPool/index.ts @@ -0,0 +1 @@ +export { default } from './CommunityPool' diff --git a/src/components/DestTokenBalance/DestTokenBalance.tsx b/src/components/DestTokenBalance/DestTokenBalance.tsx index 56906be..6d46d52 100644 --- a/src/components/DestTokenBalance/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance/DestTokenBalance.tsx @@ -4,9 +4,7 @@ import { useAccount } from 'wagmi' import { TokenBalance } from '../TokenList' import { useMetaportStore } from '../../store/MetaportState' - export default function DestTokenBalance() { - const { address } = useAccount() const token = useMetaportStore((state) => state.token) @@ -23,11 +21,7 @@ export default function DestTokenBalance() { } }, [updateDestTokenBalance, token, address]) - if (!token) return; + if (!token) return - return + return } diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index 3305422..8806ea2 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -2,7 +2,7 @@ import React from 'react' import Button from '@mui/material/Button' import { cls } from '../../core/helper' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import styles from '../../styles/styles.module.scss' import { ErrorMessage } from '../../core/dataclasses' @@ -23,48 +23,20 @@ export default function Error(props: { errorMessage: ErrorMessage }) { if (!props.errorMessage) return return (
-
- {ERROR_ICONS[props.errorMessage.icon]} -
+
{ERROR_ICONS[props.errorMessage.icon]}

Error occured

-

+

Please check logs in developer console

-
+

{props.errorMessage.text}

@@ -75,7 +47,7 @@ export default function Error(props: { errorMessage: ErrorMessage }) { variant="contained" color="primary" size="medium" - className={cls(styles.btnAction, common.margTop5)} + className={cls(styles.btnAction, cmn.mtop5)} onClick={() => { props.errorMessage.fallback() }} diff --git a/src/components/Metaport/Metaport.stories.tsx b/src/components/Metaport/Metaport.stories.tsx index 34fbb2e..04c5abd 100644 --- a/src/components/Metaport/Metaport.stories.tsx +++ b/src/components/Metaport/Metaport.stories.tsx @@ -1,22 +1,20 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import Metaport from "./Metaport"; +import type { Meta, StoryObj } from '@storybook/react' +import Metaport from './Metaport' - -import { METAPORT_CONFIG } from '../../metadata/metaportConfigStaging'; -METAPORT_CONFIG.mainnetEndpoint = import.meta.env.VITE_MAINNET_ENDPOINT; +import { METAPORT_CONFIG } from '../../metadata/metaportConfigStaging' +METAPORT_CONFIG.mainnetEndpoint = import.meta.env.VITE_MAINNET_ENDPOINT const meta: Meta = { - title: "Functional/Metaport", + title: 'Functional/Metaport', component: Metaport, // decorators: [storyDecorator], -}; - -export default meta; -type Story = StoryObj; +} +export default meta +type Story = StoryObj export const WidgetDemo: Story = { args: { - config: METAPORT_CONFIG - } -}; \ No newline at end of file + config: METAPORT_CONFIG, + }, +} diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index 54af185..108458c 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -49,7 +49,7 @@ import { useMetaportStore } from '../../store/MetaportState' import MetaportCore from '../../core/metaport' import styles from '../../styles/styles.module.scss' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' const { chains, webSocketPublicClient } = configureChains( [ @@ -106,7 +106,7 @@ export default function MetaportProvider(props: { const metaportTheme = useUIStore((state) => state.theme) const themeCls = widgetTheme.mode === 'dark' ? styles.darkTheme : styles.lightTheme - const commonThemeCls = widgetTheme.mode === 'dark' ? common.darkTheme : common.lightTheme + const commonThemeCls = widgetTheme.mode === 'dark' ? cmn.darkTheme : cmn.lightTheme useEffect(() => { setOpen(props.config.openOnLoad) diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index 50df7f7..f7151e8 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -30,7 +30,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { cls } from '../../core/helper' import styles from '../../styles/styles.module.scss' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import skaleLogoFull from '../WidgetUI/skale_logo.svg' import { useMetaportStore } from '../../store/MetaportState' @@ -62,79 +62,69 @@ export default function SkConnect() { if (!connected) { return (
-
+
-
+
-

+

Connect a wallet to use SKALE Metaport

- )} -
- - - - ))} - - +
+ + + +

{step.text}

+
+ {loading ? ( + + {btnText} + {/* {props.loadingTokens ? 'Loading...' : step.btnLoadingText} */} + + ) : ( + + )} +
+
+
+ + ))} + + - {currentStep === stepsMetadata.length && ( -
-
-

- {emoji} Transfer completed -

-
- + {currentStep === stepsMetadata.length && ( +
+
+

+ {emoji} Transfer completed +

- )} - + +
+ )} + ) } diff --git a/src/components/SwitchDirection/SwitchDirection.tsx b/src/components/SwitchDirection/SwitchDirection.tsx index 6d74d50..93be651 100644 --- a/src/components/SwitchDirection/SwitchDirection.tsx +++ b/src/components/SwitchDirection/SwitchDirection.tsx @@ -3,7 +3,7 @@ import React, { useRef } from 'react' import IconButton from '@mui/material/IconButton' import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded' import styles from '../../styles/styles.module.scss' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import { cls } from '../../core/helper' import { useUIStore } from '../../store/Store' @@ -24,15 +24,15 @@ export default function SwitchDirection() { const transferInProgress = useMetaportStore((state) => state.transferInProgress) return ( -
-
+
+
-
+
) } diff --git a/src/components/TokenList/TokenBalance.tsx b/src/components/TokenList/TokenBalance.tsx index 9d95780..be28657 100644 --- a/src/components/TokenList/TokenBalance.tsx +++ b/src/components/TokenList/TokenBalance.tsx @@ -1,25 +1,49 @@ import React from 'react' import { formatUnits } from 'ethers' import { cls } from '../../core/helper' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' +import { DEFAULT_ERC20_DECIMALS } from '../../core/constants' -function formatBalance(balance: bigint, decimals: string): string { - return formatUnits(balance, parseInt(decimals)) +function formatBalance(balance: bigint, decimals?: string): string { + const tokenDecimals = decimals ?? DEFAULT_ERC20_DECIMALS + return formatUnits(balance, parseInt(tokenDecimals)) } -export default function TokenBalance(props: { balance: bigint, decimals: string, symbol: string }) { +function truncateDecimals(input: string, numDecimals: number): string { + const delimiter = input.includes(',') ? ',' : '.' + const [integerPart, decimalPart = ''] = input.split(delimiter) + return `${integerPart}${delimiter}${decimalPart.slice(0, numDecimals)}` +} + +export default function TokenBalance(props: { + balance: bigint + symbol: string + decimals?: string + truncate?: number + primary?: boolean + size?: 'xs' | 'sm' +}) { if (props.balance === undefined || props.balance === null) return + let balance = formatBalance(props.balance, props.decimals) + if (props.truncate) { + balance = truncateDecimals(balance, props.truncate) + } + let size = props.size ?? 'xs' return ( -
-

- {formatBalance(props.balance, props.decimals)} {props.symbol} +

+

+ {balance} {props.symbol}

) diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index c82f659..2350f8c 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -20,7 +20,7 @@ import TokenBalance from './TokenBalance' import TokenIcon from '../TokenIcon' import styles from '../../styles/styles.module.scss' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import { getTokenName } from '../../core/metadata' import { useCollapseStore } from '../../store/Store' @@ -68,7 +68,7 @@ export default function TokenList() { setExpandedTokens(isExpanded ? panel : false) } - let tokensText = token ? token.meta.symbol : 'TOKEN'; + let tokensText = token ? token.meta.symbol : 'TOKEN' if (noTokens) { tokensText = 'N/A' } @@ -79,77 +79,74 @@ export default function TokenList() { onChange={handleChange('panel1')} disabled={disabled || transferInProgress || noTokens} elevation={0} - className={common.fullWidth} + className={cmn.fullWidth} > } aria-controls="panel1bh-content" id="panel1bh-header" className={styles.accordionSummaryTokens} - > -
-
- +
+
+

{tokensText}

- {/*
+ {/*
{token ? : null}
*/}
- {expandedTokens ? - {/* + {/* */} - - - - - : null} - + + + + + + ) : null} ) } diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index 3001eb2..5698796 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -8,7 +8,7 @@ import { cls } from '../../core/helper' import TokenBalance from '../TokenList/TokenBalance' import TokenIcon from '../TokenIcon' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import { getTokenName } from '../../core/metadata' @@ -27,17 +27,9 @@ export default function TokenListSection(props: { if (Object.keys(props.tokens).length === 0) return return ( -
+

{props.type} @@ -47,30 +39,17 @@ export default function TokenListSection(props: { key={key} color="secondary" size="small" - className={common.fullWidth} + className={cmn.fullWidth} onClick={() => handle(props.tokens[key])} > -

-
+
+
-

+

{getTokenName(props.tokens[key])}

-
+
(); - const [isLoaded, setIsLoaded] = React.useState(false); +export default function TransferETA(props: { token: TokenData; toChain: string }) { + const [eta, setEta] = React.useState() + const [isLoaded, setIsLoaded] = React.useState(false) async function calcETA() { - setIsLoaded(false); - let baseETA = 0; - const fromMainnet = isMainnet(props.token.chain); - const toMainnet = isMainnet(props.toChain); - baseETA += fromMainnet || toMainnet ? IMA_M2S_WAIT : IMA_S2S_WAIT; + setIsLoaded(false) + let baseETA = 0 + const fromMainnet = isMainnet(props.token.chain) + const toMainnet = isMainnet(props.toChain) + baseETA += fromMainnet || toMainnet ? IMA_M2S_WAIT : IMA_S2S_WAIT if (props.token.connections[props.toChain] && props.token.connections[props.toChain].hub) { - baseETA += IMA_HUB_WAIT; + baseETA += IMA_HUB_WAIT } setEta(baseETA) - setIsLoaded(true); + setIsLoaded(true) } useEffect(() => { - if (props.token && props.toChain) calcETA(); - }, [props.token, props.toChain]); + if (props.token && props.toChain) calcETA() + }, [props.token, props.toChain]) - const tooltipText = 'Estimated transfer time (may vary depending on the network load)'; + const tooltipText = 'Estimated transfer time (may vary depending on the network load)' return (
-
-

- ETA -

- +
+

ETA

+
{isLoaded ? ( -

+

~{eta}-{eta + 1} min -

) : ( +

+ ) : ( )}
) -} \ No newline at end of file +} diff --git a/src/components/TransferETF/TransferETF.tsx b/src/components/TransferETF/TransferETF.tsx index 7f2fdb7..bcc7345 100644 --- a/src/components/TransferETF/TransferETF.tsx +++ b/src/components/TransferETF/TransferETF.tsx @@ -1,56 +1,53 @@ -import React, { useEffect } from 'react'; -import Tooltip from '@mui/material/Tooltip'; -import InfoIcon from '@mui/icons-material/Info'; -import Skeleton from '@mui/material/Skeleton'; +import React, { useEffect } from 'react' +import Tooltip from '@mui/material/Tooltip' +import InfoIcon from '@mui/icons-material/Info' +import Skeleton from '@mui/material/Skeleton' -import { isMainnet, cls } from '../../core/helper'; +import { isMainnet, cls } from '../../core/helper' // import { getTransactionFee } from '../../core/fee_calculator'; -import common from '../../styles/common.module.scss'; - +import cmn from '../../styles/cmn.module.scss' function roundDown(number, decimals) { - decimals = decimals || 0; - return (Math.floor(number * Math.pow(10, decimals)) / Math.pow(10, decimals)); + decimals = decimals || 0 + return Math.floor(number * Math.pow(10, decimals)) / Math.pow(10, decimals) } - export default function TransferETF(props: { fromChain: string }) { - const [etf, setEtf] = React.useState(); - const [isLoaded, setIsLoaded] = React.useState(false); + const [etf, setEtf] = React.useState() + const [isLoaded, setIsLoaded] = React.useState(false) async function calcETF() { - setIsLoaded(false); - const fromMainnet = isMainnet(props.fromChain); - let baseETF = 0; + setIsLoaded(false) + const fromMainnet = isMainnet(props.fromChain) + let baseETF = 0 // if (fromMainnet) baseETF = await getTransactionFee(); if (fromMainnet) baseETF = 2.5 setEtf(baseETF) - setIsLoaded(true); + setIsLoaded(true) } useEffect(() => { - if (props.fromChain) calcETF(); - }, [props.fromChain]); + if (props.fromChain) calcETF() + }, [props.fromChain]) - const tooltipText = 'Estimated transaction fee (You pay only for Mainnet transactions, all transfers within SKALE are free)'; - const etfText = (etf === 0) ? 'Free' : `~${roundDown(etf, 3)} USD` + const tooltipText = + 'Estimated transaction fee (You pay only for Mainnet transactions, all transfers within SKALE are free)' + const etfText = etf === 0 ? 'Free' : `~${roundDown(etf, 3)} USD` return (
-
-

- Estimated Transaction Fee -

- +
+

Estimated Transaction Fee

+
{isLoaded ? ( -

- {etfText}

) : ( +

{etfText}

+ ) : ( )}
) -} \ No newline at end of file +} diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 30db64b..87d7829 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -11,10 +11,12 @@ import SwitchDirection from '../SwitchDirection' import { TokenBalance } from '../TokenList' import DestTokenBalance from '../DestTokenBalance' import ErrorMessage from '../ErrorMessage' +import CommunityPool from '../CommunityPool' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import { cls } from '../../core/helper' import { Collapse } from '@mui/material' +import { MAINNET_CHAIN_NAME } from '../../core/constants' export function WidgetBody(props) { const expandedFrom = useCollapseStore((state) => state.expandedFrom) @@ -23,6 +25,7 @@ export function WidgetBody(props) { const expandedTo = useCollapseStore((state) => state.expandedTo) const setExpandedTo = useCollapseStore((state) => state.setExpandedTo) + const expandedCP = useCollapseStore((state) => state.expandedCP) const expandedTokens = useCollapseStore((state) => state.expandedTokens) const destChains = useMetaportStore((state) => state.destChains) @@ -46,44 +49,40 @@ export function WidgetBody(props) { useEffect(() => { setChainName1(mpc.config.chains ? mpc.config.chains[0] : '') setChainName2(mpc.config.chains ? mpc.config.chains[1] : '') - }, []); + }, []) useEffect(() => { if (tokens && tokens.erc20 && Object.values(tokens.erc20)[0]) { setToken(Object.values(tokens.erc20)[0]) } - }, [tokens]); + }, [tokens]) - const showFrom = !expandedTo && !expandedTokens && !errorMessage - const showTo = !expandedFrom && !expandedTokens && !errorMessage - const showInput = !expandedFrom && !expandedTo && !errorMessage - const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage - const showStepper = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage - const showError = !!errorMessage; + const showFrom = !expandedTo && !expandedTokens && !errorMessage && !expandedCP + const showTo = !expandedFrom && !expandedTokens && !errorMessage && !expandedCP + const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP + const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP + const showStepper = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP + const showCP = !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME + const showError = !!errorMessage return (
- - + + -
-

From

+
+

From

- {token ? : null} + {token ? ( + + ) : null}
- + - -
-

To

+ +
+

To

- + + + + + + + + -
) } diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index 745cf35..f96813d 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -40,7 +40,7 @@ import WidgetBody from '../WidgetBody' import { cls } from '../../core/helper' import styles from '../../styles/styles.module.scss' -import common from '../../styles/common.module.scss' +import cmn from '../../styles/cmn.module.scss' import SkConnect from '../SkConnect' import ErrorMessage from '../ErrorMessage' @@ -67,9 +67,9 @@ export function WidgetUI(props: { config: MetaportConfig }) { } const fabButton = ( -
-
-
+
+
+
-
{fabTop ? fabButton : null}
+
{fabTop ? fabButton : null}
- {address ? :
}
+ + {address ? :
} +
-
{fabTop ? null : fabButton}
+
{fabTop ? null : fabButton}
) } diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index 353f1bc..20a43a0 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -140,12 +140,6 @@ export class Action { this.originAddress = this.mpc.originAddress(chainName1, chainName2, token.keyname, token.type) - console.log('----') - console.log(this.chainName2) - console.log(token) - console.log(token.wrapper(this.chainName2)) - console.log('----') - if (this.token.wrapper(this.chainName2)) { this.unwrappedToken = mpc.tokenContract(chainName1, token.keyname, token.type, provider1) } diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index eed6df1..809014c 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -21,81 +21,176 @@ * @copyright SKALE Labs 2023-Present */ -// import debug from 'debug'; -// import { MainnetChain, SChain } from '@skalenetwork/ima-js'; +import debug from 'debug' +import { ethers } from 'ethers' +import { MainnetChain, SChain } from '@skalenetwork/ima-js' -// import { CommunityPoolData } from './interfaces'; -// import { fromWei } from './convertation'; -// import { -// MAINNET_CHAIN_NAME, -// DEFAULT_ERC20_DECIMALS, -// RECHARGE_MULTIPLIER, -// MINIMUM_RECHARGE_AMOUNT -// } from './constants'; +import { WalletClient } from 'viem' +import { Chain } from '@wagmi/core' -// debug.enable('*'); -// const log = debug('metaport:core:community_pool'); +import { CommunityPoolData } from './interfaces' +import { fromWei } from './convertation' +import { walletClientToSigner } from './ethers' +import { + MAINNET_CHAIN_NAME, + DEFAULT_ERC20_DECIMALS, + RECHARGE_MULTIPLIER, + MINIMUM_RECHARGE_AMOUNT, + COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, + DEFAULT_ERROR_MSG, +} from './constants' +import { CHAIN_IDS, isMainnetChainId, getMainnetAbi } from './network' +import MetaportCore from './metaport' -// export function getEmptyCommunityPoolData(): CommunityPoolData { -// return { -// exitGasOk: null, -// isActive: null, -// balance: null, -// accountBalance: null, -// recommendedRechargeAmount: null, -// originalRecommendedRechargeAmount: null -// }; -// } +import * as dataclasses from '../core/dataclasses' -// export async function getCommunityPoolData( -// address: string, -// chainName1: string, -// chainName2: string, -// mainnet: MainnetChain, -// sChain: SChain -// ): Promise { +debug.enable('*') +const log = debug('metaport:core:community_pool') -// if (chainName2 !== MAINNET_CHAIN_NAME) { -// log('not a S2M transfer, skipping community pool check'); -// return { -// exitGasOk: true, -// isActive: null, -// balance: null, -// accountBalance: null, -// recommendedRechargeAmount: null, -// originalRecommendedRechargeAmount: null -// } -// } +export function getEmptyCommunityPoolData(): CommunityPoolData { + return { + exitGasOk: null, + isActive: null, + balance: null, + accountBalance: null, + recommendedRechargeAmount: null, + originalRecommendedRechargeAmount: null, + } +} + +export async function getCommunityPoolData( + address: string, + chainName1: string, + chainName2: string, + mainnet: MainnetChain, + sChain: SChain, +): Promise { + if (chainName2 !== MAINNET_CHAIN_NAME) { + log('not a S2M transfer, skipping community pool check') + return { + exitGasOk: true, + isActive: null, + balance: null, + accountBalance: null, + recommendedRechargeAmount: null, + originalRecommendedRechargeAmount: null, + } + } -// log('Getting community pool data', address, chainName1); -// const balanceWei = await mainnet.communityPool.balance(address, chainName1); -// const accountBalanceWei = await mainnet.ethBalance(address); -// const activeS = await sChain.communityLocker.contract.activeUsers( -// address -// ) -// const chainHash = mainnet.web3.utils.soliditySha3(chainName1); -// const activeM = await mainnet.communityPool.contract.activeUsers( -// address, -// chainHash -// ) + log('Getting community pool data', address, chainName1) + const balanceWei = await mainnet.communityPool.balance(address, chainName1) + const accountBalanceWei = await mainnet.ethBalance(address) + const activeS = await sChain.communityLocker.contract.activeUsers(address) + const chainHash = ethers.id(chainName1) + const activeM = await mainnet.communityPool.contract.activeUsers(address, chainHash) -// const rraWei = await mainnet.communityPool.contract.getRecommendedRechargeAmount( -// mainnet.web3.utils.soliditySha3(chainName1), -// address -// ) -// const rraEther = fromWei(rraWei as string, DEFAULT_ERC20_DECIMALS); + const rraWei = await mainnet.communityPool.contract.getRecommendedRechargeAmount(chainHash, address) + const rraEther = fromWei(rraWei as string, DEFAULT_ERC20_DECIMALS) -// let recommendedAmount = parseFloat(rraEther as string) * RECHARGE_MULTIPLIER; -// if (recommendedAmount < MINIMUM_RECHARGE_AMOUNT) recommendedAmount = MINIMUM_RECHARGE_AMOUNT; + let recommendedAmount = parseFloat(rraEther as string) * RECHARGE_MULTIPLIER + if (recommendedAmount < MINIMUM_RECHARGE_AMOUNT) recommendedAmount = MINIMUM_RECHARGE_AMOUNT -// const communityPoolData = { -// exitGasOk: activeM && activeS && rraWei === '0', -// isActive: activeM && activeS, -// balance: balanceWei, -// accountBalance: accountBalanceWei, -// recommendedRechargeAmount: recommendedAmount.toString(), -// originalRecommendedRechargeAmount: rraWei + const communityPoolData = { + exitGasOk: activeM && activeS && rraWei === 0n, + isActive: activeM && activeS, + balance: balanceWei, + accountBalance: accountBalanceWei, + recommendedRechargeAmount: recommendedAmount, + originalRecommendedRechargeAmount: rraWei, + } + log('communityPoolData:', communityPoolData) + return communityPoolData +} + +export async function connectedMainnetChain( + mpc: MetaportCore, + walletClient: WalletClient, + switchNetwork: (chainId: number | bigint) => Promise, +): Promise { + const currentChainId = walletClient.chain.id + const chainId = CHAIN_IDS[mpc.config.skaleNetwork] + log(`Current chainId: ${currentChainId}, required chainId: ${chainId} `) + if (currentChainId !== Number(chainId)) { + log(`Switching network to ${chainId}...`) + const chain = await switchNetwork(Number(chainId)) + if (!chain) { + throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `) + } + log(`Network switched to ${chainId}...`) + } + const signer = walletClientToSigner(walletClient) + return new MainnetChain(signer.provider, getMainnetAbi(mpc.config.skaleNetwork)) +} + +export async function withdraw( + mpc: MetaportCore, + walletClient: WalletClient, + chainName: string, + amount: bigint, + address: `0x${string}`, + switchNetwork: (chainId: number | bigint) => Promise, + setLoading: (loading: string | false) => void, + setErrorMessage: (errorMessage: dataclasses.ErrorMessage) => void, + errorMessageClosedFallback: () => void, +) { + setLoading('withdraw') + try { + log(`Withdrawing from community pool: ${chainName}, amount: ${amount}`) + const mainnetMetamask = await connectedMainnetChain(mpc, walletClient, switchNetwork) + await mainnetMetamask.communityPool.withdraw(chainName, amount, { + address: address, + customGasLimit: COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, + }) + setLoading(false) + } catch (err) { + console.error(err) + const msg = err.message ? err.message : DEFAULT_ERROR_MSG + setErrorMessage(new dataclasses.TransactionErrorMessage(msg, errorMessageClosedFallback)) + } +} + +// async function rechargeCommunityPool() { +// // todo: optimize +// setLoadingCommunityPool('recharge'); +// try { +// log('Recharging community pool...') +// const sChain = initSChain( +// props.config.skaleNetwork, +// chainName1 +// ); +// const mainnetMetamask = await initMainnet1(); +// setChainId(getChainId(props.config.skaleNetwork, MAINNET_CHAIN_NAME)); +// await mainnetMetamask.communityPool.recharge(chainName1, address, { +// address: address, +// value: toWei(rechargeAmount, DEFAULT_ERC20_DECIMALS) +// }); +// setLoadingCommunityPool('activate'); +// let active = false; +// const chainHash = mainnet.web3.utils.soliditySha3(chainName1); +// let counter = 0; +// while (!active) { +// log('Waiting for account activation...'); +// let activeM = await mainnet.communityPool.contract.methods.activeUsers( +// address, +// chainHash +// ).call(); +// let activeS = await sChain.communityLocker.contract.methods.activeUsers( +// address +// ).call(); +// active = activeS && activeM; +// await delay(BALANCE_UPDATE_INTERVAL_SECONDS * 1000); +// counter++; +// if (counter >= 10) break; +// } +// } catch (err) { +// console.error(err); +// const msg = err.message ? err.message : DEFAULT_ERROR_MSG; +// setErrorMessage(new TransactionErrorMessage(msg, errorMessageClosedFallback)); +// } finally { +// await initSchain1(); +// setChainId(getChainId(props.config.skaleNetwork, chainName1)); +// setMainnet(initMainnet(props.config.skaleNetwork, props.config.mainnetEndpoint)); +// await updateCommunityPoolData(); +// setLoadingCommunityPool(false); // } -// log('communityPoolData:', communityPoolData); -// return communityPoolData; // } diff --git a/src/core/constants.ts b/src/core/constants.ts index 39746ed..029739d 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -98,7 +98,7 @@ export const FAUCET_DATA = faucetJson export const RECHARGE_MULTIPLIER = 1.2 export const MINIMUM_RECHARGE_AMOUNT = 0.005 -export const COMMUNITY_POOL_WITHDRAW_GAS_LIMIT = '1500000' +export const COMMUNITY_POOL_WITHDRAW_GAS_LIMIT = 1500000n export const BALANCE_UPDATE_INTERVAL_SECONDS = 10 export const SFUEL_RESERVE_AMOUNT = 0.02 diff --git a/src/core/interfaces/CommunityPoolData.ts b/src/core/interfaces/CommunityPoolData.ts index cdd7b69..087f867 100644 --- a/src/core/interfaces/CommunityPoolData.ts +++ b/src/core/interfaces/CommunityPoolData.ts @@ -24,8 +24,8 @@ export interface CommunityPoolData { exitGasOk: boolean isActive: boolean - balance: string - accountBalance: string - recommendedRechargeAmount: string - originalRecommendedRechargeAmount: string + balance: bigint + accountBalance: bigint + recommendedRechargeAmount: number + originalRecommendedRechargeAmount: bigint } diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 2684b85..6148c94 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -30,9 +30,15 @@ import * as STAGING_CHAIN_ICONS from '../meta/staging/icons' import * as LEGACY_CHAIN_ICONS from '../meta/legacy/icons' import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons' -import * as icons from '../icons' +// import * as icons from '../icons' -// const icons = { eth: { default: '' }, skl: { default: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIj48Y2lyY2xlIGZpbGw9IiMwMDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPjxnIGZpbGw9IiNGRkYiPjxwYXRoIGQ9Ik0yMi41MTQgOC40OTJ2Ljk5MUg5LjgxdjEzLjAzNGgxMi43MDRWMjQuNWwtNy40Mi0uMDU3LTcuNDUtLjA4NS0uMDg2LTguNDQzTDcuNSA3LjVoMTUuMDE0eiIvPjxwYXRoIGQ9Ik0yMy42OTggMTAuOWMxLjEyNi4zMTIgMi4xMDggMS4xOSAyLjQyNSAyLjE4Mi4xNzMuNTk1LjA4Ny42NTEtLjkyNC42NTEtLjc4IDAtMS4yMTItLjE3LTEuNDcyLS41NjYtLjQzMy0uNzA5LTIuMzk3LS43OTQtMi45NzQtLjExNC0uNjM1Ljc2NS4wNTggMS4zMzIgMi4zMSAxLjg0MiAxLjEyNi4yNTUgMi4zMS42OCAyLjYyNy45NjMgMS40NDQgMS4yNzUuODY2IDQuMDgtMS4wMSA0Ljg0NS0xLjI3LjUxLTMuMzUuNTEtNC42MiAwLS44NjYtLjM2OC0xLjg3Ny0xLjY0My0xLjg3Ny0yLjQzNiAwLS41MSAxLjg3Ny0uMzEyIDIuMzY4LjI4MyAxLjA0IDEuMTYyIDMuNDY0Ljk5MiAzLjYzOC0uMjU1LjE0NC0uOTYzLS40MDUtMS4zODgtMi4wNS0xLjU4Ny0yLjY4NS0uMzY4LTMuNjY3LTEuMTktMy42NjctMy4wNiAwLTIuMjEgMi40MjUtMy41MTMgNS4yMjYtMi43NDh6Ii8+PC9nPjwvZz48L3N2Zz4=' } }; // TODO: storybook fix +const icons = { + eth: { default: '' }, + skl: { + default: + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIj48Y2lyY2xlIGZpbGw9IiMwMDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPjxnIGZpbGw9IiNGRkYiPjxwYXRoIGQ9Ik0yMi41MTQgOC40OTJ2Ljk5MUg5LjgxdjEzLjAzNGgxMi43MDRWMjQuNWwtNy40Mi0uMDU3LTcuNDUtLjA4NS0uMDg2LTguNDQzTDcuNSA3LjVoMTUuMDE0eiIvPjxwYXRoIGQ9Ik0yMy42OTggMTAuOWMxLjEyNi4zMTIgMi4xMDggMS4xOSAyLjQyNSAyLjE4Mi4xNzMuNTk1LjA4Ny42NTEtLjkyNC42NTEtLjc4IDAtMS4yMTItLjE3LTEuNDcyLS41NjYtLjQzMy0uNzA5LTIuMzk3LS43OTQtMi45NzQtLjExNC0uNjM1Ljc2NS4wNTggMS4zMzIgMi4zMSAxLjg0MiAxLjEyNi4yNTUgMi4zMS42OCAyLjYyNy45NjMgMS40NDQgMS4yNzUuODY2IDQuMDgtMS4wMSA0Ljg0NS0xLjI3LjUxLTMuMzUuNTEtNC42MiAwLS44NjYtLjM2OC0xLjg3Ny0xLjY0My0xLjg3Ny0yLjQzNiAwLS41MSAxLjg3Ny0uMzEyIDIuMzY4LjI4MyAxLjA0IDEuMTYyIDMuNDY0Ljk5MiAzLjYzOC0uMjU1LjE0NC0uOTYzLS40MDUtMS4zODgtMi4wNS0xLjU4Ny0yLjY4NS0uMzY4LTMuNjY3LTEuMTktMy42NjctMy4wNiAwLTIuMjEgMi40MjUtMy41MTMgNS4yMjYtMi43NDh6Ii8+PC9nPjwvZz48L3N2Zz4=', + }, +} // TODO: storybook fix const CHAIN_ICONS = { mainnet: MAINNET_CHAIN_ICONS, diff --git a/src/index.ts b/src/index.ts index 400926d..0660704 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,12 +22,13 @@ import TransferETA from './components/TransferETA' import AmountErrorMessage from './components/AmountErrorMessage' import DestTokenBalance from './components/DestTokenBalance' import ErrorMessage from './components/ErrorMessage' +import CommunityPool from './components/CommunityPool' import { cls } from './core/helper' import styles from './styles/styles.module.scss' -import common from './styles/common.module.scss' +import cmn from './styles/cmn.module.scss' -import { getWidgetTheme as getMetaportTheme } from './core/themes'; +import { getWidgetTheme as getMetaportTheme } from './core/themes' import { useAccount as useWagmiAccount } from 'wagmi' @@ -51,10 +52,11 @@ export { TokenBalance, DestTokenBalance, ErrorMessage, + CommunityPool, cls, styles, - common, + cmn, getMetaportTheme, useWagmiAccount, - PROXY_ENDPOINTS + PROXY_ENDPOINTS, } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index a65c6fd..3c42430 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -1,273 +1,275 @@ import * as interfaces from '../core/interfaces' export const METAPORT_CONFIG: interfaces.MetaportConfig = { - "skaleNetwork": "staging", - "openOnLoad": true, - "openButton": true, - "debug": false, - "chains": [ - "mainnet", - "staging-legal-crazy-castor", // Europa - "staging-utter-unripe-menkar", // Calypso - "staging-faint-slimy-achird", // Nebula - "staging-perfect-parallel-gacrux", // Test Chain 1 - "staging-severe-violet-wezen", // Test Chain 2 - "staging-weepy-fitting-caph" // Tank War Zone + skaleNetwork: 'staging', + openOnLoad: true, + openButton: true, + debug: false, + chains: [ + 'mainnet', + 'staging-legal-crazy-castor', // Europa + 'staging-utter-unripe-menkar', // Calypso + 'staging-faint-slimy-achird', // Nebula + 'staging-perfect-parallel-gacrux', // Test Chain 1 + 'staging-severe-violet-wezen', // Test Chain 2 + 'staging-weepy-fitting-caph', // Tank War Zone ], - "tokens": { - "eth": { - "symbol": "eth" + tokens: { + eth: { + symbol: 'eth', }, - "skl": { - "decimals": "18", - "name": "SKALE", - "symbol": "SKL" + skl: { + decimals: '18', + name: 'SKALE', + symbol: 'SKL', }, - "usdc": { - "decimals": "6", - "symbol": "USDC", - "name": "USD Coin" + usdc: { + decimals: '6', + symbol: 'USDC', + name: 'USD Coin', }, - "usdt": { - "decimals": "6", - "symbol": "USDT", - "name": "Tether USD" + usdt: { + decimals: '6', + symbol: 'USDT', + name: 'Tether USD', }, - "wbtc": { - "decimals": "18", - "symbol": "WBTC", - "name": "WBTC" + wbtc: { + decimals: '18', + symbol: 'WBTC', + name: 'WBTC', }, - "_SPACE_1": { - "name": "SKALE Space", - "symbol": "SPACE", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png" + _SPACE_1: { + name: 'SKALE Space', + symbol: 'SPACE', + iconUrl: 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png', }, - "_SKALIENS_1": { - "name": "SKALIENS Collection", - "symbol": "SKALIENS", - "iconUrl": "https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png" + _SKALIENS_1: { + name: 'SKALIENS Collection', + symbol: 'SKALIENS', + iconUrl: 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png', }, - "ruby": { - "name": "Ruby Token", - "iconUrl": "https://ruby.exchange/images/tokens/ruby-square.png", - "symbol": "RUBY" + ruby: { + name: 'Ruby Token', + iconUrl: 'https://ruby.exchange/images/tokens/ruby-square.png', + symbol: 'RUBY', }, - "dai": { - "name": "DAI Stablecoin", - "symbol": "DAI" + dai: { + name: 'DAI Stablecoin', + symbol: 'DAI', }, - "usdp": { - "name": "Pax Dollar", - "symbol": "USDP", - "iconUrl": "https://ruby.exchange/images/tokens/usdp-square.png" + usdp: { + name: 'Pax Dollar', + symbol: 'USDP', + iconUrl: 'https://ruby.exchange/images/tokens/usdp-square.png', + }, + hmt: { + name: 'Human Token', + symbol: 'HMT', + iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png', }, - "hmt": { - "name": "Human Token", - "symbol": "HMT", - "iconUrl": "https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png" - } }, - "connections": { - "mainnet": { - "erc20": { - "skl": { - "address": "0x493D4442013717189C9963a2e275Ad33bfAFcE11", - "chains": { - "staging-legal-crazy-castor": {}, - "staging-utter-unripe-menkar": { - "hub": "staging-legal-crazy-castor" + connections: { + mainnet: { + erc20: { + skl: { + address: '0x493D4442013717189C9963a2e275Ad33bfAFcE11', + chains: { + 'staging-legal-crazy-castor': {}, + 'staging-utter-unripe-menkar': { + hub: 'staging-legal-crazy-castor', + }, + 'staging-faint-slimy-achird': { + hub: 'staging-legal-crazy-castor', }, - "staging-faint-slimy-achird": { - "hub": "staging-legal-crazy-castor" - } - } + }, }, - "ruby": { - "address": "0xd66641E25E9D36A995682572eaD74E24C11Bb422", - "chains": { - "staging-legal-crazy-castor": {} - } + ruby: { + address: '0xd66641E25E9D36A995682572eaD74E24C11Bb422', + chains: { + 'staging-legal-crazy-castor': {}, + }, }, - "dai": { - "address": "0x83B38f79cFFB47CF74f7eC8a5F8D7DD69349fBf7", - "chains": { - "staging-legal-crazy-castor": {} - } + dai: { + address: '0x83B38f79cFFB47CF74f7eC8a5F8D7DD69349fBf7', + chains: { + 'staging-legal-crazy-castor': {}, + }, }, - "usdp": { - "address": "0x66259E472f8d09083ecB51D42F9F872A61001426", - "chains": { - "staging-legal-crazy-castor": {} - } + usdp: { + address: '0x66259E472f8d09083ecB51D42F9F872A61001426', + chains: { + 'staging-legal-crazy-castor': {}, + }, }, - "usdt": { - "address": "0xD1E44e3afd6d3F155e7704c67705E3bAC2e491b6", - "chains": { - "staging-legal-crazy-castor": {} - } + usdt: { + address: '0xD1E44e3afd6d3F155e7704c67705E3bAC2e491b6', + chains: { + 'staging-legal-crazy-castor': {}, + }, + }, + usdc: { + address: '0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9', + chains: { + 'staging-legal-crazy-castor': {}, + 'staging-utter-unripe-menkar': { + hub: 'staging-legal-crazy-castor', + }, + }, }, - "usdc": { - "address": "0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9", - "chains": { - "staging-legal-crazy-castor": {}, - "staging-utter-unripe-menkar": { - "hub": "staging-legal-crazy-castor" - } - } + wbtc: { + address: '0xd80BC0126A38c9F7b915e1B2B9f78280639cadb3', + chains: { + 'staging-legal-crazy-castor': {}, + }, }, - "wbtc": { - "address": "0xd80BC0126A38c9F7b915e1B2B9f78280639cadb3", - "chains": { - "staging-legal-crazy-castor": {} - } + hmt: { + address: '0x4058d058ff62ED347dB8a69c43Ae9C67268B50b0', + chains: {}, }, - "hmt": { - "address": "0x4058d058ff62ED347dB8a69c43Ae9C67268B50b0", - "chains": {} - } }, - "erc721meta": { - "_SPACE_1": { - "address": "0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6", - "chains": {} - } + erc721meta: { + _SPACE_1: { + address: '0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6', + chains: {}, + }, + }, + erc1155: { + _SKALIENS_1: { + address: '0x6cb73D413970ae9379560aA45c769b417Fbf33D6', + chains: {}, + }, }, - "erc1155": { - "_SKALIENS_1": { - "address": "0x6cb73D413970ae9379560aA45c769b417Fbf33D6", - "chains": {} - } - } }, - "staging-utter-unripe-menkar": { // Calypso connections - "erc20": { - "skl": { - "address": "0x7E1B8750C21AebC3bb2a0bDf40be104C609a9852", - "chains": { - "staging-legal-crazy-castor": { - "clone": true + 'staging-utter-unripe-menkar': { + // Calypso connections + erc20: { + skl: { + address: '0x7E1B8750C21AebC3bb2a0bDf40be104C609a9852', + chains: { + 'staging-legal-crazy-castor': { + clone: true, }, - "staging-faint-slimy-achird": { - "hub": "staging-legal-crazy-castor", - "clone": true + 'staging-faint-slimy-achird': { + hub: 'staging-legal-crazy-castor', + clone: true, }, - "mainnet": { - "hub": "staging-legal-crazy-castor", - "clone": true - } - } + mainnet: { + hub: 'staging-legal-crazy-castor', + clone: true, + }, + }, }, - "usdc": { - "address": "0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6", - "chains": { - "staging-legal-crazy-castor": { - "clone": true + usdc: { + address: '0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6', + chains: { + 'staging-legal-crazy-castor': { + clone: true, + }, + mainnet: { + hub: 'staging-legal-crazy-castor', + clone: true, }, - "mainnet": { - "hub": "staging-legal-crazy-castor", - "clone": true - } - } - } - } + }, + }, + }, }, - "staging-faint-slimy-achird": { - "erc20": { - "skl": { - "address": "0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d", - "chains": { - "staging-legal-crazy-castor": { - "clone": true + 'staging-faint-slimy-achird': { + erc20: { + skl: { + address: '0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d', + chains: { + 'staging-legal-crazy-castor': { + clone: true, + }, + mainnet: { + hub: 'staging-legal-crazy-castor', + clone: true, }, - "mainnet": { - "hub": "staging-legal-crazy-castor", - "clone": true + 'staging-utter-unripe-menkar': { + hub: 'staging-legal-crazy-castor', + clone: true, }, - "staging-utter-unripe-menkar": { - "hub": "staging-legal-crazy-castor", - "clone": true - } - } - } - } + }, + }, + }, }, - "staging-legal-crazy-castor": { // Europa connections - "erc20": { - "skl": { - "address": "0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6", - "chains": { - "mainnet": { - "clone": true + 'staging-legal-crazy-castor': { + // Europa connections + erc20: { + skl: { + address: '0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6', + chains: { + mainnet: { + clone: true, + }, + 'staging-utter-unripe-menkar': { + wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6', }, - "staging-utter-unripe-menkar": { - "wrapper": "0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6" + 'staging-faint-slimy-achird': { + wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6', }, - "staging-faint-slimy-achird": { - "wrapper": "0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6" - } - } + }, }, - "ruby": { - "address": "0xf06De9214B1Db39fFE9db2AebFA74E52f1e46e39", - "chains": { - "mainnet": { - "clone": true - } - } + ruby: { + address: '0xf06De9214B1Db39fFE9db2AebFA74E52f1e46e39', + chains: { + mainnet: { + clone: true, + }, + }, }, - "dai": { - "address": "0x3595E2f313780cb2f23e197B8e297066fd410d30", - "chains": { - "mainnet": { - "clone": true - } - } + dai: { + address: '0x3595E2f313780cb2f23e197B8e297066fd410d30', + chains: { + mainnet: { + clone: true, + }, + }, }, - "usdp": { - "address": "0xe0E2cb3A5d6f94a5bc2D00FAa3e64460A9D241E1", - "chains": { - "mainnet": { - "clone": true - } - } + usdp: { + address: '0xe0E2cb3A5d6f94a5bc2D00FAa3e64460A9D241E1', + chains: { + mainnet: { + clone: true, + }, + }, }, - "usdt": { - "address": "0xa388F9783d8E5B0502548061c3b06bf4300Fc0E1", - "chains": { - "mainnet": { - "clone": true - } - } + usdt: { + address: '0xa388F9783d8E5B0502548061c3b06bf4300Fc0E1', + chains: { + mainnet: { + clone: true, + }, + }, }, - "usdc": { - "address": "0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33", - "chains": { - "mainnet": { - "clone": true + usdc: { + address: '0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33', + chains: { + mainnet: { + clone: true, + }, + 'staging-utter-unripe-menkar': { + wrapper: '0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0', }, - "staging-utter-unripe-menkar": { - "wrapper": "0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0" - } - } + }, }, - "wbtc": { - "address": "0xf5E880E1066DDc90471B9BAE6f183D5344fd289F", - "chains": { - "mainnet": { - "clone": true - } - } - } - } + wbtc: { + address: '0xf5E880E1066DDc90471B9BAE6f183D5344fd289F', + chains: { + mainnet: { + clone: true, + }, + }, + }, + }, }, - "staging-severe-violet-wezen": { - "erc20": {} + 'staging-severe-violet-wezen': { + erc20: {}, }, - "staging-perfect-parallel-gacrux": { - "erc20": {}, - "erc721": {}, - "erc1155": { + 'staging-perfect-parallel-gacrux': { + erc20: {}, + erc721: {}, + erc1155: { // "skaliens": { // "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", // "chains": {} @@ -280,10 +282,10 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { // "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", // "chains": {} // } - } - } + }, + }, + }, + theme: { + mode: 'dark', }, - "theme": { - "mode": "dark" - } -} \ No newline at end of file +} diff --git a/src/store/CommunityPoolStore.ts b/src/store/CommunityPoolStore.ts new file mode 100644 index 0000000..3f12bf7 --- /dev/null +++ b/src/store/CommunityPoolStore.ts @@ -0,0 +1,72 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file CommunityPoolStore.ts + * @copyright SKALE Labs 2023-Present + */ + +import { create } from 'zustand' +import { MainnetChain, SChain } from '@skalenetwork/ima-js' + +import * as interfaces from '../core/interfaces' +import { getEmptyCommunityPoolData, getCommunityPoolData } from '../core/community_pool' +import MetaportCore from '../core/metaport' +import { MAINNET_CHAIN_NAME } from '../core/constants' + + +interface CommunityPoolState { + cpData: interfaces.CommunityPoolData + setCpData: (cpData: interfaces.CommunityPoolData) => void + loading: string | false + setLoading: (loading: string | false) => void + amount: string + setAmount: (amount: string) => void + updateCPData: (address: string, chainName1: string, chainName2: string, mpc: MetaportCore) => void + chainName: string + mainnet: MainnetChain + sChain: SChain +} + +export const useCPStore = create()((set, get) => ({ + cpData: getEmptyCommunityPoolData(), + setCpData: (cpData: interfaces.CommunityPoolData) => set(() => ({ cpData: cpData })), + loading: false, + setLoading: (loading: string | false) => set(() => ({ loading: loading })), + amount: '', + setAmount: (amount: string) => + set(() => { + return { amount: amount } + }), + + mainnet: null, + sChain: null, + chainName: null, + + updateCPData: async (address: string, chainName1: string, chainName2: string, mpc: MetaportCore) => { + if (chainName2 !== MAINNET_CHAIN_NAME) return + if (!get().mainnet) { + set({ mainnet: mpc.mainnet() }) + } + if (!get().sChain || get().chainName !== chainName1) { + set({ sChain: mpc.schain(chainName1) }) + } + const cpData = await getCommunityPoolData(address, chainName1, chainName2, get().mainnet, get().sChain) + set({ cpData: cpData }) + }, +})) diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 02aa95d..780458f 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -23,7 +23,7 @@ import debug from 'debug' -import { Contract } from 'ethers'; +import { Contract } from 'ethers' import { MainnetChain, SChain } from '@skalenetwork/ima-js' import { create } from 'zustand' @@ -91,7 +91,7 @@ interface MetaportState { setAmountErrorMessage: (amountErrorMessage: string) => void errorMessage: dataclasses.ErrorMessage - setErrorMessage: (amountErrorMessage: dataclasses.ErrorMessage) => void + setErrorMessage: (errorMessage: dataclasses.ErrorMessage) => void actionBtnDisabled: boolean setActionBtnDisabled: (actionBtnDisabled: boolean) => void @@ -195,7 +195,7 @@ export const useMetaportStore = create()((set, get) => ({ tokenId: null, currentStep: 0, transferInProgress: false, - destTokenBalance: null + destTokenBalance: null, }) }, @@ -284,18 +284,13 @@ export const useMetaportStore = create()((set, get) => ({ setToken: async (token: dataclasses.TokenData) => { const provider = get().chainName2 === MAINNET_CHAIN_NAME ? get().mainnetChain.provider : get().sChain2.provider - const destTokenContract = get().mpc.tokenContract( - get().chainName2, - token.keyname, - token.type, - provider - ) + const destTokenContract = get().mpc.tokenContract(get().chainName2, token.keyname, token.type, provider) set({ token: token, stepsMetadata: getStepsMetadata(get().mpc.config, token, get().chainName2), destTokenContract: destTokenContract, destTokenBalance: null, - destChains: Object.keys(token.connections) + destChains: Object.keys(token.connections), }) }, diff --git a/src/store/Store.ts b/src/store/Store.ts index 43b0952..cb63089 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -47,6 +47,9 @@ interface CollapseState { expandedTokens: string | false setExpandedTokens: (expanded: string | false) => void + + expandedCP: string | false + setExpandedCP: (expanded: string | false) => void } export const useCollapseStore = create()((set) => ({ @@ -56,6 +59,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: expanded, expandedTo: false, expandedTokens: false, + expandedCP: false, })), expandedTo: false, setExpandedTo: (expanded: string | false) => @@ -63,6 +67,7 @@ export const useCollapseStore = create()((set) => ({ expandedTo: expanded, expandedFrom: false, expandedTokens: false, + expandedCP: false, })), expandedTokens: false, setExpandedTokens: (expanded: string | false) => @@ -70,5 +75,14 @@ export const useCollapseStore = create()((set) => ({ expandedTokens: expanded, expandedFrom: false, expandedTo: false, + expandedCP: false, + })), + expandedCP: false, + setExpandedCP: (expanded: string | false) => + set(() => ({ + expandedCP: expanded, + expandedFrom: false, + expandedTo: false, + expandedTokens: false, })), })) diff --git a/src/styles/common.module.scss b/src/styles/cmn.module.scss similarity index 70% rename from src/styles/common.module.scss rename to src/styles/cmn.module.scss index 15dba77..8efd048 100644 --- a/src/styles/common.module.scss +++ b/src/styles/cmn.module.scss @@ -9,16 +9,16 @@ justify-content: end; } -.flexCentered { +.flexc { align-items: center; justify-content: center; } -.flexCenteredVert { +.flexcv { align-items: center; } -.flexGrow { +.flexg { flex-grow: 1; } @@ -27,49 +27,41 @@ flex-wrap: wrap; } -.marg10 { - margin: 10px !important; -} .padd10 { padding: 10px !important; } -.margTop10 { +.mtop10 { margin-top: 10px !important; } -.margTop20 { +.mtop20 { margin-top: 20px !important; } -.margTop40 { - margin-top: 40px !important; -} - - -.margLeft5 { +.mleft5 { margin-left: 5px !important; } -.margLeft10 { +.mleft10 { margin-left: 10px !important; } -.margLeft20 { +.mleft20 { margin-left: 20px !important; } -.margRi20 { +.mri20 { margin-right: 20px !important; } -.margRi10 { +.mri10 { margin-right: 10px !important; } -.margRi5 { +.mri5 { margin-right: 5px !important; } @@ -81,75 +73,39 @@ margin-bottom: -15px !important; } -.marg-top-20 { - margin-top: 20px !important; -} - -.margTop20Pt { +.mtop20Pt { margin-top: 20pt !important; } -.marg-top-30 { - margin-top: 30px !important; -} - -.margBott20 { +.mbott20 { margin-bottom: 20px !important; } -.margBott40 { - margin-bottom: 40px !important; -} - -.margBott15 { - margin-bottom: 15px !important; -} - -.margBott10 { +.mbott10 { margin-bottom: 10px !important; } -.marg-bott-40 { - margin-bottom: 40px !important; -} - -.margBott5 { +.mbott5 { margin-bottom: 5px !important; } -.margTop5 { +.mtop5 { margin-top: 5px !important; } -.noMargTop { - margin-top: 0 !important; -} - -.noMargBott { - margin-bottom: 0 !important; -} - -.noMarg { +.nom { margin: 0 !important; } -.paddTop10 { - padding-top: 10px !important; -} - -.paddTop20 { +.ptop20 { padding-top: 20px !important; } -.paddBott10 { - padding-bottom: 10px !important; -} - -.noPadd { +.nop { padding: 0 !important; } -.capitalize { +.cap { text-transform: capitalize !important; } @@ -176,7 +132,7 @@ } -.uppercase { +.upp { text-transform: uppercase !important; } @@ -201,11 +157,11 @@ } .darkTheme { - .pMain { + .pPrim { color: white !important; } - .pSecondary { + .pSec { color: $sk-secondary-dark !important; } @@ -215,11 +171,11 @@ } .lightTheme { - .pMain { + .pPrim { color: black !important; } - .pSecondary { + .pSec { color: $sk-secondary-light !important; } @@ -232,7 +188,7 @@ width: 100% !important; } -.textCentered { +.pCent { text-align: center; } diff --git a/src/styles/common.module.scss.d.ts b/src/styles/common.module.scss.d.ts deleted file mode 100644 index b3bc836..0000000 --- a/src/styles/common.module.scss.d.ts +++ /dev/null @@ -1,55 +0,0 @@ -import globalClassNames from '../style.d' -declare const classNames: typeof globalClassNames & { - readonly flex: 'flex' - readonly flexRight: 'flexRight' - readonly flexCentered: 'flexCentered' - readonly flexCenteredVert: 'flexCenteredVert' - readonly flexGrow: 'flexGrow' - readonly flexRow: 'flexRow' - readonly marg10: 'marg10' - readonly padd10: 'padd10' - readonly margTop10: 'margTop10' - readonly margTop20: 'margTop20' - readonly margTop40: 'margTop40' - readonly margLeft5: 'margLeft5' - readonly margLeft10: 'margLeft10' - readonly margLeft20: 'margLeft20' - readonly margRi20: 'margRi20' - readonly margRi10: 'margRi10' - readonly margRi5: 'margRi5' - readonly margBottMin10: 'margBottMin10' - readonly margBottMin15: 'margBottMin15' - readonly 'marg-top-20': 'marg-top-20' - readonly margTop20Pt: 'margTop20Pt' - readonly 'marg-top-30': 'marg-top-30' - readonly margBott20: 'margBott20' - readonly margBott40: 'margBott40' - readonly margBott15: 'margBott15' - readonly margBott10: 'margBott10' - readonly 'marg-bott-40': 'marg-bott-40' - readonly margBott5: 'margBott5' - readonly margTop5: 'margTop5' - readonly noMargTop: 'noMargTop' - readonly noMargBott: 'noMargBott' - readonly noMarg: 'noMarg' - readonly paddTop10: 'paddTop10' - readonly paddBott10: 'paddBott10' - readonly noPadd: 'noPadd' - readonly capitalize: 'capitalize' - readonly p: 'p' - readonly p500: 'p500' - readonly p600: 'p600' - readonly uppercase: 'uppercase' - readonly p1: 'p1' - readonly p2: 'p2' - readonly p3: 'p3' - readonly p4: 'p4' - readonly darkTheme: 'darkTheme' - readonly pMain: 'pMain' - readonly pSecondary: 'pSecondary' - readonly pDisabled: 'pDisabled' - readonly lightTheme: 'lightTheme' - readonly fullWidth: 'fullWidth' - readonly textCentered: 'textCentered' -} -export = classNames diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index d47c11b..3c1a742 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -15,6 +15,7 @@ } .popper { + margin-left: 20px; max-width: 390px; max-height: calc(100vh - 110pt); padding: 10pt; @@ -115,8 +116,16 @@ button { :global(.MuiAccordion-root) { background-color: transparent !important; + background-image: none !important; } + + // .accordionSm { + // :global(.MuiAccordionSummary-content) { + // margin: 0 !important; + // } + // } + :global(.MuiButton-root) { font-weight: 600 !important; box-shadow: none !important; @@ -136,6 +145,11 @@ button { } } +.widgetContent { + max-height: calc(100vh - 180px); + overflow-y: auto; +} + .imaWidgetBody { border-radius: $sk-border-radius-outter !important; @@ -268,6 +282,10 @@ button { padding: 10px 26px !important; } +.accordionContent { + padding: 0 26px !important; +} + .accordionSummaryTokens { padding-right: 11pt !important; } \ No newline at end of file From a390b5fcfd5fdcf07d11b4217be6a9592ffd8880 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 22 Aug 2023 17:32:15 +0100 Subject: [PATCH 026/110] Add recharge community pool function --- .../CommunityPool/CommunityPool.tsx | 75 ++++++++++----- src/components/Stepper/SkStepper.tsx | 6 +- src/core/community_pool.ts | 91 +++++++++---------- src/store/CommunityPoolStore.ts | 5 +- 4 files changed, 104 insertions(+), 73 deletions(-) diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx index ad43b12..b225e57 100644 --- a/src/components/CommunityPool/CommunityPool.tsx +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -44,7 +44,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle' import ErrorIcon from '@mui/icons-material/Error' import { fromWei } from '../../core/convertation' -import { withdraw } from '../../core/community_pool' +import { withdraw, recharge } from '../../core/community_pool' import { DEFAULT_ERC20_DECIMALS } from '../../core/constants' import { cls } from '../../core/helper' @@ -55,19 +55,8 @@ import { useCPStore } from '../../store/CommunityPoolStore' import { useCollapseStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' -// import { getChainIcon } from '../ChainsList/helper'; - -export default function CommunityPool(props: { - // communityPoolData: CommunityPoolData - // loading: string | false - // rechargeAmount: string - // setRechargeAmount: (amount: string) => {} - // expanded: string | false - // setExpanded: (expanded: string | false) => {} - // recharge: () => {} - // withdraw: () => {} - // marg: boolean -}) { + +export default function CommunityPool() { const { data: walletClient } = useWalletClient() const { switchNetworkAsync } = useSwitchNetwork() @@ -145,9 +134,31 @@ export default function CommunityPool(props: { ) } + async function rechargeCP() { + await recharge( + mpc, + walletClient, + chainName1, + amount, + address, + switchNetworkAsync, + setLoading, + setErrorMessage, + async () => { + setLoading(false) + setErrorMessage(null) + } + ) + setExpandedCP(false) + } + return (
- + } @@ -161,20 +172,26 @@ export default function CommunityPool(props: { -

- This wallet is used to pay for Ethereum gas fees from your transactions to the Ethereum Mainnet. You may - withdraw funds from your SKALE Gas Wallet at anytime. +

+ This wallet is used to pay for Ethereum gas fees from your transactions to the + Ethereum Mainnet. You may withdraw funds from your SKALE Gas Wallet at anytime.

-

ETH Balance

+

+ ETH Balance +

- +
-

Exit wallet Balance

+

+ Exit wallet Balance +

- +
@@ -189,10 +206,18 @@ export default function CommunityPool(props: { placeholder="0.00" value={amount} onChange={handleAmountChange} - // disabled={transferInProgress} + disabled={!!loading} />
-

+

ETH

@@ -203,7 +228,7 @@ export default function CommunityPool(props: { color="primary" size="medium" className={cls(styles.btnAction, cmn.mtop5)} - //onClick={props.recharge} + onClick={rechargeCP} disabled={ !!loading || !cpData.accountBalance || diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 77f5cdf..1fa37e8 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -15,6 +15,7 @@ import localStyles from './SkStepper.module.scss' import ChainIcon from '../ChainIcon' import { useMetaportStore } from '../../store/MetaportState' +import { useCPStore } from '../../store/CommunityPoolStore' import { Collapse } from '@mui/material' import { SkaleNetwork } from '../../core/interfaces' @@ -43,6 +44,8 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { const amount = useMetaportStore((state) => state.amount) + const cpData = useCPStore((state) => state.cpData) + const [emoji, setEmoji] = useState() useEffect(() => { setEmoji(getRandom(SUCCESS_EMOJIS)) @@ -96,7 +99,8 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { size="medium" className={cls(styles.btnAction, cmn.mtop5)} onClick={() => execute(address, switchNetworkAsync, walletClient)} - disabled={!!(amountErrorMessage || actionBtnDisabled || loading || amount == '')} + disabled={!!( + amountErrorMessage || actionBtnDisabled || loading || amount == '' || !cpData.exitGasOk)} > {step.btnText} diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index 809014c..d704855 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -29,7 +29,7 @@ import { WalletClient } from 'viem' import { Chain } from '@wagmi/core' import { CommunityPoolData } from './interfaces' -import { fromWei } from './convertation' +import { fromWei, toWei } from './convertation' import { walletClientToSigner } from './ethers' import { MAINNET_CHAIN_NAME, @@ -38,7 +38,9 @@ import { MINIMUM_RECHARGE_AMOUNT, COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, DEFAULT_ERROR_MSG, + BALANCE_UPDATE_INTERVAL_SECONDS } from './constants' +import { delay } from './helper' import { CHAIN_IDS, isMainnetChainId, getMainnetAbi } from './network' import MetaportCore from './metaport' @@ -149,48 +151,45 @@ export async function withdraw( } } -// async function rechargeCommunityPool() { -// // todo: optimize -// setLoadingCommunityPool('recharge'); -// try { -// log('Recharging community pool...') -// const sChain = initSChain( -// props.config.skaleNetwork, -// chainName1 -// ); -// const mainnetMetamask = await initMainnet1(); -// setChainId(getChainId(props.config.skaleNetwork, MAINNET_CHAIN_NAME)); -// await mainnetMetamask.communityPool.recharge(chainName1, address, { -// address: address, -// value: toWei(rechargeAmount, DEFAULT_ERC20_DECIMALS) -// }); -// setLoadingCommunityPool('activate'); -// let active = false; -// const chainHash = mainnet.web3.utils.soliditySha3(chainName1); -// let counter = 0; -// while (!active) { -// log('Waiting for account activation...'); -// let activeM = await mainnet.communityPool.contract.methods.activeUsers( -// address, -// chainHash -// ).call(); -// let activeS = await sChain.communityLocker.contract.methods.activeUsers( -// address -// ).call(); -// active = activeS && activeM; -// await delay(BALANCE_UPDATE_INTERVAL_SECONDS * 1000); -// counter++; -// if (counter >= 10) break; -// } -// } catch (err) { -// console.error(err); -// const msg = err.message ? err.message : DEFAULT_ERROR_MSG; -// setErrorMessage(new TransactionErrorMessage(msg, errorMessageClosedFallback)); -// } finally { -// await initSchain1(); -// setChainId(getChainId(props.config.skaleNetwork, chainName1)); -// setMainnet(initMainnet(props.config.skaleNetwork, props.config.mainnetEndpoint)); -// await updateCommunityPoolData(); -// setLoadingCommunityPool(false); -// } -// } +export async function recharge( + mpc: MetaportCore, + walletClient: WalletClient, + chainName: string, + amount: string, + address: `0x${string}`, + switchNetwork: (chainId: number | bigint) => Promise, + setLoading: (loading: string | false) => void, + setErrorMessage: (errorMessage: dataclasses.ErrorMessage) => void, + errorMessageClosedFallback: () => void, +) { + setLoading('recharge'); + try { + log(`Recharging community pool: ${chainName}, amount: ${amount}`) + + const sChain = mpc.schain(chainName) + const mainnetMetamask = await connectedMainnetChain(mpc, walletClient, switchNetwork) + await mainnetMetamask.communityPool.recharge(chainName, address, { + address: address, + value: toWei(amount, DEFAULT_ERC20_DECIMALS) + }); + setLoading('activate'); + let active = false; + const chainHash = ethers.id(chainName) + let counter = 0; + while (!active) { + log('Waiting for account activation...'); + let activeM = await mainnetMetamask.communityPool.contract.activeUsers(address, chainHash); + let activeS = await sChain.communityLocker.contract.activeUsers(address); + active = activeS && activeM; + await delay(BALANCE_UPDATE_INTERVAL_SECONDS * 1000); + counter++; + if (counter >= 10) break; + } + } catch (err) { + console.error(err); + const msg = err.message ? err.message : DEFAULT_ERROR_MSG; + setErrorMessage(new dataclasses.TransactionErrorMessage(msg, errorMessageClosedFallback)); + } finally { + setLoading(false); + } +} diff --git a/src/store/CommunityPoolStore.ts b/src/store/CommunityPoolStore.ts index 3f12bf7..1a994ca 100644 --- a/src/store/CommunityPoolStore.ts +++ b/src/store/CommunityPoolStore.ts @@ -67,6 +67,9 @@ export const useCPStore = create()((set, get) => ({ set({ sChain: mpc.schain(chainName1) }) } const cpData = await getCommunityPoolData(address, chainName1, chainName2, get().mainnet, get().sChain) - set({ cpData: cpData }) + set({ + cpData: cpData, + amount: cpData.recommendedRechargeAmount.toString() + }) }, })) From d55550f284d0d805e88181c9d1c1755d2c5433bd Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 22 Aug 2023 20:02:14 +0100 Subject: [PATCH 027/110] Update storybook, update vercel script --- package.json | 14 +++++++------- vercel.json | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 579c0f6..df162fe 100644 --- a/package.json +++ b/package.json @@ -35,13 +35,13 @@ }, "devDependencies": { "@babel/core": "7.22.10", - "@storybook/addon-essentials": "7.2.2", - "@storybook/addon-interactions": "7.2.2", - "@storybook/addon-links": "7.2.2", + "@storybook/addon-essentials": "7.3.2", + "@storybook/addon-interactions": "7.3.2", + "@storybook/addon-links": "7.3.2", "@storybook/addon-styling": "1.3.6", - "@storybook/blocks": "7.2.2", - "@storybook/react": "7.2.2", - "@storybook/react-vite": "7.2.2", + "@storybook/blocks": "7.3.2", + "@storybook/react": "7.3.2", + "@storybook/react-vite": "7.3.2", "@storybook/testing-library": "0.2.0", "@testing-library/react": "14.0.0", "@types/node": "20.4.9", @@ -69,7 +69,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.65.1", - "storybook": "7.2.2", + "storybook": "7.3.2", "typescript": "5.1.6", "vite": "4.4.9", "vite-plugin-dts": "3.5.1", diff --git a/vercel.json b/vercel.json index 202e3db..cab7b14 100644 --- a/vercel.json +++ b/vercel.json @@ -1,8 +1,8 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "yarn build-storybook", - "devCommand": "yarn storybook", - "installCommand": "bash prepare_meta.sh && yarn install && yarn build", + "buildCommand": "yarn build", + "devCommand": "yarn dev", + "installCommand": "bash prepare_meta.sh && yarn install && yarn build:lib", "framework": null, "outputDirectory": "./storybook-static" } \ No newline at end of file From d7bd049b749e3ff78311459b955ec15bce413f3b Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 22 Aug 2023 20:06:06 +0100 Subject: [PATCH 028/110] Use default token icons --- src/core/metadata.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 6148c94..af58bf0 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -30,15 +30,15 @@ import * as STAGING_CHAIN_ICONS from '../meta/staging/icons' import * as LEGACY_CHAIN_ICONS from '../meta/legacy/icons' import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons' -// import * as icons from '../icons' +import * as icons from '../icons' -const icons = { - eth: { default: '' }, - skl: { - default: - 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIj48Y2lyY2xlIGZpbGw9IiMwMDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPjxnIGZpbGw9IiNGRkYiPjxwYXRoIGQ9Ik0yMi41MTQgOC40OTJ2Ljk5MUg5LjgxdjEzLjAzNGgxMi43MDRWMjQuNWwtNy40Mi0uMDU3LTcuNDUtLjA4NS0uMDg2LTguNDQzTDcuNSA3LjVoMTUuMDE0eiIvPjxwYXRoIGQ9Ik0yMy42OTggMTAuOWMxLjEyNi4zMTIgMi4xMDggMS4xOSAyLjQyNSAyLjE4Mi4xNzMuNTk1LjA4Ny42NTEtLjkyNC42NTEtLjc4IDAtMS4yMTItLjE3LTEuNDcyLS41NjYtLjQzMy0uNzA5LTIuMzk3LS43OTQtMi45NzQtLjExNC0uNjM1Ljc2NS4wNTggMS4zMzIgMi4zMSAxLjg0MiAxLjEyNi4yNTUgMi4zMS42OCAyLjYyNy45NjMgMS40NDQgMS4yNzUuODY2IDQuMDgtMS4wMSA0Ljg0NS0xLjI3LjUxLTMuMzUuNTEtNC42MiAwLS44NjYtLjM2OC0xLjg3Ny0xLjY0My0xLjg3Ny0yLjQzNiAwLS41MSAxLjg3Ny0uMzEyIDIuMzY4LjI4MyAxLjA0IDEuMTYyIDMuNDY0Ljk5MiAzLjYzOC0uMjU1LjE0NC0uOTYzLS40MDUtMS4zODgtMi4wNS0xLjU4Ny0yLjY4NS0uMzY4LTMuNjY3LTEuMTktMy42NjctMy4wNiAwLTIuMjEgMi40MjUtMy41MTMgNS4yMjYtMi43NDh6Ii8+PC9nPjwvZz48L3N2Zz4=', - }, -} // TODO: storybook fix +// const icons = { +// eth: { default: '' }, +// skl: { +// default: +// 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIj48Y2lyY2xlIGZpbGw9IiMwMDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPjxnIGZpbGw9IiNGRkYiPjxwYXRoIGQ9Ik0yMi41MTQgOC40OTJ2Ljk5MUg5LjgxdjEzLjAzNGgxMi43MDRWMjQuNWwtNy40Mi0uMDU3LTcuNDUtLjA4NS0uMDg2LTguNDQzTDcuNSA3LjVoMTUuMDE0eiIvPjxwYXRoIGQ9Ik0yMy42OTggMTAuOWMxLjEyNi4zMTIgMi4xMDggMS4xOSAyLjQyNSAyLjE4Mi4xNzMuNTk1LjA4Ny42NTEtLjkyNC42NTEtLjc4IDAtMS4yMTItLjE3LTEuNDcyLS41NjYtLjQzMy0uNzA5LTIuMzk3LS43OTQtMi45NzQtLjExNC0uNjM1Ljc2NS4wNTggMS4zMzIgMi4zMSAxLjg0MiAxLjEyNi4yNTUgMi4zMS42OCAyLjYyNy45NjMgMS40NDQgMS4yNzUuODY2IDQuMDgtMS4wMSA0Ljg0NS0xLjI3LjUxLTMuMzUuNTEtNC42MiAwLS44NjYtLjM2OC0xLjg3Ny0xLjY0My0xLjg3Ny0yLjQzNiAwLS41MSAxLjg3Ny0uMzEyIDIuMzY4LjI4MyAxLjA0IDEuMTYyIDMuNDY0Ljk5MiAzLjYzOC0uMjU1LjE0NC0uOTYzLS40MDUtMS4zODgtMi4wNS0xLjU4Ny0yLjY4NS0uMzY4LTMuNjY3LTEuMTktMy42NjctMy4wNiAwLTIuMjEgMi40MjUtMy41MTMgNS4yMjYtMi43NDh6Ii8+PC9nPjwvZz48L3N2Zz4=', +// }, +// } // TODO: storybook fix const CHAIN_ICONS = { mainnet: MAINNET_CHAIN_ICONS, From 78234c8cb782c5063c32f234c302b26767cc4921 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 22 Aug 2023 20:43:22 +0100 Subject: [PATCH 029/110] Fix community pool logic for hub chains --- .../CommunityPool/CommunityPool.tsx | 27 ++++++++++++++----- src/store/CommunityPoolStore.ts | 7 ++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx index b225e57..50d9c72 100644 --- a/src/components/CommunityPool/CommunityPool.tsx +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -69,6 +69,8 @@ export default function CommunityPool() { const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) + const token = useMetaportStore((state) => state.token) + const mpc = useMetaportStore((state) => state.mpc) const setErrorMessage = useMetaportStore((state) => state.setErrorMessage) @@ -77,6 +79,12 @@ export default function CommunityPool() { const { address } = useAccount() + let chainName + if (token) { + chainName = chainName1 + if (token.connections[chainName2].hub) chainName = token.connections[chainName2].hub + } + const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { setExpandedCP(isExpanded ? panel : false) } @@ -90,15 +98,19 @@ export default function CommunityPool() { } useEffect(() => { - updateCPData(address, chainName1, chainName2, mpc) + updateCPData(address, chainName, chainName2, mpc) + }, []) + + useEffect(() => { + updateCPData(address, chainName, chainName2, mpc) const intervalId = setInterval(() => { - updateCPData(address, chainName1, chainName2, mpc) + updateCPData(address, chainName, chainName2, mpc) }, 10000) // Fetch users every 10 seconds return () => { clearInterval(intervalId) // Clear interval on component unmount } - }, [chainName1, chainName2, address]) + }, [chainName, chainName2, address]) const text = cpData.exitGasOk ? 'Exit gas wallet OK' : 'Recharge exit gas wallet' const icon = cpData.exitGasOk ? : @@ -121,7 +133,7 @@ export default function CommunityPool() { withdraw( mpc, walletClient, - chainName1, + chainName, cpData.balance, address, switchNetworkAsync, @@ -138,7 +150,7 @@ export default function CommunityPool() { await recharge( mpc, walletClient, - chainName1, + chainName, amount, address, switchNetworkAsync, @@ -235,7 +247,8 @@ export default function CommunityPool() { Number(amount) > Number(accountBalanceEther) || amount === '' || amount === '0' || - !amount + !amount || + !chainName } > {getRechargeBtnText()} @@ -248,7 +261,7 @@ export default function CommunityPool() { size="small" className={cls(styles.btnAction, cmn.mtop5)} onClick={withdrawCP} - disabled={!!loading} + disabled={!!loading || !chainName} > {getWithdrawBtnText()} diff --git a/src/store/CommunityPoolStore.ts b/src/store/CommunityPoolStore.ts index 1a994ca..f36bdf0 100644 --- a/src/store/CommunityPoolStore.ts +++ b/src/store/CommunityPoolStore.ts @@ -59,7 +59,7 @@ export const useCPStore = create()((set, get) => ({ chainName: null, updateCPData: async (address: string, chainName1: string, chainName2: string, mpc: MetaportCore) => { - if (chainName2 !== MAINNET_CHAIN_NAME) return + if (!chainName1 || !chainName2) return if (!get().mainnet) { set({ mainnet: mpc.mainnet() }) } @@ -68,8 +68,9 @@ export const useCPStore = create()((set, get) => ({ } const cpData = await getCommunityPoolData(address, chainName1, chainName2, get().mainnet, get().sChain) set({ + chainName: chainName1, cpData: cpData, - amount: cpData.recommendedRechargeAmount.toString() + amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : null }) - }, + } })) From f4d687c92acc7172042314764dfcc3bd73abfa31 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 23 Aug 2023 19:38:37 +0100 Subject: [PATCH 030/110] Add sFUEL station, fix community pool errors --- package.json | 2 +- .../CommunityPool/CommunityPool.tsx | 2 +- .../DestTokenBalance/DestTokenBalance.tsx | 2 +- src/components/SFuelWarning/SFuelWarning.tsx | 230 ++++++++++++++++++ src/components/SFuelWarning/index.ts | 1 + src/components/TokenList/TokenList.tsx | 2 +- src/components/WidgetBody/WidgetBody.tsx | 9 +- src/core/constants.ts | 4 +- src/core/faucet.ts | 64 ++--- src/core/sfuel.ts | 183 ++++++-------- src/store/CommunityPoolStore.ts | 3 +- src/store/SFuelStore.ts | 92 +++++++ 12 files changed, 448 insertions(+), 146 deletions(-) create mode 100644 src/components/SFuelWarning/SFuelWarning.tsx create mode 100644 src/components/SFuelWarning/index.ts create mode 100644 src/store/SFuelStore.ts diff --git a/package.json b/package.json index df162fe..2ad2617 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "@mui/material": "^5.8.1", "@rainbow-me/rainbowkit": "^1.0.8", "@skalenetwork/ima-js": "2.0.0-develop.3", - "@skaleproject/pow-ethers": "0.3.2", + "@skaleproject/pow-ethers": "0.3.3", "coingecko-api-v3": "^0.0.28", "react-jazzicon": "^1.0.4", "viem": "^1.5.3", diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx index 50d9c72..c3170ac 100644 --- a/src/components/CommunityPool/CommunityPool.tsx +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -105,7 +105,7 @@ export default function CommunityPool() { updateCPData(address, chainName, chainName2, mpc) const intervalId = setInterval(() => { updateCPData(address, chainName, chainName2, mpc) - }, 10000) // Fetch users every 10 seconds + }, 10000) return () => { clearInterval(intervalId) // Clear interval on component unmount diff --git a/src/components/DestTokenBalance/DestTokenBalance.tsx b/src/components/DestTokenBalance/DestTokenBalance.tsx index 6d46d52..eb7b7e4 100644 --- a/src/components/DestTokenBalance/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance/DestTokenBalance.tsx @@ -15,7 +15,7 @@ export default function DestTokenBalance() { updateDestTokenBalance(address) // Fetch users immediately on component mount const intervalId = setInterval(() => { updateDestTokenBalance(address) - }, 10000) // Fetch users every 10 seconds + }, 10000) return () => { clearInterval(intervalId) // Clear interval on component unmount } diff --git a/src/components/SFuelWarning/SFuelWarning.tsx b/src/components/SFuelWarning/SFuelWarning.tsx new file mode 100644 index 0000000..9652891 --- /dev/null +++ b/src/components/SFuelWarning/SFuelWarning.tsx @@ -0,0 +1,230 @@ + +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file SFuelWarning.ts + * @copyright SKALE Labs 2023-Present + */ + +import React, { useEffect } from 'react'; +import debug from 'debug'; + +import { useAccount } from 'wagmi' + +import Button from '@mui/material/Button'; +import LoadingButton from '@mui/lab/LoadingButton'; +import { Collapse } from '@mui/material'; + +import { + MAINNET_CHAIN_NAME, + SFUEL_CHEKCS_INTERVAL, + SFUEL_TEXT, + DEFAULT_FAUCET_URL +} from '../../core/constants'; + + +import { Station, StationData } from '../../core/sfuel'; +import { View } from '../../core/dataclasses/View'; + + +import { useMetaportStore } from '../../store/MetaportState' +import { useSFuelStore } from '../../store/SFuelStore' + +import { cls } from '../../core/helper'; +import cmn from '../../styles/cmn.module.scss' +import styles from '../../styles/styles.module.scss' + +// import CustomStationUrl from '../CustomStationUrl'; + + +debug.enable('*'); +const log = debug('metaport:components:SFuel'); + + +export default function SFuelWarning(props: {}) { + + const mpc = useMetaportStore((state) => state.mpc) + const chainName1 = useMetaportStore((state) => state.chainName1) + const chainName2 = useMetaportStore((state) => state.chainName2) + const token = useMetaportStore((state) => state.token) + + const loading = useSFuelStore((state) => state.loading) + const setLoading = useSFuelStore((state) => state.setLoading) + const mining = useSFuelStore((state) => state.mining) + const setMining = useSFuelStore((state) => state.setMining) + + const fromChainStation = useSFuelStore((state) => state.fromChainStation); + const setFromChainStation = useSFuelStore((state) => state.setFromChainStation); + + const toChainStation = useSFuelStore((state) => state.toChainStation); + const setToChainStation = useSFuelStore((state) => state.setToChainStation); + + const hubChainStation = useSFuelStore((state) => state.hubChainStation); + const setHubChainStation = useSFuelStore((state) => state.setHubChainStation); + + const sFuelStatus = useSFuelStore((state) => state.sFuelStatus); + const setSFuelStatus = useSFuelStore((state) => state.setSFuelStatus); + + const sFuelOk = useSFuelStore((state) => state.sFuelOk); + const setSFuelOk = useSFuelStore((state) => state.setSFuelOk); + + const fromStationData = useSFuelStore((state) => state.fromStationData); + const setFromStationData = useSFuelStore((state) => state.setFromStationData); + + const toStationData = useSFuelStore((state) => state.toStationData); + const setToStationData = useSFuelStore((state) => state.setToStationData); + + const hubStationData = useSFuelStore((state) => state.hubStationData); + const setHubStationData = useSFuelStore((state) => state.setHubStationData); + + const { address } = useAccount() + + let hubChain; + + if (token && token.connections[chainName2].hub) { + hubChain = token.connections[chainName2].hub + } + + useEffect(() => { + if (!chainName1 || !chainName2 || !address) return; + log('Initializing SFuelWarning', chainName1, chainName2, hubChain, address); + createStations() + + }, [chainName1, chainName2, hubChain, address]) + + + useEffect(() => { + if (!fromStationData.ok || (hubChainStation && !hubStationData.ok)) { + setSFuelStatus('error'); + setSFuelOk(false); + } else { + if (!toStationData.ok) { + setSFuelStatus('warning'); + setSFuelOk(false); + } else { + setSFuelStatus('action'); + setSFuelOk(true); + } + } + }, [fromStationData, toStationData, hubStationData]) + + + useEffect(() => { + updateStationsData() + const intervalId = setInterval(() => { + updateStationsData() + }, 10000) + return () => { + clearInterval(intervalId) // Clear interval on component unmount + } + }, [fromChainStation, toChainStation, hubChainStation]) + + function createStations() { + setFromChainStation(new Station(chainName1, mpc)) + setToChainStation(new Station(chainName2, mpc)) + if (hubChain) setHubChainStation(new Station(hubChain, mpc)) + } + + async function updateStationsData() { + if (fromChainStation) setFromStationData(await fromChainStation.getData(address)); + if (toChainStation) setToStationData(await toChainStation.getData(address)); + if (hubChainStation) setHubStationData(await hubChainStation.getData(address)); + setLoading(false) + } + + async function doPoW() { + let fromPowRes; + let toPowRes; + let hubPowRes; + + setMining(true); + + if (fromChainStation && !fromStationData.ok) { + log(`Doing PoW on ${fromChainStation.chainName}`); + fromPowRes = await fromChainStation.doPoW(address); + } + if (toChainStation && !toStationData.ok) { + log(`Doing PoW on ${toChainStation.chainName}`); + toPowRes = await toChainStation.doPoW(address); + } + if (hubChainStation && !hubStationData.ok) { + log(`Doing PoW on ${hubChainStation.chainName}`); + hubPowRes = await hubChainStation.doPoW(address); + } + + if ( + (fromPowRes && !fromPowRes.ok) || + (toPowRes && !toPowRes.ok) || + (hubPowRes && !hubPowRes.ok) + ) { + log('PoW failed!'); + if (fromPowRes) log(chainName1, fromPowRes.message); + if (toPowRes) log(chainName2, toPowRes.message); + if (hubPowRes) log(hubChain, hubPowRes.message); + // window.open(DEFAULT_FAUCET_URL, '_blank'); + } + await updateStationsData(); + setMining(false); + } + + const noEth = (fromStationData && !fromStationData.ok && chainName1 === MAINNET_CHAIN_NAME); + const noEthDest = (toStationData && !toStationData.ok && chainName2 === MAINNET_CHAIN_NAME); + + function getSFuelText() { + if (noEth || (fromStationData && fromStationData.ok && noEthDest)) { + return SFUEL_TEXT['gas'][sFuelStatus]; + } + return SFUEL_TEXT['sfuel'][sFuelStatus]; + } + + return ( +
+

+ ⛽ {getSFuelText()} +

+ { + !noEth && ((fromStationData && !fromStationData.ok) || !noEthDest) ? (
+ {mining ? + Getting sFUEL... + : } +
+ ) : null + } +
+
) +} \ No newline at end of file diff --git a/src/components/SFuelWarning/index.ts b/src/components/SFuelWarning/index.ts new file mode 100644 index 0000000..d96f36f --- /dev/null +++ b/src/components/SFuelWarning/index.ts @@ -0,0 +1 @@ +export { default } from "./SFuelWarning"; \ No newline at end of file diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index 2350f8c..98db15d 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -46,7 +46,7 @@ export default function TokenList() { updateTokenBalances(address) // Fetch users immediately on component mount const intervalId = setInterval(() => { updateTokenBalances(address) - }, 10000) // Fetch users every 10 seconds + }, 10000) return () => { clearInterval(intervalId) // Clear interval on component unmount diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 87d7829..1f0d705 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -1,6 +1,8 @@ import React, { useEffect } from 'react' import { useCollapseStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' +import { useSFuelStore } from '../../store/SFuelStore' + import ChainsList from '../ChainsList' import AmountInput from '../AmountInput' @@ -12,6 +14,7 @@ import { TokenBalance } from '../TokenList' import DestTokenBalance from '../DestTokenBalance' import ErrorMessage from '../ErrorMessage' import CommunityPool from '../CommunityPool' +import SFuelWarning from '../SFuelWarning' import cmn from '../../styles/cmn.module.scss' import { cls } from '../../core/helper' @@ -46,6 +49,8 @@ export function WidgetBody(props) { const transferInProgress = useMetaportStore((state) => state.transferInProgress) + const sFuelOk = useSFuelStore((state) => state.sFuelOk); + useEffect(() => { setChainName1(mpc.config.chains ? mpc.config.chains[0] : '') setChainName2(mpc.config.chains ? mpc.config.chains[1] : '') @@ -61,7 +66,7 @@ export function WidgetBody(props) { const showTo = !expandedFrom && !expandedTokens && !errorMessage && !expandedCP const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP - const showStepper = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP + const showStepper = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && sFuelOk const showCP = !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME const showError = !!errorMessage @@ -135,6 +140,8 @@ export function WidgetBody(props) { + + diff --git a/src/core/constants.ts b/src/core/constants.ts index 029739d..9efab24 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -35,7 +35,7 @@ export const UNWRAP_ACTION = 'unwrap' // tslint:disable-next-line export const MAX_APPROVE_AMOUNT = '115792089237316195423570985008687907853269984665640564039457584007913129639935' // (2^256 - 1 ) -export const DEFAULT_MIN_SFUEL_WEI = '21000000000000' +export const DEFAULT_MIN_SFUEL_WEI = 21000000000000n export const DEFAULT_ERC20_DECIMALS = '18' export const DEFAULT_ERROR_MSG = 'Ooops... Something went wrong...' @@ -76,12 +76,10 @@ export const SFUEL_CHEKCS_INTERVAL = 8 export const SFUEL_TEXT = { sfuel: { - action: '', warning: 'You may need sFUEL on the destination chain', error: 'You need sFUEL to perform a transfer', }, gas: { - action: '', warning: 'You may need ETH on the destination chain', error: 'You need ETH to perform a transfer', }, diff --git a/src/core/faucet.ts b/src/core/faucet.ts index 6ad0d9e..d4e681f 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -21,31 +21,39 @@ * @copyright SKALE Labs 2023-Present */ -// TODO! - -// import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants'; - -// function getAddress(chainName: string, skaleNetwork: string) { -// if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS; -// const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; -// return faucet[chainName].address; -// } - -// function getFunc(chainName: string, skaleNetwork: string) { -// if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_FUNCSIG; -// const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; -// return faucet[chainName].func; -// } - -// export function isFaucetAvailable(chainName: string, skaleNetwork: string) { -// if (!FAUCET_DATA[skaleNetwork]) return false; -// const keys = Object.keys(FAUCET_DATA[skaleNetwork]); -// return keys.includes(chainName); -// } - -// export function getFuncData(web3: Web3, chainName: string, address: string, skaleNetwork: string) { -// const faucetAddress = getAddress(chainName, skaleNetwork); -// const functionSig = getFunc(chainName, skaleNetwork); -// const functionParam = web3.eth.abi.encodeParameter('address', address); -// return { to: faucetAddress, data: functionSig + functionParam.slice(2) }; -// } +import { Provider, AbiCoder } from 'ethers' +import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants' + +function getAddress(chainName: string, skaleNetwork: string) { + if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS; + const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; + return faucet[chainName].address; +} + +function getFunc(chainName: string, skaleNetwork: string) { + if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_FUNCSIG; + const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; + return faucet[chainName].func; +} + +export function isFaucetAvailable(chainName: string, skaleNetwork: string) { + if (!FAUCET_DATA[skaleNetwork]) return false; + const keys = Object.keys(FAUCET_DATA[skaleNetwork]); + return keys.includes(chainName); +} + +export function getFuncData( + provider: Provider, + chainName: string, + address: string, + skaleNetwork: string +) { + const faucetAddress = getAddress(chainName, skaleNetwork); + const functionSig = getFunc(chainName, skaleNetwork); + + const encoder = new AbiCoder() + const functionParam = encoder.encode(['address'], [address]) + + // const functionParam = web3.eth.abi.encodeParameter('address', address); + return { to: faucetAddress, data: functionSig + functionParam.slice(2) }; +} diff --git a/src/core/sfuel.ts b/src/core/sfuel.ts index 6b93762..4ed6e5a 100644 --- a/src/core/sfuel.ts +++ b/src/core/sfuel.ts @@ -21,111 +21,78 @@ * @copyright SKALE Labs 2022-Present */ -// import debug from 'debug'; -// import { AnonymousPoW } from "@skaleproject/pow-ethers"; - -// import { getChainEndpoint, initWeb3 } from '../core/core'; -// import { getFuncData, isFaucetAvailable } from '../core/faucet'; -// import { DEFAULT_MIN_SFUEL_WEI, DEFAULT_FAUCET_URL, MAINNET_CHAIN_NAME } from '../core/constants'; - -// debug.enable('*'); -// const log = debug('metaport:Widget'); - -// function getFaucetUrl(chainsMetadata: object, chainName: string): string { -// if (chainsMetadata && chainsMetadata[chainName]) return chainsMetadata[chainName].faucetUrl; -// return DEFAULT_FAUCET_URL; -// } - -// function getMinSfuelWei(chainName: string, chainsMetadata?: object): string { -// if (chainsMetadata && chainsMetadata[chainName] && chainsMetadata[chainName].minSfuelWei) { -// return chainsMetadata[chainName].minSfuelWei; -// } else { -// return DEFAULT_MIN_SFUEL_WEI; -// } -// } - -// async function getSfuelBalance(web3: any, address: string): Promise { -// //return await provider.getBalance(address); -// // TODO! -// console.log(web3, address); -// return ''; -// } - -// export interface StationData { -// faucetUrl: string; -// minSfuelWei: string; -// balance: string; -// ok: boolean; -// } - -// export interface StationPowRes { -// message: string; -// ok: boolean; -// } - -// export class Station { - -// endpoint: string; -// web3: any; // todo! - -// constructor( -// public chainName: string, -// public skaleNetwork: string, -// public mainnetEndpoint?: string, -// public chainsMetadata?: object -// ) { -// this.chainName = chainName; -// this.skaleNetwork = skaleNetwork; - -// this.endpoint = getChainEndpoint(chainName, mainnetEndpoint, skaleNetwork); - -// this.web3 = initWeb3(this.endpoint); -// this.chainsMetadata = chainsMetadata; -// } - -// async getData(address: string): Promise { -// try { -// const minSfuelWei = getMinSfuelWei(this.chainName, this.chainsMetadata); -// const balance = await getSfuelBalance(this.web3, address); -// return { -// faucetUrl: getFaucetUrl(this.chainsMetadata, this.chainName), -// minSfuelWei, -// balance, -// ok: Number(balance) >= Number(minSfuelWei) -// } -// } catch (e) { -// log(`ERROR: getSFuelData for ${this.chainName} failed!`); -// log(e); -// return { -// faucetUrl: undefined, minSfuelWei: undefined, balance: undefined, ok: undefined -// }; -// } -// } - -// async doPoW(address: string): Promise { -// if (!this.chainName || !isFaucetAvailable(this.chainName, this.skaleNetwork)) { -// log('WARNING: PoW is not available for this chain'); -// if (this.chainName === MAINNET_CHAIN_NAME) { -// return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; -// } -// return { ok: false, message: 'PoW is not available for this chain' }; -// } -// log('Mining sFUEL for ' + address + ' on ' + this.chainName + '...'); -// try { -// const endpoint = getChainEndpoint(this.chainName, undefined, this.skaleNetwork); -// const web3 = initWeb3(endpoint); -// const anon = new AnonymousPoW({ rpcUrl: endpoint }); -// await (await anon.send(getFuncData( -// web3, -// this.chainName, -// address, -// this.skaleNetwork -// ))).wait(); -// return { ok: true, message: 'PoW finished successfully' } -// } catch (e) { -// log('ERROR: PoW failed!'); -// log(e); -// return { ok: false, message: e.message }; -// } -// } -// } +import debug from 'debug'; +import { Provider } from 'ethers'; +import { AnonymousPoW } from "@skaleproject/pow-ethers"; + +import MetaportCore from './metaport' +import { getFuncData, isFaucetAvailable } from '../core/faucet' +import { MAINNET_CHAIN_NAME, DEFAULT_MIN_SFUEL_WEI } from '../core/constants' + + +debug.enable('*'); +const log = debug('metaport:sfuel'); + + +export interface StationData { + balance: bigint + ok: boolean +} + +export interface StationPowRes { + message: string + ok: boolean +} + +export class Station { + + endpoint: string; + provider: Provider; + + constructor( + public chainName: string, + public mpc: MetaportCore + ) { + this.chainName = chainName + this.mpc = mpc + this.provider = mpc.provider(chainName); + } + + async getData(address: string): Promise { + try { + const balance = await this.provider.getBalance(address); + return { balance, ok: balance >= DEFAULT_MIN_SFUEL_WEI } + } catch (e) { + log(`ERROR: getSFuelData for ${this.chainName} failed!`); + log(e); + return { balance: undefined, ok: undefined }; + } + } + + async doPoW(address: string): Promise { + // return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; + if (!this.chainName || !isFaucetAvailable(this.chainName, this.mpc.config.skaleNetwork)) { + log('WARNING: PoW is not available for this chain'); + if (this.chainName === MAINNET_CHAIN_NAME) { + return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; + } + return { ok: false, message: 'PoW is not available for this chain' }; + } + log('Mining sFUEL for ' + address + ' on ' + this.chainName + '...'); + try { + const endpoint = this.mpc.endpoint(this.chainName) + const anon = new AnonymousPoW({ rpcUrl: endpoint }); + await (await anon.send(getFuncData( + this.provider, + this.chainName, + address, + this.mpc.config.skaleNetwork + ))).wait(); + return { ok: true, message: 'PoW finished successfully' } + } catch (e) { + log('ERROR: PoW failed!'); + log(e); + return { ok: false, message: e.message }; + } + } +} diff --git a/src/store/CommunityPoolStore.ts b/src/store/CommunityPoolStore.ts index f36bdf0..bc0754a 100644 --- a/src/store/CommunityPoolStore.ts +++ b/src/store/CommunityPoolStore.ts @@ -27,7 +27,6 @@ import { MainnetChain, SChain } from '@skalenetwork/ima-js' import * as interfaces from '../core/interfaces' import { getEmptyCommunityPoolData, getCommunityPoolData } from '../core/community_pool' import MetaportCore from '../core/metaport' -import { MAINNET_CHAIN_NAME } from '../core/constants' interface CommunityPoolState { @@ -70,7 +69,7 @@ export const useCPStore = create()((set, get) => ({ set({ chainName: chainName1, cpData: cpData, - amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : null + amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : '' }) } })) diff --git a/src/store/SFuelStore.ts b/src/store/SFuelStore.ts new file mode 100644 index 0000000..d9a2ce4 --- /dev/null +++ b/src/store/SFuelStore.ts @@ -0,0 +1,92 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file SFuelStore.ts + * @copyright SKALE Labs 2023-Present + */ + +import { create } from 'zustand' +import { MainnetChain, SChain } from '@skalenetwork/ima-js' + +import * as interfaces from '../core/interfaces' +import { Station, StationData } from '../core/sfuel' +import MetaportCore from '../core/metaport' + + +interface SFuelState { + loading: boolean + setLoading: (loading: boolean) => void + mining: boolean + setMining: (loading: boolean) => void + + fromChainStation: Station + setFromChainStation: (station: Station) => void + + toChainStation: Station + setToChainStation: (station: Station) => void + + hubChainStation: Station + setHubChainStation: (station: Station) => void + + sFuelStatus: 'action' | 'warning' | 'error'; + setSFuelStatus: (status: 'action' | 'warning' | 'error') => void + + sFuelOk: boolean + setSFuelOk: (loading: boolean) => void + + fromStationData: StationData; + setFromStationData: (data: StationData) => void; + + toStationData: StationData; + setToStationData: (data: StationData) => void; + + hubStationData: StationData; + setHubStationData: (data: StationData) => void; +} + +export const useSFuelStore = create()((set, get) => ({ + loading: true, + setLoading: (loading: boolean) => set(() => ({ loading: loading })), + mining: false, + setMining: (mining: boolean) => set(() => ({ mining: mining })), + + fromChainStation: undefined, + setFromChainStation: (station: Station) => set({ fromChainStation: station }), + + toChainStation: undefined, + setToChainStation: (station: Station) => set({ toChainStation: station }), + + hubChainStation: undefined, + setHubChainStation: (station: Station) => set({ hubChainStation: station }), + + sFuelStatus: 'action', + setSFuelStatus: (status: 'action' | 'warning' | 'error') => set({ sFuelStatus: status }), + + sFuelOk: false, + setSFuelOk: (sFuelOk: boolean) => set(() => ({ sFuelOk: sFuelOk })), + + fromStationData: { ok: false, balance: null }, + setFromStationData: (data: StationData) => set({ fromStationData: data }), + + toStationData: { ok: false, balance: null }, + setToStationData: (data: StationData) => set({ toStationData: data }), + + hubStationData: { ok: false, balance: null }, + setHubStationData: (data: StationData) => set({ hubStationData: data }), +})) From a463b13c289413c65e8ea55976fcec777c5e4d76 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 28 Aug 2023 17:40:56 +0100 Subject: [PATCH 031/110] PoW sFUEL faucet --- package.json | 1 - src/core/faucet.ts | 38 +++++++++++++++--- src/core/interfaces/index.ts | 2 + src/core/miner.ts | 75 ++++++++++++++++++++++++++++++++++++ src/core/sfuel.ts | 17 +++----- src/metadata/faucet.json | 12 ++++++ 6 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 src/core/miner.ts diff --git a/package.json b/package.json index 2ad2617..c1f9603 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "@mui/material": "^5.8.1", "@rainbow-me/rainbowkit": "^1.0.8", "@skalenetwork/ima-js": "2.0.0-develop.3", - "@skaleproject/pow-ethers": "0.3.3", "coingecko-api-v3": "^0.0.28", "react-jazzicon": "^1.0.4", "viem": "^1.5.3", diff --git a/src/core/faucet.ts b/src/core/faucet.ts index d4e681f..1d7be5b 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -21,8 +21,13 @@ * @copyright SKALE Labs 2023-Present */ -import { Provider, AbiCoder } from 'ethers' +import { Provider, Wallet, JsonRpcProvider, AbiCoder, TransactionResponse } from 'ethers'; + +import SkalePowMiner from './miner' import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants' +import { AddressType, SkaleNetwork } from './interfaces'; +import MetaportCore from './metaport'; + function getAddress(chainName: string, skaleNetwork: string) { if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS; @@ -30,30 +35,51 @@ function getAddress(chainName: string, skaleNetwork: string) { return faucet[chainName].address; } + function getFunc(chainName: string, skaleNetwork: string) { if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_FUNCSIG; const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; return faucet[chainName].func; } + export function isFaucetAvailable(chainName: string, skaleNetwork: string) { if (!FAUCET_DATA[skaleNetwork]) return false; const keys = Object.keys(FAUCET_DATA[skaleNetwork]); return keys.includes(chainName); } -export function getFuncData( - provider: Provider, + +function getFuncData( chainName: string, address: string, skaleNetwork: string ) { const faucetAddress = getAddress(chainName, skaleNetwork); const functionSig = getFunc(chainName, skaleNetwork); - const encoder = new AbiCoder() const functionParam = encoder.encode(['address'], [address]) - - // const functionParam = web3.eth.abi.encodeParameter('address', address); return { to: faucetAddress, data: functionSig + functionParam.slice(2) }; } + + +export async function getSFuel( + chainName: string, + address: AddressType, + mpc: MetaportCore +): Promise { + const endpoint = mpc.endpoint(chainName) + const miner = new SkalePowMiner() + const provider = new JsonRpcProvider(endpoint); + const wallet = Wallet.createRandom().connect(provider) + let nonce: number = await wallet.getNonce(); + const mineFreeGasResult = await miner.mineGasForTransaction(nonce, 100000, wallet.address); + const { to, data } = getFuncData(chainName, address, mpc.config.skaleNetwork) + return await wallet.sendTransaction({ + from: wallet.address, + to, + data, + nonce, + gasPrice: mineFreeGasResult + }) +} \ No newline at end of file diff --git a/src/core/interfaces/index.ts b/src/core/interfaces/index.ts index bb0ca49..e76f9bb 100644 --- a/src/core/interfaces/index.ts +++ b/src/core/interfaces/index.ts @@ -31,3 +31,5 @@ export * from './CheckRes' export * from './TransactionHistory' export * from './CommunityPoolData' export * from './TokenMetadata' + +export type AddressType = `0x${string}` \ No newline at end of file diff --git a/src/core/miner.ts b/src/core/miner.ts new file mode 100644 index 0000000..e12debd --- /dev/null +++ b/src/core/miner.ts @@ -0,0 +1,75 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file miner.ts + * @copyright SKALE Labs 2023-Present + */ + +import BN from "bn.js"; +import { isHexString, getNumber, randomBytes, keccak256, hexlify, toBeHex } from 'ethers' + + +interface Params { + difficulty?: BN; +} + + +export default class SkalePowMiner { + + public difficulty: BN = new BN(1); + + constructor(params?: Params) { + if (params && params.difficulty) this.difficulty = params.difficulty; + } + + public async mineGasForTransaction(nonce: string | number, gas: string | number, from: string, bytes?: string): Promise { + let address = from; + nonce = isHexString(nonce) ? getNumber(nonce) : nonce as number; + gas = isHexString(gas) ? getNumber(gas) : gas as number; + return await this.mineFreeGas(gas as number, address, nonce as number, bytes); + } + + public async mineFreeGas(gasAmount: number, address: string, nonce: number, bytes?: string) { + let nonceHash = new BN((keccak256(toBeHex(nonce, 32))).slice(2), 16); + let addressHash = new BN((keccak256(address) as string).slice(2), 16); + let nonceAddressXOR = nonceHash.xor(addressHash) + let maxNumber = new BN(2).pow(new BN(256)).sub(new BN(1)); + let divConstant = maxNumber.div(this.difficulty); + let candidate: BN; + let _bytes: string; + let iterations = 0 + + while (true) { + const _bt = hexlify(randomBytes(32)) + _bytes = _bt.slice(2); + candidate = new BN(bytes ?? _bytes, 16); + let candidateHash = new BN((keccak256(_bt)).slice(2), 16); + let resultHash = nonceAddressXOR.xor(candidateHash); + let externalGas = divConstant.div(resultHash).toNumber(); + if (externalGas >= gasAmount) { + break; + } + // every 2k iterations, yield to the event loop + if (iterations++ % 2_000 === 0) { + await new Promise((resolve) => setTimeout(resolve, 0)); + } + } + return BigInt(candidate) + } +} \ No newline at end of file diff --git a/src/core/sfuel.ts b/src/core/sfuel.ts index 4ed6e5a..acdee35 100644 --- a/src/core/sfuel.ts +++ b/src/core/sfuel.ts @@ -23,10 +23,10 @@ import debug from 'debug'; import { Provider } from 'ethers'; -import { AnonymousPoW } from "@skaleproject/pow-ethers"; import MetaportCore from './metaport' -import { getFuncData, isFaucetAvailable } from '../core/faucet' +import { AddressType } from './interfaces' +import { isFaucetAvailable, getSFuel } from './faucet' import { MAINNET_CHAIN_NAME, DEFAULT_MIN_SFUEL_WEI } from '../core/constants' @@ -58,7 +58,7 @@ export class Station { this.provider = mpc.provider(chainName); } - async getData(address: string): Promise { + async getData(address: AddressType): Promise { try { const balance = await this.provider.getBalance(address); return { balance, ok: balance >= DEFAULT_MIN_SFUEL_WEI } @@ -69,7 +69,7 @@ export class Station { } } - async doPoW(address: string): Promise { + async doPoW(address: AddressType): Promise { // return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; if (!this.chainName || !isFaucetAvailable(this.chainName, this.mpc.config.skaleNetwork)) { log('WARNING: PoW is not available for this chain'); @@ -80,14 +80,7 @@ export class Station { } log('Mining sFUEL for ' + address + ' on ' + this.chainName + '...'); try { - const endpoint = this.mpc.endpoint(this.chainName) - const anon = new AnonymousPoW({ rpcUrl: endpoint }); - await (await anon.send(getFuncData( - this.provider, - this.chainName, - address, - this.mpc.config.skaleNetwork - ))).wait(); + await getSFuel(this.chainName, address, this.mpc) return { ok: true, message: 'PoW finished successfully' } } catch (e) { log('ERROR: PoW failed!'); diff --git a/src/metadata/faucet.json b/src/metadata/faucet.json index d40e6e5..8b1163f 100644 --- a/src/metadata/faucet.json +++ b/src/metadata/faucet.json @@ -33,6 +33,18 @@ "staging-severe-violet-wezen": { "address": "0x37412E23bBF1058A7e325A16C01FF654E1D53562", "func": "0x0c11dedd" + }, + "staging-legal-crazy-castor": { + "address": "0x436389289aEAFefD1d7471b7FbEc67539Bde3E34", + "func": "0x6a627842" + }, + "staging-utter-unripe-menkar": { + "address": "0x84b7265Bc964BB69b4275d4Dac4df0FD87556960", + "func": "0x0c11dedd" + }, + "staging-faint-slimy-achird": { + "address": "0xfd56A3456fbAB0fc013213edCc830B9d32403C8B", + "func": "0x0c11dedd" } }, "legacy": null, From 4eb413f874b07bfbbcba982bec85e5dfb10acf12 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 28 Aug 2023 18:14:29 +0100 Subject: [PATCH 032/110] Remove web3-utils --- src/core/constants.ts | 4 ++-- src/core/fee_calculator.ts | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/core/constants.ts b/src/core/constants.ts index 9efab24..bf88ec4 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -76,11 +76,11 @@ export const SFUEL_CHEKCS_INTERVAL = 8 export const SFUEL_TEXT = { sfuel: { - warning: 'You may need sFUEL on the destination chain', + warning: 'You need sFUEL on the destination chain', error: 'You need sFUEL to perform a transfer', }, gas: { - warning: 'You may need ETH on the destination chain', + warning: 'You need ETH on the destination chain', error: 'You need ETH to perform a transfer', }, } diff --git a/src/core/fee_calculator.ts b/src/core/fee_calculator.ts index f8e2f90..a9e5e2a 100644 --- a/src/core/fee_calculator.ts +++ b/src/core/fee_calculator.ts @@ -21,11 +21,10 @@ * @copyright SKALE Labs 2022-Present */ -import { fromWei as _fromWei, toBN } from 'web3-utils' import debug from 'debug' - +import { fromWei } from './convertation' import { CoinGeckoClient } from 'coingecko-api-v3' -import * as interfaces from './interfaces/index' +import { DEFAULT_ERC20_DECIMALS } from './constants' debug.enable('*') const log = debug('metaport:components:fee_calculator') @@ -33,11 +32,11 @@ const log = debug('metaport:components:fee_calculator') export async function getTransactionFee(): Promise { // todo: get actual gas limit for transfer // todo: get actual gas price - const gasLimit = toBN('250000') - const gasPrice = toBN('10000000000') + const gasLimit = 250000n + const gasPrice = 10000000000n - const amountWei = gasLimit.mul(gasPrice) - const amountEth = _fromWei(amountWei) + const amountWei = gasLimit * gasPrice + const amountEth = fromWei(amountWei, DEFAULT_ERC20_DECIMALS) const client = new CoinGeckoClient({ timeout: 10000, From 480fa553758ae5daf9946a23d708e3f62610f96b Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 28 Aug 2023 19:00:57 +0100 Subject: [PATCH 033/110] Optimize miner script, update prettier rules --- .prettierrc | 2 +- package.json | 3 +- src/components/ChainsList/ChainsList.tsx | 24 +- .../CommunityPool/CommunityPool.tsx | 47 ++- .../DestTokenBalance/DestTokenBalance.tsx | 10 +- src/components/ErrorMessage/ErrorMessage.tsx | 4 +- src/components/SFuelWarning/SFuelWarning.tsx | 355 +++++++++--------- src/components/SFuelWarning/index.ts | 2 +- src/components/SkConnect/SkConnect.tsx | 27 +- src/components/Stepper/SkStepper.tsx | 23 +- src/components/TokenList/TokenList.tsx | 2 +- .../TokenListSection/TokenListSection.tsx | 18 +- src/components/WidgetBody/WidgetBody.tsx | 9 +- src/components/WidgetUI/WidgetUI.tsx | 4 +- src/core/actions/action.ts | 4 +- src/core/actions/checks.ts | 6 +- src/core/actions/erc20.ts | 92 ++++- src/core/actions/index.ts | 14 +- src/core/community_pool.ts | 41 +- src/core/constants.ts | 3 +- src/core/dataclasses/StepMetadata.ts | 6 +- src/core/ethers.ts | 5 +- src/core/explorer.ts | 7 +- src/core/faucet.ts | 81 ++-- src/core/helper.ts | 6 +- src/core/interfaces/index.ts | 2 +- src/core/metaport.ts | 20 +- src/core/miner.ts | 83 ++-- src/core/network.ts | 26 +- src/core/sfuel.ts | 91 +++-- src/core/themes.ts | 11 +- src/core/transfer_steps.ts | 10 +- src/core/wagmi_network.ts | 4 +- src/metadata/metaportConfigStaging.ts | 6 +- src/store/CommunityPoolStore.ts | 20 +- src/store/MetaportState.ts | 32 +- src/store/SFuelStore.ts | 15 +- 37 files changed, 680 insertions(+), 435 deletions(-) diff --git a/.prettierrc b/.prettierrc index b13587a..37bf3d9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,6 +2,6 @@ "semi": false, "trailingComma": "all", "singleQuote": true, - "printWidth": 120, + "printWidth": 100, "endOfLine": "auto" } \ No newline at end of file diff --git a/package.json b/package.json index c1f9603..8038e38 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"" }, "devDependencies": { - "@babel/core": "7.22.10", "@storybook/addon-essentials": "7.3.2", "@storybook/addon-interactions": "7.3.2", "@storybook/addon-links": "7.3.2", @@ -51,7 +50,6 @@ "@vitejs/plugin-react": "4.0.4", "@vitest/coverage-v8": "0.34.1", "autoprefixer": "10.4.14", - "babel-loader": "9.1.3", "eslint": "8.46.0", "eslint-config-prettier": "9.0.0", "eslint-config-standard-with-typescript": "37.0.0", @@ -85,6 +83,7 @@ "@mui/material": "^5.8.1", "@rainbow-me/rainbowkit": "^1.0.8", "@skalenetwork/ima-js": "2.0.0-develop.3", + "bn.js": "^5.2.1", "coingecko-api-v3": "^0.0.28", "react-jazzicon": "^1.0.4", "viem": "^1.5.3", diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index 7218df9..b284f38 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -88,13 +88,21 @@ export default function ChainsList(props: { )} -
+
{schainNames.map((name) => ( -
@@ -203,7 +213,12 @@ export default function CommunityPool() {

+ balance={cpData.balance} + symbol="ETH" + truncate={4} + size="sm" + primary + />
@@ -221,15 +236,17 @@ export default function CommunityPool() { disabled={!!loading} />
-

+

ETH

diff --git a/src/components/DestTokenBalance/DestTokenBalance.tsx b/src/components/DestTokenBalance/DestTokenBalance.tsx index eb7b7e4..7fb4684 100644 --- a/src/components/DestTokenBalance/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance/DestTokenBalance.tsx @@ -15,7 +15,7 @@ export default function DestTokenBalance() { updateDestTokenBalance(address) // Fetch users immediately on component mount const intervalId = setInterval(() => { updateDestTokenBalance(address) - }, 10000) + }, 10000) return () => { clearInterval(intervalId) // Clear interval on component unmount } @@ -23,5 +23,11 @@ export default function DestTokenBalance() { if (!token) return - return + return ( + + ) } diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index 8806ea2..52d34a5 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -23,7 +23,9 @@ export default function Error(props: { errorMessage: ErrorMessage }) { if (!props.errorMessage) return return (
-
{ERROR_ICONS[props.errorMessage.icon]}
+
+ {ERROR_ICONS[props.errorMessage.icon]} +

state.mpc) - const chainName1 = useMetaportStore((state) => state.chainName1) - const chainName2 = useMetaportStore((state) => state.chainName2) - const token = useMetaportStore((state) => state.token) - - const loading = useSFuelStore((state) => state.loading) - const setLoading = useSFuelStore((state) => state.setLoading) - const mining = useSFuelStore((state) => state.mining) - const setMining = useSFuelStore((state) => state.setMining) - - const fromChainStation = useSFuelStore((state) => state.fromChainStation); - const setFromChainStation = useSFuelStore((state) => state.setFromChainStation); - - const toChainStation = useSFuelStore((state) => state.toChainStation); - const setToChainStation = useSFuelStore((state) => state.setToChainStation); - - const hubChainStation = useSFuelStore((state) => state.hubChainStation); - const setHubChainStation = useSFuelStore((state) => state.setHubChainStation); - - const sFuelStatus = useSFuelStore((state) => state.sFuelStatus); - const setSFuelStatus = useSFuelStore((state) => state.setSFuelStatus); - - const sFuelOk = useSFuelStore((state) => state.sFuelOk); - const setSFuelOk = useSFuelStore((state) => state.setSFuelOk); - - const fromStationData = useSFuelStore((state) => state.fromStationData); - const setFromStationData = useSFuelStore((state) => state.setFromStationData); - - const toStationData = useSFuelStore((state) => state.toStationData); - const setToStationData = useSFuelStore((state) => state.setToStationData); - - const hubStationData = useSFuelStore((state) => state.hubStationData); - const setHubStationData = useSFuelStore((state) => state.setHubStationData); - - const { address } = useAccount() - - let hubChain; - - if (token && token.connections[chainName2].hub) { - hubChain = token.connections[chainName2].hub + const mpc = useMetaportStore((state) => state.mpc) + const chainName1 = useMetaportStore((state) => state.chainName1) + const chainName2 = useMetaportStore((state) => state.chainName2) + const token = useMetaportStore((state) => state.token) + + const loading = useSFuelStore((state) => state.loading) + const setLoading = useSFuelStore((state) => state.setLoading) + const mining = useSFuelStore((state) => state.mining) + const setMining = useSFuelStore((state) => state.setMining) + + const fromChainStation = useSFuelStore((state) => state.fromChainStation) + const setFromChainStation = useSFuelStore((state) => state.setFromChainStation) + + const toChainStation = useSFuelStore((state) => state.toChainStation) + const setToChainStation = useSFuelStore((state) => state.setToChainStation) + + const hubChainStation = useSFuelStore((state) => state.hubChainStation) + const setHubChainStation = useSFuelStore((state) => state.setHubChainStation) + + const sFuelStatus = useSFuelStore((state) => state.sFuelStatus) + const setSFuelStatus = useSFuelStore((state) => state.setSFuelStatus) + + const sFuelOk = useSFuelStore((state) => state.sFuelOk) + const setSFuelOk = useSFuelStore((state) => state.setSFuelOk) + + const fromStationData = useSFuelStore((state) => state.fromStationData) + const setFromStationData = useSFuelStore((state) => state.setFromStationData) + + const toStationData = useSFuelStore((state) => state.toStationData) + const setToStationData = useSFuelStore((state) => state.setToStationData) + + const hubStationData = useSFuelStore((state) => state.hubStationData) + const setHubStationData = useSFuelStore((state) => state.setHubStationData) + + const { address } = useAccount() + + let hubChain + + if (token && token.connections[chainName2].hub) { + hubChain = token.connections[chainName2].hub + } + + useEffect(() => { + if (!chainName1 || !chainName2 || !address) return + log('Initializing SFuelWarning', chainName1, chainName2, hubChain, address) + createStations() + }, [chainName1, chainName2, hubChain, address]) + + useEffect(() => { + if (!fromStationData.ok || (hubChainStation && !hubStationData.ok)) { + setSFuelStatus('error') + setSFuelOk(false) + } else { + if (!toStationData.ok) { + setSFuelStatus('warning') + setSFuelOk(false) + } else { + setSFuelStatus('action') + setSFuelOk(true) + } } - - useEffect(() => { - if (!chainName1 || !chainName2 || !address) return; - log('Initializing SFuelWarning', chainName1, chainName2, hubChain, address); - createStations() - - }, [chainName1, chainName2, hubChain, address]) - - - useEffect(() => { - if (!fromStationData.ok || (hubChainStation && !hubStationData.ok)) { - setSFuelStatus('error'); - setSFuelOk(false); - } else { - if (!toStationData.ok) { - setSFuelStatus('warning'); - setSFuelOk(false); - } else { - setSFuelStatus('action'); - setSFuelOk(true); - } - } - }, [fromStationData, toStationData, hubStationData]) - - - useEffect(() => { - updateStationsData() - const intervalId = setInterval(() => { - updateStationsData() - }, 10000) - return () => { - clearInterval(intervalId) // Clear interval on component unmount - } - }, [fromChainStation, toChainStation, hubChainStation]) - - function createStations() { - setFromChainStation(new Station(chainName1, mpc)) - setToChainStation(new Station(chainName2, mpc)) - if (hubChain) setHubChainStation(new Station(hubChain, mpc)) + }, [fromStationData, toStationData, hubStationData]) + + useEffect(() => { + updateStationsData() + const intervalId = setInterval(() => { + updateStationsData() + }, 10000) + return () => { + clearInterval(intervalId) // Clear interval on component unmount } - - async function updateStationsData() { - if (fromChainStation) setFromStationData(await fromChainStation.getData(address)); - if (toChainStation) setToStationData(await toChainStation.getData(address)); - if (hubChainStation) setHubStationData(await hubChainStation.getData(address)); - setLoading(false) + }, [fromChainStation, toChainStation, hubChainStation]) + + function createStations() { + setFromChainStation(new Station(chainName1, mpc)) + setToChainStation(new Station(chainName2, mpc)) + if (hubChain) setHubChainStation(new Station(hubChain, mpc)) + } + + async function updateStationsData() { + if (fromChainStation) setFromStationData(await fromChainStation.getData(address)) + if (toChainStation) setToStationData(await toChainStation.getData(address)) + if (hubChainStation) setHubStationData(await hubChainStation.getData(address)) + setLoading(false) + } + + async function doPoW() { + let fromPowRes + let toPowRes + let hubPowRes + + setMining(true) + + if (fromChainStation && !fromStationData.ok) { + log(`Doing PoW on ${fromChainStation.chainName}`) + fromPowRes = await fromChainStation.doPoW(address) + } + if (toChainStation && !toStationData.ok) { + log(`Doing PoW on ${toChainStation.chainName}`) + toPowRes = await toChainStation.doPoW(address) + } + if (hubChainStation && !hubStationData.ok) { + log(`Doing PoW on ${hubChainStation.chainName}`) + hubPowRes = await hubChainStation.doPoW(address) } - async function doPoW() { - let fromPowRes; - let toPowRes; - let hubPowRes; - - setMining(true); - - if (fromChainStation && !fromStationData.ok) { - log(`Doing PoW on ${fromChainStation.chainName}`); - fromPowRes = await fromChainStation.doPoW(address); - } - if (toChainStation && !toStationData.ok) { - log(`Doing PoW on ${toChainStation.chainName}`); - toPowRes = await toChainStation.doPoW(address); - } - if (hubChainStation && !hubStationData.ok) { - log(`Doing PoW on ${hubChainStation.chainName}`); - hubPowRes = await hubChainStation.doPoW(address); - } - - if ( - (fromPowRes && !fromPowRes.ok) || - (toPowRes && !toPowRes.ok) || - (hubPowRes && !hubPowRes.ok) - ) { - log('PoW failed!'); - if (fromPowRes) log(chainName1, fromPowRes.message); - if (toPowRes) log(chainName2, toPowRes.message); - if (hubPowRes) log(hubChain, hubPowRes.message); - // window.open(DEFAULT_FAUCET_URL, '_blank'); - } - await updateStationsData(); - setMining(false); + if ( + (fromPowRes && !fromPowRes.ok) || + (toPowRes && !toPowRes.ok) || + (hubPowRes && !hubPowRes.ok) + ) { + log('PoW failed!') + if (fromPowRes) log(chainName1, fromPowRes.message) + if (toPowRes) log(chainName2, toPowRes.message) + if (hubPowRes) log(hubChain, hubPowRes.message) + // window.open(DEFAULT_FAUCET_URL, '_blank'); } + await updateStationsData() + setMining(false) + } - const noEth = (fromStationData && !fromStationData.ok && chainName1 === MAINNET_CHAIN_NAME); - const noEthDest = (toStationData && !toStationData.ok && chainName2 === MAINNET_CHAIN_NAME); + const noEth = fromStationData && !fromStationData.ok && chainName1 === MAINNET_CHAIN_NAME + const noEthDest = toStationData && !toStationData.ok && chainName2 === MAINNET_CHAIN_NAME - function getSFuelText() { - if (noEth || (fromStationData && fromStationData.ok && noEthDest)) { - return SFUEL_TEXT['gas'][sFuelStatus]; - } - return SFUEL_TEXT['sfuel'][sFuelStatus]; + function getSFuelText() { + if (noEth || (fromStationData && fromStationData.ok && noEthDest)) { + return SFUEL_TEXT['gas'][sFuelStatus] } - - return ( -

-

- ⛽ {getSFuelText()} -

- { - !noEth && ((fromStationData && !fromStationData.ok) || !noEthDest) ? (
- {mining ? - Getting sFUEL... - : } -
- ) : null - } -
- ) -} \ No newline at end of file + return SFUEL_TEXT['sfuel'][sFuelStatus] + } + + return ( + +
+

+ ⛽ {getSFuelText()} +

+ {!noEth && ((fromStationData && !fromStationData.ok) || !noEthDest) ? ( +
+ {mining ? ( + + Getting sFUEL... + + ) : ( + + )} +
+ ) : null} +
+
+ ) +} diff --git a/src/components/SFuelWarning/index.ts b/src/components/SFuelWarning/index.ts index d96f36f..3512fcb 100644 --- a/src/components/SFuelWarning/index.ts +++ b/src/components/SFuelWarning/index.ts @@ -1 +1 @@ -export { default } from "./SFuelWarning"; \ No newline at end of file +export { default } from './SFuelWarning' diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index f7151e8..322cd11 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -41,12 +41,23 @@ export default function SkConnect() { const transferInProgress = useMetaportStore((state) => state.transferInProgress) return ( - {({ account, chain, openAccountModal, openChainModal, openConnectModal, authenticationStatus, mounted }) => { + {({ + account, + chain, + openAccountModal, + openChainModal, + openConnectModal, + authenticationStatus, + mounted, + }) => { // Note: If your app doesn't use authentication, you // can remove all 'authenticationStatus' checks const ready = mounted && authenticationStatus !== 'loading' const connected = - ready && account && chain && (!authenticationStatus || authenticationStatus === 'authenticated') + ready && + account && + chain && + (!authenticationStatus || authenticationStatus === 'authenticated') return (
-

+

Connect a wallet to use SKALE Metaport

@@ -116,7 +123,17 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { {currentStep === stepsMetadata.length && (
-

+

{emoji} Transfer completed

diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index 98db15d..ff81587 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -46,7 +46,7 @@ export default function TokenList() { updateTokenBalances(address) // Fetch users immediately on component mount const intervalId = setInterval(() => { updateTokenBalances(address) - }, 10000) + }, 10000) return () => { clearInterval(intervalId) // Clear interval on component unmount diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index 5698796..ea2fce3 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -44,9 +44,23 @@ export default function TokenListSection(props: { >
- +
-

+

{getTokenName(props.tokens[key])}

diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 1f0d705..d7e5351 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -3,7 +3,6 @@ import { useCollapseStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' import { useSFuelStore } from '../../store/SFuelStore' - import ChainsList from '../ChainsList' import AmountInput from '../AmountInput' import SkStepper from '../Stepper' @@ -49,7 +48,7 @@ export function WidgetBody(props) { const transferInProgress = useMetaportStore((state) => state.transferInProgress) - const sFuelOk = useSFuelStore((state) => state.sFuelOk); + const sFuelOk = useSFuelStore((state) => state.sFuelOk) useEffect(() => { setChainName1(mpc.config.chains ? mpc.config.chains[0] : '') @@ -66,8 +65,10 @@ export function WidgetBody(props) { const showTo = !expandedFrom && !expandedTokens && !errorMessage && !expandedCP const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP - const showStepper = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && sFuelOk - const showCP = !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME + const showStepper = + !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && sFuelOk + const showCP = + !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME const showError = !!errorMessage return ( diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index f96813d..507587e 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -96,7 +96,9 @@ export function WidgetUI(props: { config: MetaportConfig }) { className={cls(styles.imaWidgetBody)} style={metaportTheme ? { ...metaportTheme.position, zIndex: metaportTheme.zIndex } : null} > -
{fabTop ? fabButton : null}
+
+ {fabTop ? fabButton : null} +
diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index 20a43a0..8cfa2f0 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -146,7 +146,9 @@ export class Action { // todo: use wrapper address! const destWrapperAddress = - this.mpc.config.connections[this.chainName2][this.token.type][this.token.keyname].chains[this.chainName1].wrapper + this.mpc.config.connections[this.chainName2][this.token.type][this.token.keyname].chains[ + this.chainName1 + ].wrapper if (this.token.isClone(this.chainName2) && destWrapperAddress) { this.destToken = mpc.tokenContract( chainName2, diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index 0e6a83c..af93a66 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -85,7 +85,11 @@ export async function checkERC20Balance( } } -export async function checkSFuelBalance(address: string, amount: string, sChain: SChain): Promise { +export async function checkSFuelBalance( + address: string, + amount: string, + sChain: SChain, +): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes try { diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index fc19744..6ee8587 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -53,9 +53,14 @@ export class TransferERC20S2S extends TransferAction { )) as SChain if (!checkResAllowance.res) { this.updateState('approve') - const approveTx = await sChain.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, sChain.erc20.address, { - address: this.address, - }) + const approveTx = await sChain.erc20.approve( + this.token.keyname, + MAX_APPROVE_AMOUNT, + sChain.erc20.address, + { + address: this.address, + }, + ) const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) externalEvents.transactionCompleted(approveTx, txBlock.timestamp, this.chainName1, 'approve') @@ -93,7 +98,12 @@ export class TransferERC20S2S extends TransferAction { } async preAction() { - const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.sourceToken) + const checkResBalance = await checkERC20Balance( + this.address, + this.amount, + this.token, + this.sourceToken, + ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) return @@ -149,21 +159,33 @@ export class WrapERC20S extends Action { sChain.erc20.addToken(`wrap_${this.token.keyname}`, wrapperToken) if (!checkResAllowance.res) { this.updateState('approveWrap') - const approveTx = await sChain.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, this.token.address, { - address: this.address, - }) + const approveTx = await sChain.erc20.approve( + this.token.keyname, + MAX_APPROVE_AMOUNT, + this.token.address, + { + address: this.address, + }, + ) const txBlock = await this.sChain1.provider.getBlock(approveTx.blockNumber) this.updateState('approveWrapDone', approveTx.hash, txBlock.timestamp) } this.updateState('wrap') const amountWei = toWei(this.amount, this.token.meta.decimals) - const tx = await sChain.erc20.wrap(`wrap_${this.token.keyname}`, amountWei, { address: this.address }) + const tx = await sChain.erc20.wrap(`wrap_${this.token.keyname}`, amountWei, { + address: this.address, + }) const block = await this.sChain1.provider.getBlock(tx.blockNumber) this.updateState('wrapDone', tx.hash, block.timestamp) } async preAction() { - const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.unwrappedToken) + const checkResBalance = await checkERC20Balance( + this.address, + this.amount, + this.token, + this.unwrappedToken, + ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) return @@ -249,7 +271,12 @@ export class UnWrapERC20S extends Action { async preAction() { log('preAction: UnWrapERC20S') const tokenContract = this.sChain1.erc20.tokens[this.token.keyname] - const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, tokenContract) + const checkResBalance = await checkERC20Balance( + this.address, + this.amount, + this.token, + tokenContract, + ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) return @@ -272,7 +299,9 @@ export class TransferERC20M2S extends TransferAction { const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain if (!checkResAllowance.res) { this.updateState('approve') - const approveTx = await mainnet.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, { address: this.address }) + const approveTx = await mainnet.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, { + address: this.address, + }) const txBlock = await mainnet.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) } @@ -290,11 +319,22 @@ export class TransferERC20M2S extends TransferAction { await this.sChain2.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination) this.updateState('received') log('TransferERC20M2S:execute - tokens received to destination chain') - externalEvents.transferComplete(tx.hash, this.chainName1, this.chainName2, this.token.keyname, false) + externalEvents.transferComplete( + tx.hash, + this.chainName1, + this.chainName2, + this.token.keyname, + false, + ) } async preAction() { - const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.sourceToken) + const checkResBalance = await checkERC20Balance( + this.address, + this.amount, + this.token, + this.sourceToken, + ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) return @@ -318,9 +358,14 @@ export class TransferERC20S2M extends TransferAction { const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain if (!checkResAllowance.res) { this.updateState('approve') - const approveTx = await sChain.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, sChain.erc20.address, { - address: this.address, - }) + const approveTx = await sChain.erc20.approve( + this.token.keyname, + MAX_APPROVE_AMOUNT, + sChain.erc20.address, + { + address: this.address, + }, + ) const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) externalEvents.transactionCompleted(approveTx, txBlock.timestamp, this.chainName1, 'approve') @@ -337,11 +382,22 @@ export class TransferERC20S2M extends TransferAction { this.mainnet.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination) this.updateState('received') log('TransferERC20S2M:execute - tokens received to destination chain') - externalEvents.transferComplete(tx.hash, this.chainName1, this.chainName2, this.token.keyname, false) + externalEvents.transferComplete( + tx.hash, + this.chainName1, + this.chainName2, + this.token.keyname, + false, + ) } async preAction() { - const checkResBalance = await checkERC20Balance(this.address, this.amount, this.token, this.sourceToken) + const checkResBalance = await checkERC20Balance( + this.address, + this.amount, + this.token, + this.sourceToken, + ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) return diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index 853fe53..8af3f83 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -23,7 +23,13 @@ import debug from 'debug' -import { TransferERC20S2S, WrapERC20S, UnWrapERC20S, TransferERC20M2S, TransferERC20S2M } from './erc20' +import { + TransferERC20S2S, + WrapERC20S, + UnWrapERC20S, + TransferERC20M2S, + TransferERC20S2M, +} from './erc20' import { Action } from './action' @@ -34,7 +40,11 @@ import { S2S_POSTFIX, M2S_POSTFIX, S2M_POSTFIX } from '../constants' debug.enable('*') const log = debug('metaport:actions') -export function getActionName(chainName1: string, chainName2: string, tokenType: TokenType): string { +export function getActionName( + chainName1: string, + chainName2: string, + tokenType: TokenType, +): string { if (!chainName1 || !chainName2 || !tokenType) return log(`Getting action name: ${chainName1} ${chainName2} ${tokenType}`) let postfix = S2S_POSTFIX diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index d704855..f3a817d 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -38,7 +38,7 @@ import { MINIMUM_RECHARGE_AMOUNT, COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, DEFAULT_ERROR_MSG, - BALANCE_UPDATE_INTERVAL_SECONDS + BALANCE_UPDATE_INTERVAL_SECONDS, } from './constants' import { delay } from './helper' import { CHAIN_IDS, isMainnetChainId, getMainnetAbi } from './network' @@ -86,7 +86,10 @@ export async function getCommunityPoolData( const chainHash = ethers.id(chainName1) const activeM = await mainnet.communityPool.contract.activeUsers(address, chainHash) - const rraWei = await mainnet.communityPool.contract.getRecommendedRechargeAmount(chainHash, address) + const rraWei = await mainnet.communityPool.contract.getRecommendedRechargeAmount( + chainHash, + address, + ) const rraEther = fromWei(rraWei as string, DEFAULT_ERC20_DECIMALS) let recommendedAmount = parseFloat(rraEther as string) * RECHARGE_MULTIPLIER @@ -162,7 +165,7 @@ export async function recharge( setErrorMessage: (errorMessage: dataclasses.ErrorMessage) => void, errorMessageClosedFallback: () => void, ) { - setLoading('recharge'); + setLoading('recharge') try { log(`Recharging community pool: ${chainName}, amount: ${amount}`) @@ -170,26 +173,26 @@ export async function recharge( const mainnetMetamask = await connectedMainnetChain(mpc, walletClient, switchNetwork) await mainnetMetamask.communityPool.recharge(chainName, address, { address: address, - value: toWei(amount, DEFAULT_ERC20_DECIMALS) - }); - setLoading('activate'); - let active = false; + value: toWei(amount, DEFAULT_ERC20_DECIMALS), + }) + setLoading('activate') + let active = false const chainHash = ethers.id(chainName) - let counter = 0; + let counter = 0 while (!active) { - log('Waiting for account activation...'); - let activeM = await mainnetMetamask.communityPool.contract.activeUsers(address, chainHash); - let activeS = await sChain.communityLocker.contract.activeUsers(address); - active = activeS && activeM; - await delay(BALANCE_UPDATE_INTERVAL_SECONDS * 1000); - counter++; - if (counter >= 10) break; + log('Waiting for account activation...') + let activeM = await mainnetMetamask.communityPool.contract.activeUsers(address, chainHash) + let activeS = await sChain.communityLocker.contract.activeUsers(address) + active = activeS && activeM + await delay(BALANCE_UPDATE_INTERVAL_SECONDS * 1000) + counter++ + if (counter >= 10) break } } catch (err) { - console.error(err); - const msg = err.message ? err.message : DEFAULT_ERROR_MSG; - setErrorMessage(new dataclasses.TransactionErrorMessage(msg, errorMessageClosedFallback)); + console.error(err) + const msg = err.message ? err.message : DEFAULT_ERROR_MSG + setErrorMessage(new dataclasses.TransactionErrorMessage(msg, errorMessageClosedFallback)) } finally { - setLoading(false); + setLoading(false) } } diff --git a/src/core/constants.ts b/src/core/constants.ts index bf88ec4..d77ab4e 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -33,7 +33,8 @@ export const WRAP_ACTION = 'wrap' export const UNWRAP_ACTION = 'unwrap' // tslint:disable-next-line -export const MAX_APPROVE_AMOUNT = '115792089237316195423570985008687907853269984665640564039457584007913129639935' // (2^256 - 1 ) +export const MAX_APPROVE_AMOUNT = + '115792089237316195423570985008687907853269984665640564039457584007913129639935' // (2^256 - 1 ) export const DEFAULT_MIN_SFUEL_WEI = 21000000000000n diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts index f88e220..380162c 100644 --- a/src/core/dataclasses/StepMetadata.ts +++ b/src/core/dataclasses/StepMetadata.ts @@ -38,7 +38,11 @@ export enum ActionType { unwrap = 'unwrap', } -export function getActionType(chainName1: string, chainName2: string, tokenType: TokenType): ActionType { +export function getActionType( + chainName1: string, + chainName2: string, + tokenType: TokenType, +): ActionType { if (!chainName1 || !chainName2 || !tokenType) return let postfix = S2S_POSTFIX if (isMainnet(chainName1)) { diff --git a/src/core/ethers.ts b/src/core/ethers.ts index cfacbf1..8c2fa91 100644 --- a/src/core/ethers.ts +++ b/src/core/ethers.ts @@ -16,7 +16,10 @@ export function walletClientToSigner(walletClient: WalletClient) { /** Hook to convert a viem Wallet Client to an ethers.js Signer. */ export function useEthersSigner({ chainId }: { chainId?: number } = {}) { const { data: walletClient } = useWalletClient({ chainId }) - return React.useMemo(() => (walletClient ? walletClientToSigner(walletClient) : undefined), [walletClient]) + return React.useMemo( + () => (walletClient ? walletClientToSigner(walletClient) : undefined), + [walletClient], + ) } export function publicClientToProvider(publicClient: PublicClient) { diff --git a/src/core/explorer.ts b/src/core/explorer.ts index 4b48e8b..f51fcb6 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -21,7 +21,12 @@ * @copyright SKALE Labs 2023-Present */ -import { HTTPS_PREFIX, MAINNET_CHAIN_NAME, MAINNET_EXPLORER_URLS, BASE_EXPLORER_URLS } from './constants' +import { + HTTPS_PREFIX, + MAINNET_CHAIN_NAME, + MAINNET_EXPLORER_URLS, + BASE_EXPLORER_URLS, +} from './constants' import { SkaleNetwork } from './interfaces' function getMainnetExplorerUrl(skaleNetwork: string) { diff --git a/src/core/faucet.ts b/src/core/faucet.ts index 1d7be5b..20468a5 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -21,65 +21,56 @@ * @copyright SKALE Labs 2023-Present */ -import { Provider, Wallet, JsonRpcProvider, AbiCoder, TransactionResponse } from 'ethers'; +import { Provider, Wallet, JsonRpcProvider, AbiCoder, TransactionResponse } from 'ethers' import SkalePowMiner from './miner' import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants' -import { AddressType, SkaleNetwork } from './interfaces'; -import MetaportCore from './metaport'; - +import { AddressType, SkaleNetwork } from './interfaces' +import MetaportCore from './metaport' function getAddress(chainName: string, skaleNetwork: string) { - if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS; - const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; - return faucet[chainName].address; + if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_ADDRESS + const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork] + return faucet[chainName].address } - function getFunc(chainName: string, skaleNetwork: string) { - if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_FUNCSIG; - const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork]; - return faucet[chainName].func; + if (!isFaucetAvailable(chainName, skaleNetwork)) return ZERO_FUNCSIG + const faucet: { [x: string]: { [x: string]: string } } = FAUCET_DATA[skaleNetwork] + return faucet[chainName].func } - export function isFaucetAvailable(chainName: string, skaleNetwork: string) { - if (!FAUCET_DATA[skaleNetwork]) return false; - const keys = Object.keys(FAUCET_DATA[skaleNetwork]); - return keys.includes(chainName); + if (!FAUCET_DATA[skaleNetwork]) return false + const keys = Object.keys(FAUCET_DATA[skaleNetwork]) + return keys.includes(chainName) } - -function getFuncData( - chainName: string, - address: string, - skaleNetwork: string -) { - const faucetAddress = getAddress(chainName, skaleNetwork); - const functionSig = getFunc(chainName, skaleNetwork); - const encoder = new AbiCoder() - const functionParam = encoder.encode(['address'], [address]) - return { to: faucetAddress, data: functionSig + functionParam.slice(2) }; +function getFuncData(chainName: string, address: string, skaleNetwork: string) { + const faucetAddress = getAddress(chainName, skaleNetwork) + const functionSig = getFunc(chainName, skaleNetwork) + const encoder = new AbiCoder() + const functionParam = encoder.encode(['address'], [address]) + return { to: faucetAddress, data: functionSig + functionParam.slice(2) } } - export async function getSFuel( - chainName: string, - address: AddressType, - mpc: MetaportCore + chainName: string, + address: AddressType, + mpc: MetaportCore, ): Promise { - const endpoint = mpc.endpoint(chainName) - const miner = new SkalePowMiner() - const provider = new JsonRpcProvider(endpoint); - const wallet = Wallet.createRandom().connect(provider) - let nonce: number = await wallet.getNonce(); - const mineFreeGasResult = await miner.mineGasForTransaction(nonce, 100000, wallet.address); - const { to, data } = getFuncData(chainName, address, mpc.config.skaleNetwork) - return await wallet.sendTransaction({ - from: wallet.address, - to, - data, - nonce, - gasPrice: mineFreeGasResult - }) -} \ No newline at end of file + const endpoint = mpc.endpoint(chainName) + const miner = new SkalePowMiner() + const provider = new JsonRpcProvider(endpoint) + const wallet = Wallet.createRandom().connect(provider) + let nonce: number = await wallet.getNonce() + const mineFreeGasResult = await miner.mineGasForTransaction(nonce, 100000, wallet.address) + const { to, data } = getFuncData(chainName, address, mpc.config.skaleNetwork) + return await wallet.sendTransaction({ + from: wallet.address, + to, + data, + nonce, + gasPrice: mineFreeGasResult, + }) +} diff --git a/src/core/helper.ts b/src/core/helper.ts index ca8499d..0f89f6a 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -80,7 +80,11 @@ export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app return 'Ethereum' } if (CHAINS_META[skaleNetwork] && CHAINS_META[skaleNetwork][chainName]) { - if (app && CHAINS_META[skaleNetwork][chainName].apps && CHAINS_META[skaleNetwork][chainName].apps[app]) { + if ( + app && + CHAINS_META[skaleNetwork][chainName].apps && + CHAINS_META[skaleNetwork][chainName].apps[app] + ) { return CHAINS_META[skaleNetwork][chainName].apps[app].alias } return CHAINS_META[skaleNetwork][chainName].alias diff --git a/src/core/interfaces/index.ts b/src/core/interfaces/index.ts index e76f9bb..b8a8543 100644 --- a/src/core/interfaces/index.ts +++ b/src/core/interfaces/index.ts @@ -32,4 +32,4 @@ export * from './TransactionHistory' export * from './CommunityPoolData' export * from './TokenMetadata' -export type AddressType = `0x${string}` \ No newline at end of file +export type AddressType = `0x${string}` diff --git a/src/core/metaport.ts b/src/core/metaport.ts index 8eba3a8..9d1d45d 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -23,7 +23,13 @@ import { Provider, JsonRpcProvider, Contract } from 'ethers' -import { MetaportConfig, TokenDataTypesMap, Token, TokenContractsMap, TokenBalancesMap } from './interfaces' +import { + MetaportConfig, + TokenDataTypesMap, + Token, + TokenContractsMap, + TokenBalancesMap, +} from './interfaces' import { TokenType, TokenData, CustomAbiTokenType } from './dataclasses' import { getEmptyTokenDataMap } from './tokens/helper' @@ -120,7 +126,10 @@ export default class MetaportCore { return await tokenContract.balanceOf(address) } - async tokenBalances(tokenContracts: TokenContractsMap, address: string): Promise { + async tokenBalances( + tokenContracts: TokenContractsMap, + address: string, + ): Promise { const balances: TokenBalancesMap = {} const tokenKeynames = Object.keys(tokenContracts) for (const tokenKeyname of tokenKeynames) { @@ -159,7 +168,12 @@ export default class MetaportCore { return new Contract(address, abi, provider) } - originAddress(chainName1: string, chainName2: string, tokenKeyname: string, tokenType: TokenType) { + originAddress( + chainName1: string, + chainName2: string, + tokenKeyname: string, + tokenType: TokenType, + ) { let token = this._config.connections[chainName1][tokenType][tokenKeyname] const isClone = token.chains[chainName2].clone if (isClone) { diff --git a/src/core/miner.ts b/src/core/miner.ts index e12debd..52913f5 100644 --- a/src/core/miner.ts +++ b/src/core/miner.ts @@ -21,55 +21,52 @@ * @copyright SKALE Labs 2023-Present */ -import BN from "bn.js"; -import { isHexString, getNumber, randomBytes, keccak256, hexlify, toBeHex } from 'ethers' - +import BN from 'bn.js' +import { isHexString, getNumber, randomBytes, keccak256, hexlify, toBeHex, toBigInt } from 'ethers' interface Params { - difficulty?: BN; + difficulty?: BN } - export default class SkalePowMiner { + public difficulty: BN = new BN(1) - public difficulty: BN = new BN(1); + constructor(params?: Params) { + if (params && params.difficulty) this.difficulty = params.difficulty + } - constructor(params?: Params) { - if (params && params.difficulty) this.difficulty = params.difficulty; - } + public async mineGasForTransaction( + nonce: string | number, + gas: string | number, + from: string, + ): Promise { + let address = from + nonce = isHexString(nonce) ? getNumber(nonce) : (nonce as number) + gas = isHexString(gas) ? getNumber(gas) : (gas as number) + return await this.mineFreeGas(gas as number, address, nonce as number) + } - public async mineGasForTransaction(nonce: string | number, gas: string | number, from: string, bytes?: string): Promise { - let address = from; - nonce = isHexString(nonce) ? getNumber(nonce) : nonce as number; - gas = isHexString(gas) ? getNumber(gas) : gas as number; - return await this.mineFreeGas(gas as number, address, nonce as number, bytes); + public async mineFreeGas(gasAmount: number, address: string, nonce: number): Promise { + let nonceHash = new BN(keccak256(toBeHex(nonce, 32)).slice(2), 16) + let addressHash = new BN((keccak256(address) as string).slice(2), 16) + let nonceAddressXOR = nonceHash.xor(addressHash) + let maxNumber = new BN(2).pow(new BN(256)).sub(new BN(1)) + let divConstant = maxNumber.div(this.difficulty) + let candidate: string + let iterations = 0 + while (true) { + candidate = hexlify(randomBytes(32)) + let candidateHash = new BN(keccak256(candidate).slice(2), 16) + let resultHash = nonceAddressXOR.xor(candidateHash) + let externalGas = divConstant.div(resultHash).toNumber() + if (externalGas >= gasAmount) { + break + } + // every 2k iterations, yield to the event loop + if (iterations++ % 2_000 === 0) { + await new Promise((resolve) => setTimeout(resolve, 0)) + } } - - public async mineFreeGas(gasAmount: number, address: string, nonce: number, bytes?: string) { - let nonceHash = new BN((keccak256(toBeHex(nonce, 32))).slice(2), 16); - let addressHash = new BN((keccak256(address) as string).slice(2), 16); - let nonceAddressXOR = nonceHash.xor(addressHash) - let maxNumber = new BN(2).pow(new BN(256)).sub(new BN(1)); - let divConstant = maxNumber.div(this.difficulty); - let candidate: BN; - let _bytes: string; - let iterations = 0 - - while (true) { - const _bt = hexlify(randomBytes(32)) - _bytes = _bt.slice(2); - candidate = new BN(bytes ?? _bytes, 16); - let candidateHash = new BN((keccak256(_bt)).slice(2), 16); - let resultHash = nonceAddressXOR.xor(candidateHash); - let externalGas = divConstant.div(resultHash).toNumber(); - if (externalGas >= gasAmount) { - break; - } - // every 2k iterations, yield to the event loop - if (iterations++ % 2_000 === 0) { - await new Promise((resolve) => setTimeout(resolve, 0)); - } - } - return BigInt(candidate) - } -} \ No newline at end of file + return toBigInt(candidate) + } +} diff --git a/src/core/network.ts b/src/core/network.ts index 3f07564..40aaba5 100644 --- a/src/core/network.ts +++ b/src/core/network.ts @@ -47,13 +47,27 @@ export function isMainnetChainId(chainId: number | BigInt, skaleNetwork: SkaleNe return Number(chainId) === CHAIN_IDS[skaleNetwork] } -export function getChainEndpoint(mainnetEndpoint: string, network: SkaleNetwork, chainName: string): string { +export function getChainEndpoint( + mainnetEndpoint: string, + network: SkaleNetwork, + chainName: string, +): string { if (chainName === MAINNET_CHAIN_NAME) return mainnetEndpoint return getSChainEndpoint(network, chainName) } -export function getSChainEndpoint(network: SkaleNetwork, sChainName: string, protocol: 'http' | 'ws' = 'http'): string { - return PROTOCOL[protocol] + getProxyEndpoint(network) + '/v1/' + (protocol === 'ws' ? 'ws/' : '') + sChainName +export function getSChainEndpoint( + network: SkaleNetwork, + sChainName: string, + protocol: 'http' | 'ws' = 'http', +): string { + return ( + PROTOCOL[protocol] + + getProxyEndpoint(network) + + '/v1/' + + (protocol === 'ws' ? 'ws/' : '') + + sChainName + ) } function getProxyEndpoint(network: SkaleNetwork) { @@ -73,7 +87,11 @@ export function getMainnetAbi(network: string) { return { ...IMA_ABIS.mainnet, ...IMA_ADDRESSES.mainnet } } -export function initIMA(mainnetEndpoint: string, network: SkaleNetwork, chainName: string): MainnetChain | SChain { +export function initIMA( + mainnetEndpoint: string, + network: SkaleNetwork, + chainName: string, +): MainnetChain | SChain { if (chainName === MAINNET_CHAIN_NAME) return initMainnet(mainnetEndpoint, network) return initSChain(network, chainName) } diff --git a/src/core/sfuel.ts b/src/core/sfuel.ts index acdee35..965fb23 100644 --- a/src/core/sfuel.ts +++ b/src/core/sfuel.ts @@ -21,71 +21,68 @@ * @copyright SKALE Labs 2022-Present */ -import debug from 'debug'; -import { Provider } from 'ethers'; +import debug from 'debug' +import { Provider } from 'ethers' import MetaportCore from './metaport' import { AddressType } from './interfaces' import { isFaucetAvailable, getSFuel } from './faucet' import { MAINNET_CHAIN_NAME, DEFAULT_MIN_SFUEL_WEI } from '../core/constants' - -debug.enable('*'); -const log = debug('metaport:sfuel'); - +debug.enable('*') +const log = debug('metaport:sfuel') export interface StationData { - balance: bigint - ok: boolean + balance: bigint + ok: boolean } export interface StationPowRes { - message: string - ok: boolean + message: string + ok: boolean } export class Station { + endpoint: string + provider: Provider - endpoint: string; - provider: Provider; + constructor( + public chainName: string, + public mpc: MetaportCore, + ) { + this.chainName = chainName + this.mpc = mpc + this.provider = mpc.provider(chainName) + } - constructor( - public chainName: string, - public mpc: MetaportCore - ) { - this.chainName = chainName - this.mpc = mpc - this.provider = mpc.provider(chainName); + async getData(address: AddressType): Promise { + try { + const balance = await this.provider.getBalance(address) + return { balance, ok: balance >= DEFAULT_MIN_SFUEL_WEI } + } catch (e) { + log(`ERROR: getSFuelData for ${this.chainName} failed!`) + log(e) + return { balance: undefined, ok: undefined } } + } - async getData(address: AddressType): Promise { - try { - const balance = await this.provider.getBalance(address); - return { balance, ok: balance >= DEFAULT_MIN_SFUEL_WEI } - } catch (e) { - log(`ERROR: getSFuelData for ${this.chainName} failed!`); - log(e); - return { balance: undefined, ok: undefined }; - } + async doPoW(address: AddressType): Promise { + // return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; + if (!this.chainName || !isFaucetAvailable(this.chainName, this.mpc.config.skaleNetwork)) { + log('WARNING: PoW is not available for this chain') + if (this.chainName === MAINNET_CHAIN_NAME) { + return { ok: true, message: 'PoW is not available for Ethereum Mainnet' } + } + return { ok: false, message: 'PoW is not available for this chain' } } - - async doPoW(address: AddressType): Promise { - // return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; - if (!this.chainName || !isFaucetAvailable(this.chainName, this.mpc.config.skaleNetwork)) { - log('WARNING: PoW is not available for this chain'); - if (this.chainName === MAINNET_CHAIN_NAME) { - return { ok: true, message: 'PoW is not available for Ethereum Mainnet' }; - } - return { ok: false, message: 'PoW is not available for this chain' }; - } - log('Mining sFUEL for ' + address + ' on ' + this.chainName + '...'); - try { - await getSFuel(this.chainName, address, this.mpc) - return { ok: true, message: 'PoW finished successfully' } - } catch (e) { - log('ERROR: PoW failed!'); - log(e); - return { ok: false, message: e.message }; - } + log('Mining sFUEL for ' + address + ' on ' + this.chainName + '...') + try { + await getSFuel(this.chainName, address, this.mpc) + return { ok: true, message: 'PoW finished successfully' } + } catch (e) { + log('ERROR: PoW failed!') + log(e) + return { ok: false, message: e.message } } + } } diff --git a/src/core/themes.ts b/src/core/themes.ts index da8fdf8..3f2603a 100644 --- a/src/core/themes.ts +++ b/src/core/themes.ts @@ -43,7 +43,16 @@ const defaultThemes = { } // warning: order is important here -const MUI_ELEMENTS = ['mobileStepper', 'fab', 'speedDial', 'appBar', 'drawer', 'modal', 'snackbar', 'tooltip'] +const MUI_ELEMENTS = [ + 'mobileStepper', + 'fab', + 'speedDial', + 'appBar', + 'drawer', + 'modal', + 'snackbar', + 'tooltip', +] const INDEX_STEP = 50 diff --git a/src/core/transfer_steps.ts b/src/core/transfer_steps.ts index 757888d..2ec2049 100644 --- a/src/core/transfer_steps.ts +++ b/src/core/transfer_steps.ts @@ -39,7 +39,11 @@ import { MAINNET_CHAIN_NAME } from './constants' debug.enable('*') const log = debug('metaport:core:transferSteps') -export function getStepsMetadata(config: MetaportConfig, token: TokenData, to: string): StepMetadata[] { +export function getStepsMetadata( + config: MetaportConfig, + token: TokenData, + to: string, +): StepMetadata[] { const steps: StepMetadata[] = [] if (token === undefined || token === null || to === null || to === '') return steps @@ -53,7 +57,9 @@ export function getStepsMetadata(config: MetaportConfig, token: TokenData, to: s if (token.connections[toChain].wrapper) { steps.push(new WrapStepMetadata(token.chain, to)) } - steps.push(new TransferStepMetadata(getActionType(token.chain, toChain, token.type), token.chain, toChain)) + steps.push( + new TransferStepMetadata(getActionType(token.chain, toChain, token.type), token.chain, toChain), + ) if (hubTokenOptions.wrapper && !isCloneToClone) { steps.push(new UnwrapStepMetadata(token.chain, toChain)) } diff --git a/src/core/wagmi_network.ts b/src/core/wagmi_network.ts index 9bf72b9..2491124 100644 --- a/src/core/wagmi_network.ts +++ b/src/core/wagmi_network.ts @@ -59,5 +59,7 @@ export function constructWagmiChain(network: SkaleNetwork, chainName: string): C export function getWebSocketUrl(chain: Chain): string { // return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : ''; - return chain.rpcUrls.default.webSocket ? chain.rpcUrls.default.webSocket[0] : 'wss://goerli-light.eth.linkpool.io/ws' // TODO - IP! + return chain.rpcUrls.default.webSocket + ? chain.rpcUrls.default.webSocket[0] + : 'wss://goerli-light.eth.linkpool.io/ws' // TODO - IP! } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 3c42430..7e43ae6 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -41,12 +41,14 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { _SPACE_1: { name: 'SKALE Space', symbol: 'SPACE', - iconUrl: 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png', + iconUrl: + 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png', }, _SKALIENS_1: { name: 'SKALIENS Collection', symbol: 'SKALIENS', - iconUrl: 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png', + iconUrl: + 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png', }, ruby: { name: 'Ruby Token', diff --git a/src/store/CommunityPoolStore.ts b/src/store/CommunityPoolStore.ts index bc0754a..18d219d 100644 --- a/src/store/CommunityPoolStore.ts +++ b/src/store/CommunityPoolStore.ts @@ -28,7 +28,6 @@ import * as interfaces from '../core/interfaces' import { getEmptyCommunityPoolData, getCommunityPoolData } from '../core/community_pool' import MetaportCore from '../core/metaport' - interface CommunityPoolState { cpData: interfaces.CommunityPoolData setCpData: (cpData: interfaces.CommunityPoolData) => void @@ -57,7 +56,12 @@ export const useCPStore = create()((set, get) => ({ sChain: null, chainName: null, - updateCPData: async (address: string, chainName1: string, chainName2: string, mpc: MetaportCore) => { + updateCPData: async ( + address: string, + chainName1: string, + chainName2: string, + mpc: MetaportCore, + ) => { if (!chainName1 || !chainName2) return if (!get().mainnet) { set({ mainnet: mpc.mainnet() }) @@ -65,11 +69,17 @@ export const useCPStore = create()((set, get) => ({ if (!get().sChain || get().chainName !== chainName1) { set({ sChain: mpc.schain(chainName1) }) } - const cpData = await getCommunityPoolData(address, chainName1, chainName2, get().mainnet, get().sChain) + const cpData = await getCommunityPoolData( + address, + chainName1, + chainName2, + get().mainnet, + get().sChain, + ) set({ chainName: chainName1, cpData: cpData, - amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : '' + amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : '', }) - } + }, })) diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 780458f..afba2f8 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -57,7 +57,11 @@ interface MetaportState { tokenId: number setTokenId: (tokenId: number) => void - execute: (address: string, switchNetwork: (chainId: number) => void, walletClient: WalletClient) => void + execute: ( + address: string, + switchNetwork: (chainId: number) => void, + walletClient: WalletClient, + ) => void check: (amount: string, address: `0x${string}`) => void currentStep: number @@ -166,7 +170,10 @@ export const useMetaportStore = create()((set, get) => ({ console.error(err) const msg = err.message ? err.message : DEFAULT_ERROR_MSG set({ - errorMessage: new dataclasses.TransactionErrorMessage(msg, get().errorMessageClosedFallback), + errorMessage: new dataclasses.TransactionErrorMessage( + msg, + get().errorMessageClosedFallback, + ), }) return } finally { @@ -245,9 +252,16 @@ export const useMetaportStore = create()((set, get) => ({ } else { updState['sChain1'] = state.mpc.schain(name) } - const provider = updState['mainnetChain'] ? updState['mainnetChain'].provider : updState['sChain1'].provider + const provider = updState['mainnetChain'] + ? updState['mainnetChain'].provider + : updState['sChain1'].provider const tokens = state.mpc.tokens(name) - const tokenContracts = state.mpc.tokenContracts(tokens, dataclasses.TokenType.erc20, name, provider) + const tokenContracts = state.mpc.tokenContracts( + tokens, + dataclasses.TokenType.erc20, + name, + provider, + ) return { currentStep: 0, token: null, @@ -283,8 +297,14 @@ export const useMetaportStore = create()((set, get) => ({ token: null, setToken: async (token: dataclasses.TokenData) => { - const provider = get().chainName2 === MAINNET_CHAIN_NAME ? get().mainnetChain.provider : get().sChain2.provider - const destTokenContract = get().mpc.tokenContract(get().chainName2, token.keyname, token.type, provider) + const provider = + get().chainName2 === MAINNET_CHAIN_NAME ? get().mainnetChain.provider : get().sChain2.provider + const destTokenContract = get().mpc.tokenContract( + get().chainName2, + token.keyname, + token.type, + provider, + ) set({ token: token, stepsMetadata: getStepsMetadata(get().mpc.config, token, get().chainName2), diff --git a/src/store/SFuelStore.ts b/src/store/SFuelStore.ts index d9a2ce4..9938ca5 100644 --- a/src/store/SFuelStore.ts +++ b/src/store/SFuelStore.ts @@ -28,7 +28,6 @@ import * as interfaces from '../core/interfaces' import { Station, StationData } from '../core/sfuel' import MetaportCore from '../core/metaport' - interface SFuelState { loading: boolean setLoading: (loading: boolean) => void @@ -44,20 +43,20 @@ interface SFuelState { hubChainStation: Station setHubChainStation: (station: Station) => void - sFuelStatus: 'action' | 'warning' | 'error'; + sFuelStatus: 'action' | 'warning' | 'error' setSFuelStatus: (status: 'action' | 'warning' | 'error') => void sFuelOk: boolean setSFuelOk: (loading: boolean) => void - fromStationData: StationData; - setFromStationData: (data: StationData) => void; + fromStationData: StationData + setFromStationData: (data: StationData) => void - toStationData: StationData; - setToStationData: (data: StationData) => void; + toStationData: StationData + setToStationData: (data: StationData) => void - hubStationData: StationData; - setHubStationData: (data: StationData) => void; + hubStationData: StationData + setHubStationData: (data: StationData) => void } export const useSFuelStore = create()((set, get) => ({ From 6a9889fea416d19a24c7d0add51d65e354a5d3a4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 28 Aug 2023 19:55:33 +0100 Subject: [PATCH 034/110] Drop bn.js requirement --- package.json | 1 - src/core/community_pool.ts | 2 +- src/core/constants.ts | 2 ++ src/core/miner.ts | 21 ++++++++++----------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 8038e38..01296b6 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "@mui/material": "^5.8.1", "@rainbow-me/rainbowkit": "^1.0.8", "@skalenetwork/ima-js": "2.0.0-develop.3", - "bn.js": "^5.2.1", "coingecko-api-v3": "^0.0.28", "react-jazzicon": "^1.0.4", "viem": "^1.5.3", diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index f3a817d..4345cef 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -68,7 +68,7 @@ export async function getCommunityPoolData( sChain: SChain, ): Promise { if (chainName2 !== MAINNET_CHAIN_NAME) { - log('not a S2M transfer, skipping community pool check') + // log('not a S2M transfer, skipping community pool check') return { exitGasOk: true, isActive: null, diff --git a/src/core/constants.ts b/src/core/constants.ts index d77ab4e..635a367 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -32,6 +32,8 @@ export const S2S_POSTFIX = 's2s' export const WRAP_ACTION = 'wrap' export const UNWRAP_ACTION = 'unwrap' +export const MAX_NUMBER = 2n ** 256n - 1n + // tslint:disable-next-line export const MAX_APPROVE_AMOUNT = '115792089237316195423570985008687907853269984665640564039457584007913129639935' // (2^256 - 1 ) diff --git a/src/core/miner.ts b/src/core/miner.ts index 52913f5..e426ddf 100644 --- a/src/core/miner.ts +++ b/src/core/miner.ts @@ -21,15 +21,15 @@ * @copyright SKALE Labs 2023-Present */ -import BN from 'bn.js' import { isHexString, getNumber, randomBytes, keccak256, hexlify, toBeHex, toBigInt } from 'ethers' +import { MAX_NUMBER } from './constants' interface Params { - difficulty?: BN + difficulty?: bigint } export default class SkalePowMiner { - public difficulty: BN = new BN(1) + public difficulty: bigint = 1n constructor(params?: Params) { if (params && params.difficulty) this.difficulty = params.difficulty @@ -47,18 +47,17 @@ export default class SkalePowMiner { } public async mineFreeGas(gasAmount: number, address: string, nonce: number): Promise { - let nonceHash = new BN(keccak256(toBeHex(nonce, 32)).slice(2), 16) - let addressHash = new BN((keccak256(address) as string).slice(2), 16) - let nonceAddressXOR = nonceHash.xor(addressHash) - let maxNumber = new BN(2).pow(new BN(256)).sub(new BN(1)) - let divConstant = maxNumber.div(this.difficulty) + let nonceHash = toBigInt(keccak256(toBeHex(nonce, 32))) + let addressHash = toBigInt(keccak256(address)) + let nonceAddressXOR = nonceHash ^ addressHash + let divConstant = MAX_NUMBER / this.difficulty let candidate: string let iterations = 0 while (true) { candidate = hexlify(randomBytes(32)) - let candidateHash = new BN(keccak256(candidate).slice(2), 16) - let resultHash = nonceAddressXOR.xor(candidateHash) - let externalGas = divConstant.div(resultHash).toNumber() + let candidateHash = toBigInt(keccak256(candidate)) + let resultHash = nonceAddressXOR ^ candidateHash + let externalGas = divConstant / resultHash if (externalGas >= gasAmount) { break } From 5219de7387192a764802be0c48e4422e94160439 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 28 Aug 2023 20:09:56 +0100 Subject: [PATCH 035/110] Update miner scirpt --- src/core/miner.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/miner.ts b/src/core/miner.ts index e426ddf..0ab235e 100644 --- a/src/core/miner.ts +++ b/src/core/miner.ts @@ -51,10 +51,11 @@ export default class SkalePowMiner { let addressHash = toBigInt(keccak256(address)) let nonceAddressXOR = nonceHash ^ addressHash let divConstant = MAX_NUMBER / this.difficulty - let candidate: string + let candidate: Uint8Array let iterations = 0 + const start = performance.now() while (true) { - candidate = hexlify(randomBytes(32)) + candidate = randomBytes(32) let candidateHash = toBigInt(keccak256(candidate)) let resultHash = nonceAddressXOR ^ candidateHash let externalGas = divConstant / resultHash @@ -66,6 +67,8 @@ export default class SkalePowMiner { await new Promise((resolve) => setTimeout(resolve, 0)) } } + const end = performance.now() + console.log(`PoW xecution time: ${end - start} ms`) return toBigInt(candidate) } } From 69d6ac614738739a21c70c9de48aa4714eaa33be Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 29 Aug 2023 20:09:21 +0100 Subject: [PATCH 036/110] Update sFUEL logic, add vibrant mode --- src/components/ChainApps/ChainApps.tsx | 36 +++++- src/components/ChainsList/ChainsList.tsx | 88 +++++++++---- src/components/SFuelWarning/SFuelWarning.tsx | 125 ++++++++++--------- src/components/WidgetBody/WidgetBody.tsx | 13 +- src/core/constants.ts | 2 +- src/core/faucet.ts | 2 +- src/core/interfaces/Theme.ts | 1 + src/core/metadata.ts | 17 +++ src/core/miner.ts | 2 +- src/index.ts | 5 + src/metadata/metaportConfigStaging.ts | 3 +- src/store/SFuelStore.ts | 20 +-- src/styles/cmn.module.scss | 4 + src/styles/styles.module.scss | 4 + 14 files changed, 214 insertions(+), 108 deletions(-) diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index ea454c9..245edd4 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -7,23 +7,51 @@ import { SkaleNetwork } from '../../core/interfaces' import ChainIcon from '../ChainIcon' -export default function ChainApps(props: { skaleNetwork: SkaleNetwork; chain: string }) { +export default function ChainApps(props: { + skaleNetwork: SkaleNetwork + chain: string + size?: 'sm' | 'md' +}) { const apps = getChainAppsMeta(props.chain, props.skaleNetwork) if (!apps || !Object.keys(apps) || Object.keys(apps).length === 0) return
+ const size = props.size ?? 'sm' + const iconSize = props.size === 'sm' ? 'xs' : 'sm' + return (
{Object.keys(apps).map((key, _) => ( -
+
-

+

{getChainAlias(props.skaleNetwork, props.chain, key)}

diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index b284f38..3acfed5 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -6,6 +6,7 @@ import Typography from '@mui/material/Typography' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import Tooltip from '@mui/material/Tooltip' import Button from '@mui/material/Button' +import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded' import ChainApps from '../ChainApps' import ChainIcon from '../ChainIcon' @@ -26,6 +27,7 @@ export default function ChainsList(props: { disabledChain: string from?: boolean disabled?: boolean + size?: 'sm' | 'md' }) { const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { props.setExpanded(isExpanded ? panel : false) @@ -44,6 +46,8 @@ export default function ChainsList(props: { props.setChain(schainName) } + const size = props.size ?? 'sm' + return (
{props.chain ? (
-
- +
+
- -

- {getChainAlias(props.config.skaleNetwork, props.chain)} -

-
+

+ {getChainAlias(props.config.skaleNetwork, props.chain)} +

{/*
-
- +
+
{schainNames.map((name) => ( @@ -106,16 +130,41 @@ export default function ChainsList(props: { {/*
*/} -
-
- +
+
+

- {/*
- -
*/} +
-
- +
+
))} diff --git a/src/components/SFuelWarning/SFuelWarning.tsx b/src/components/SFuelWarning/SFuelWarning.tsx index e093926..72887af 100644 --- a/src/components/SFuelWarning/SFuelWarning.tsx +++ b/src/components/SFuelWarning/SFuelWarning.tsx @@ -29,16 +29,10 @@ import { useAccount } from 'wagmi' import Button from '@mui/material/Button' import LoadingButton from '@mui/lab/LoadingButton' import { Collapse } from '@mui/material' +import LinearProgress from '@mui/material/LinearProgress'; -import { - MAINNET_CHAIN_NAME, - SFUEL_CHEKCS_INTERVAL, - SFUEL_TEXT, - DEFAULT_FAUCET_URL, -} from '../../core/constants' - -import { Station, StationData } from '../../core/sfuel' -import { View } from '../../core/dataclasses/View' +import { MAINNET_CHAIN_NAME, SFUEL_TEXT } from '../../core/constants' +import { Station } from '../../core/sfuel' import { useMetaportStore } from '../../store/MetaportState' import { useSFuelStore } from '../../store/SFuelStore' @@ -47,8 +41,6 @@ import { cls } from '../../core/helper' import cmn from '../../styles/cmn.module.scss' import styles from '../../styles/styles.module.scss' -// import CustomStationUrl from '../CustomStationUrl'; - debug.enable('*') const log = debug('metaport:components:SFuel') @@ -78,15 +70,6 @@ export default function SFuelWarning(props: {}) { const sFuelOk = useSFuelStore((state) => state.sFuelOk) const setSFuelOk = useSFuelStore((state) => state.setSFuelOk) - const fromStationData = useSFuelStore((state) => state.fromStationData) - const setFromStationData = useSFuelStore((state) => state.setFromStationData) - - const toStationData = useSFuelStore((state) => state.toStationData) - const setToStationData = useSFuelStore((state) => state.setToStationData) - - const hubStationData = useSFuelStore((state) => state.hubStationData) - const setHubStationData = useSFuelStore((state) => state.setHubStationData) - const { address } = useAccount() let hubChain @@ -97,25 +80,15 @@ export default function SFuelWarning(props: {}) { useEffect(() => { if (!chainName1 || !chainName2 || !address) return + setLoading(true) + setFromChainStation(null) + setToChainStation(null) + setHubChainStation(null) + setSFuelOk(false) log('Initializing SFuelWarning', chainName1, chainName2, hubChain, address) createStations() }, [chainName1, chainName2, hubChain, address]) - useEffect(() => { - if (!fromStationData.ok || (hubChainStation && !hubStationData.ok)) { - setSFuelStatus('error') - setSFuelOk(false) - } else { - if (!toStationData.ok) { - setSFuelStatus('warning') - setSFuelOk(false) - } else { - setSFuelStatus('action') - setSFuelOk(true) - } - } - }, [fromStationData, toStationData, hubStationData]) - useEffect(() => { updateStationsData() const intervalId = setInterval(() => { @@ -133,10 +106,35 @@ export default function SFuelWarning(props: {}) { } async function updateStationsData() { - if (fromChainStation) setFromStationData(await fromChainStation.getData(address)) - if (toChainStation) setToStationData(await toChainStation.getData(address)) - if (hubChainStation) setHubStationData(await hubChainStation.getData(address)) - setLoading(false) + let fromData + let toData + let hubData + if (fromChainStation) { + fromData = await fromChainStation.getData(address) + } + if (toChainStation) { + toData = await toChainStation.getData(address) + } + if (hubChainStation) { + hubData = await hubChainStation.getData(address) + } + if ((fromData && !fromData.ok) || (hubData && !hubData.ok)) { + setSFuelStatus('error') + setSFuelOk(false) + } else { + if (toData && !toData.ok) { + setSFuelStatus('warning') + setSFuelOk(false) + } else { + if (fromData && fromData.ok && toData && toData.ok) { + setSFuelStatus('action') + setSFuelOk(true) + } + } + } + if (fromData && toData) { + setLoading(false) + } } async function doPoW() { @@ -145,20 +143,27 @@ export default function SFuelWarning(props: {}) { let hubPowRes setMining(true) - - if (fromChainStation && !fromStationData.ok) { - log(`Doing PoW on ${fromChainStation.chainName}`) - fromPowRes = await fromChainStation.doPoW(address) + if (fromChainStation) { + const fromData = await fromChainStation.getData(address) + if (!fromData.ok) { + log(`Doing PoW on ${fromChainStation.chainName}`) + fromPowRes = await fromChainStation.doPoW(address) + } } - if (toChainStation && !toStationData.ok) { - log(`Doing PoW on ${toChainStation.chainName}`) - toPowRes = await toChainStation.doPoW(address) + if (toChainStation) { + const toData = await toChainStation.getData(address) + if (!toData.ok) { + log(`Doing PoW on ${toChainStation.chainName}`) + toPowRes = await toChainStation.doPoW(address) + } } - if (hubChainStation && !hubStationData.ok) { - log(`Doing PoW on ${hubChainStation.chainName}`) - hubPowRes = await hubChainStation.doPoW(address) + if (hubChainStation) { + const hubData = await hubChainStation.getData(address) + if (!hubData.ok) { + log(`Doing PoW on ${hubChainStation.chainName}`) + hubPowRes = await hubChainStation.doPoW(address) + } } - if ( (fromPowRes && !fromPowRes.ok) || (toPowRes && !toPowRes.ok) || @@ -169,28 +174,34 @@ export default function SFuelWarning(props: {}) { if (toPowRes) log(chainName2, toPowRes.message) if (hubPowRes) log(hubChain, hubPowRes.message) // window.open(DEFAULT_FAUCET_URL, '_blank'); + } else { + setSFuelStatus('action') + setSFuelOk(true) } - await updateStationsData() + // await updateStationsData() setMining(false) } - const noEth = fromStationData && !fromStationData.ok && chainName1 === MAINNET_CHAIN_NAME - const noEthDest = toStationData && !toStationData.ok && chainName2 === MAINNET_CHAIN_NAME + const mainnetTransfer = chainName1 === MAINNET_CHAIN_NAME || chainName2 === MAINNET_CHAIN_NAME function getSFuelText() { - if (noEth || (fromStationData && fromStationData.ok && noEthDest)) { + if (mainnetTransfer) { return SFUEL_TEXT['gas'][sFuelStatus] } return SFUEL_TEXT['sfuel'][sFuelStatus] } + if (loading) return
+ +
+ return ( - +
-

+

⛽ {getSFuelText()}

- {!noEth && ((fromStationData && !fromStationData.ok) || !noEthDest) ? ( + {!mainnetTransfer ? (
{mining ? ( state.expandedFrom) @@ -50,6 +53,8 @@ export function WidgetBody(props) { const sFuelOk = useSFuelStore((state) => state.sFuelOk) + const theme = useUIStore((state) => state.theme) + useEffect(() => { setChainName1(mpc.config.chains ? mpc.config.chains[0] : '') setChainName2(mpc.config.chains ? mpc.config.chains[1] : '') @@ -71,12 +76,16 @@ export function WidgetBody(props) { !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME const showError = !!errorMessage + const grayBg = 'rgb(136 135 135 / 15%)' + const sourceBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName1) : grayBg + const destBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName2) : grayBg + return (
- +
@@ -116,7 +125,7 @@ export function WidgetBody(props) { - +

To

diff --git a/src/core/constants.ts b/src/core/constants.ts index 635a367..dff456a 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -75,7 +75,7 @@ export const IMA_HUB_WAIT = 5 export const COINGECKO_API_ENDPOINT = '' export const DEFAULT_FAUCET_URL = 'https://sfuel.skale.network/' -export const SFUEL_CHEKCS_INTERVAL = 8 +export const SFUEL_CHECKS_INTERVAL = 8 export const SFUEL_TEXT = { sfuel: { diff --git a/src/core/faucet.ts b/src/core/faucet.ts index 20468a5..8bcbefd 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -64,7 +64,7 @@ export async function getSFuel( const provider = new JsonRpcProvider(endpoint) const wallet = Wallet.createRandom().connect(provider) let nonce: number = await wallet.getNonce() - const mineFreeGasResult = await miner.mineGasForTransaction(nonce, 100000, wallet.address) + const mineFreeGasResult = await miner.mineGasForTransaction(nonce, 1000000, wallet.address) const { to, data } = getFuncData(chainName, address, mpc.config.skaleNetwork) return await wallet.sendTransaction({ from: wallet.address, diff --git a/src/core/interfaces/Theme.ts b/src/core/interfaces/Theme.ts index af1b59c..abedad6 100644 --- a/src/core/interfaces/Theme.ts +++ b/src/core/interfaces/Theme.ts @@ -31,4 +31,5 @@ export interface MetaportTheme { background?: string position?: Position zIndex?: number + vibrant?: boolean } diff --git a/src/core/metadata.ts b/src/core/metadata.ts index af58bf0..1e5dd74 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -24,6 +24,7 @@ import { TokenData } from './dataclasses' import { SkaleNetwork } from './interfaces' import { MAINNET_CHAIN_NAME } from './constants' +import { CHAINS_META } from './helper' import * as MAINNET_CHAIN_ICONS from '../meta/mainnet/icons' import * as STAGING_CHAIN_ICONS from '../meta/staging/icons' @@ -60,6 +61,22 @@ export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: st } } +export function chainBg(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { + if (CHAINS_META[skaleNetwork][chainName]) { + if (app) { + if (CHAINS_META[skaleNetwork][chainName]['apps'][app]['gradientBackground']) { + return CHAINS_META[skaleNetwork][chainName]['apps'][app]['gradientBackground'] + } + return CHAINS_META[skaleNetwork][chainName]['apps'][app]['background'] + } + if (CHAINS_META[skaleNetwork][chainName]['gradientBackground']) { + return CHAINS_META[skaleNetwork][chainName]['gradientBackground'] + } + return CHAINS_META[skaleNetwork][chainName]['background'] + } + return 'linear-gradient(273.67deg, rgb(47 50 80), rgb(39 43 68))' +} + export function tokenIcon(tokenSymbol: string) { if (!tokenSymbol) return const key = tokenSymbol.toLowerCase() diff --git a/src/core/miner.ts b/src/core/miner.ts index 0ab235e..d889df9 100644 --- a/src/core/miner.ts +++ b/src/core/miner.ts @@ -68,7 +68,7 @@ export default class SkalePowMiner { } } const end = performance.now() - console.log(`PoW xecution time: ${end - start} ms`) + console.log(`PoW execution time: ${end - start} ms`) return toBigInt(candidate) } } diff --git a/src/index.ts b/src/index.ts index 0660704..42f89c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export { interfaces, dataclasses } from './Metaport' export { useMetaportStore } from './store/MetaportState' export { useUIStore, useCollapseStore } from './store/Store' +export { useSFuelStore } from './store/SFuelStore' import Metaport from './components/Metaport' import MetaportProvider from './components/MetaportProvider' @@ -23,8 +24,10 @@ import AmountErrorMessage from './components/AmountErrorMessage' import DestTokenBalance from './components/DestTokenBalance' import ErrorMessage from './components/ErrorMessage' import CommunityPool from './components/CommunityPool' +import SFuelWarning from './components/SFuelWarning' import { cls } from './core/helper' +import { chainBg } from './core/metadata' import styles from './styles/styles.module.scss' import cmn from './styles/cmn.module.scss' @@ -53,10 +56,12 @@ export { DestTokenBalance, ErrorMessage, CommunityPool, + SFuelWarning, cls, styles, cmn, getMetaportTheme, useWagmiAccount, PROXY_ENDPOINTS, + chainBg } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 7e43ae6..d3f34f2 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -289,5 +289,6 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { }, theme: { mode: 'dark', - }, + vibrant: true, + } } diff --git a/src/store/SFuelStore.ts b/src/store/SFuelStore.ts index 9938ca5..d464896 100644 --- a/src/store/SFuelStore.ts +++ b/src/store/SFuelStore.ts @@ -48,15 +48,6 @@ interface SFuelState { sFuelOk: boolean setSFuelOk: (loading: boolean) => void - - fromStationData: StationData - setFromStationData: (data: StationData) => void - - toStationData: StationData - setToStationData: (data: StationData) => void - - hubStationData: StationData - setHubStationData: (data: StationData) => void } export const useSFuelStore = create()((set, get) => ({ @@ -78,14 +69,5 @@ export const useSFuelStore = create()((set, get) => ({ setSFuelStatus: (status: 'action' | 'warning' | 'error') => set({ sFuelStatus: status }), sFuelOk: false, - setSFuelOk: (sFuelOk: boolean) => set(() => ({ sFuelOk: sFuelOk })), - - fromStationData: { ok: false, balance: null }, - setFromStationData: (data: StationData) => set({ fromStationData: data }), - - toStationData: { ok: false, balance: null }, - setToStationData: (data: StationData) => set({ toStationData: data }), - - hubStationData: { ok: false, balance: null }, - setHubStationData: (data: StationData) => set({ hubStationData: data }), + setSFuelOk: (sFuelOk: boolean) => set(() => ({ sFuelOk: sFuelOk })) })) diff --git a/src/styles/cmn.module.scss b/src/styles/cmn.module.scss index 8efd048..eb499ad 100644 --- a/src/styles/cmn.module.scss +++ b/src/styles/cmn.module.scss @@ -61,6 +61,10 @@ margin-right: 10px !important; } +.mri15 { + margin-right: 15px !important; +} + .mri5 { margin-right: 5px !important; } diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index 3c1a742..a788335 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -120,6 +120,10 @@ button { } + :global(.MuiLinearProgress-root) { + border-radius: 20px; + } + // .accordionSm { // :global(.MuiAccordionSummary-content) { // margin: 0 !important; From b76537ab3ed489f717b5821ebf9a4e10fd7fd76c Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 30 Aug 2023 22:15:41 +0100 Subject: [PATCH 037/110] Add ETH M2S, S2M methods, add ETH balance check --- .../DestTokenBalance/DestTokenBalance.tsx | 1 + src/components/TokenList/TokenList.tsx | 5 +- .../TokenListSection/TokenListSection.tsx | 2 +- src/components/WidgetBody/WidgetBody.tsx | 1 + src/core/actions/eth.ts | 123 ++++++++++++++++++ src/core/actions/index.ts | 9 +- src/core/dataclasses/StepMetadata.ts | 4 +- src/core/metaport.ts | 12 +- src/metadata/metaportConfigStaging.ts | 26 +++- src/store/MetaportState.ts | 14 +- 10 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 src/core/actions/eth.ts diff --git a/src/components/DestTokenBalance/DestTokenBalance.tsx b/src/components/DestTokenBalance/DestTokenBalance.tsx index 7fb4684..9f67408 100644 --- a/src/components/DestTokenBalance/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance/DestTokenBalance.tsx @@ -28,6 +28,7 @@ export default function DestTokenBalance() { balance={destTokenBalance} symbol={token.meta.symbol} decimals={token.meta.decimals} + truncate={9} /> ) } diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index ff81587..91dc1e1 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -114,12 +114,13 @@ export default function TokenList() { {expandedTokens ? ( - {/* */} + tokenBalances={tokenBalances} + />
diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index b4529f4..7c5d372 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -96,6 +96,7 @@ export function WidgetBody(props) { balance={tokenBalances[token.keyname]} symbol={token.meta.symbol} decimals={token.meta.decimals} + truncate={9} /> ) : null}
diff --git a/src/core/actions/eth.ts b/src/core/actions/eth.ts new file mode 100644 index 0000000..ccd650e --- /dev/null +++ b/src/core/actions/eth.ts @@ -0,0 +1,123 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file eth.ts + * @copyright SKALE Labs 2022-Present + */ + + +import debug from 'debug'; +import { MainnetChain, SChain } from '@skalenetwork/ima-js' + +import { externalEvents } from '../events'; +import { toWei } from '../convertation'; +import { TransferAction, Action } from './action'; +import { checkEthBalance } from './checks'; + + +debug.enable('*'); +const log = debug('metaport:actions:eth'); + + +export class TransferEthM2S extends TransferAction { + async execute() { + this.updateState('init') + const amountWei = toWei(this.amount, this.token.meta.decimals) + const sChainBalanceBefore = await this.sChain2.ethBalance(this.address); + const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain + this.updateState('transferETH'); + const tx = await mainnet.eth.deposit( + this.chainName2, + { + address: this.address, + value: amountWei + } + ); + const block = await this.mainnet.provider.getBlock(tx.blockNumber); + this.updateState('transferETHDone', tx.hash, block.timestamp); + await this.sChain2.waitETHBalanceChange(this.address, sChainBalanceBefore); + this.updateState('receivedETH'); + } + + async preAction() { + const checkResBalance = await checkEthBalance( + this.mainnet, + this.address, + this.amount, + this.token + ); + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg); + return + } + this.setAmountErrorMessage(null) + } +} + + +export class TransferEthS2M extends TransferAction { + async execute() { + log('TransferEthS2M: started'); + this.updateState('init'); + const amountWei = toWei(this.amount, this.token.meta.decimals); + const lockedETHAmount = await this.mainnet.eth.lockedETHAmount(this.address); + const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain + this.updateState('transferETH'); + const tx = await sChain.eth.withdraw( + amountWei, + { address: this.address } + ); + const block = await this.sChain1.provider.getBlock(tx.blockNumber); + this.updateState('transferETHDone', tx.hash, block.timestamp); + await this.mainnet.eth.waitLockedETHAmountChange(this.address, lockedETHAmount); + this.updateState('receivedETH'); + } + + async preAction() { + const checkResBalance = await checkEthBalance( + this.sChain1, + this.address, + this.amount, + this.token + ); + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg); + } + this.setAmountErrorMessage(null); + } +} + + +export class UnlockEthM extends Action { + static label = 'Unlock ETH' + static buttonText = 'Unlock' + static loadingText = 'Unlocking' + + async execute() { + this.updateState('init'); + const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain + this.updateState('unlock'); + const tx = await mainnet.eth.getMyEth( + { address: this.address } + ); + const block = await this.mainnet.provider.getBlock(tx.blockNumber); + this.updateState('unlockDone', tx.hash, block.timestamp); + } +} + diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index 8af3f83..8fcad64 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -23,6 +23,11 @@ import debug from 'debug' +import { + TransferEthM2S, + TransferEthS2M, + UnlockEthM +} from './eth'; import { TransferERC20S2S, WrapERC20S, @@ -60,8 +65,8 @@ export function getActionName( } export const ACTIONS: { [actionType in ActionType]: typeof Action } = { - // eth_m2s: [TransferEthM2S], - // eth_s2m: [TransferEthS2M, UnlockEthM], + eth_m2s: TransferEthM2S, + eth_s2m: TransferEthS2M, // eth_s2s: [], wrap: WrapERC20S, diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts index 380162c..52e196f 100644 --- a/src/core/dataclasses/StepMetadata.ts +++ b/src/core/dataclasses/StepMetadata.ts @@ -36,6 +36,8 @@ export enum ActionType { erc20_s2s = 'erc20_s2s', wrap = 'wrap', unwrap = 'unwrap', + eth_m2s = 'eth_m2s', + eth_s2m = 'eth_s2m' } export function getActionType( @@ -68,7 +70,7 @@ export abstract class StepMetadata { public type: ActionType, public from: string, public to: string, - ) {} + ) { } } export class TransferStepMetadata extends StepMetadata { diff --git a/src/core/metaport.ts b/src/core/metaport.ts index 9d1d45d..a1b0726 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -160,9 +160,17 @@ export default class MetaportCore { provider: Provider, customAbiTokenType?: CustomAbiTokenType, destChainName?: string, - ): Contract { + ): Contract | undefined { + let type = tokenType const token = this._config.connections[chainName][tokenType][tokenKeyname] - const abi = customAbiTokenType ? ERC_ABIS[customAbiTokenType].abi : ERC_ABIS[tokenType].abi + if (tokenType === TokenType.eth) { + if (destChainName && token.chains[destChainName].clone) { + type = TokenType.erc20 + } else { + return + } + } + const abi = customAbiTokenType ? ERC_ABIS[customAbiTokenType].abi : ERC_ABIS[type].abi const address = customAbiTokenType ? token.chains[destChainName].wrapper : token.address // TODO: add sFUEL address support! return new Contract(address, abi, provider) diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index d3f34f2..07bf879 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -16,7 +16,7 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { ], tokens: { eth: { - symbol: 'eth', + symbol: 'ETH' }, skl: { decimals: '18', @@ -72,6 +72,18 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { }, connections: { mainnet: { + eth: { + eth: { + chains: { + 'staging-legal-crazy-castor': {}, + // "staging-utter-unripe-menkar": {}, + // "staging-faint-slimy-achird": {}, + // "staging-perfect-parallel-gacrux": {}, + // "staging-severe-violet-wezen": {}, + // "staging-weepy-fitting-caph": {} + } + } + }, erc20: { skl: { address: '0x493D4442013717189C9963a2e275Ad33bfAFcE11', @@ -197,6 +209,16 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { }, 'staging-legal-crazy-castor': { // Europa connections + eth: { + eth: { + address: '0xD2Aaa00700000000000000000000000000000000', + chains: { + mainnet: { + clone: true + } + } + } + }, erc20: { skl: { address: '0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6', @@ -289,6 +311,6 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { }, theme: { mode: 'dark', - vibrant: true, + vibrant: true } } diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index afba2f8..08ddb2d 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -304,6 +304,8 @@ export const useMetaportStore = create()((set, get) => ({ token.keyname, token.type, provider, + null, + get().chainName1 ) set({ token: token, @@ -319,16 +321,26 @@ export const useMetaportStore = create()((set, get) => ({ destTokenContract: null, destTokenBalance: null, + updateDestTokenBalance: async (address: string) => { if (get().destTokenContract) { const balance = await get().mpc.tokenBalance(get().destTokenContract, address) set({ destTokenBalance: balance }) + } else { + if (get().token && get().token.type === dataclasses.TokenType.eth && get().chainName2 === MAINNET_CHAIN_NAME) { + const chain = get().mpc.mainnet() + set({ destTokenBalance: await chain.ethBalance(address) }) + } } }, updateTokenBalances: async (address: string) => { const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address) - set({ tokenBalances: tokenBalances }) + const chain = get().mpc.ima(get().chainName1) + tokenBalances.eth = await chain.ethBalance(address) + set({ + tokenBalances: tokenBalances + }) }, amountErrorMessage: null, From 75406614f5dac17534cb7280f219833daa159b9e Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 31 Aug 2023 16:04:25 +0100 Subject: [PATCH 038/110] Update eth unlock step, update wallet connector --- src/components/SkConnect/SkConnect.tsx | 10 +++++----- src/components/WidgetBody/WidgetBody.tsx | 12 ++++++++---- src/components/WidgetUI/WidgetUI.tsx | 6 ++++-- src/core/actions/checks.ts | 13 +++++++++---- src/core/actions/eth.ts | 3 ++- src/core/actions/index.ts | 2 +- src/core/community_pool.ts | 4 ++-- src/core/dataclasses/StepMetadata.ts | 19 ++++++++++++++++++- src/core/transfer_steps.ts | 3 ++- src/store/MetaportState.ts | 15 ++++++++++++--- 10 files changed, 63 insertions(+), 24 deletions(-) diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index 322cd11..819d0f9 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -73,7 +73,7 @@ export default function SkConnect() { if (!connected) { return (
-
+ {/*
@@ -127,8 +127,8 @@ export default function SkConnect() { chainName="wan-red-ain" size="xs" /> -
-

*/} + {/*

Connect a wallet to use SKALE Metaport -

+

*/}
- + {!!address ? : null} - {address ? :
} + {/* {address ? :
} */} +
+ {!address ? : null}
{fabTop ? null : fabButton}
diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index af93a66..eac8498 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -45,10 +45,15 @@ export async function checkEthBalance( // TODO: optimize balance checks try { const balance = await chain.ethBalance(address) log(`address: ${address}, eth balance: ${balance}, amount: ${amount}`) - const balanceEther = fromWei(balance, tokenData.meta.decimals) - if (Number(amount) + SFUEL_RESERVE_AMOUNT > Number(balanceEther)) { - checkRes.msg = `Current balance: ${balanceEther} ${tokenData.meta.symbol}. \ - ${SFUEL_RESERVE_AMOUNT} ETH will be reserved to cover transfer costs.` + const balanceEther = Number(fromWei(balance, tokenData.meta.decimals)) + let checkedAmount = Number(amount) + let msg = `Current balance: ${balanceEther} ${tokenData.meta.symbol}.` + if (chain instanceof MainnetChain) { + checkedAmount += SFUEL_RESERVE_AMOUNT + msg += ` ${SFUEL_RESERVE_AMOUNT} ETH will be reserved to cover transfer costs.` + } + if (checkedAmount > balanceEther) { + checkRes.msg = msg } else { checkRes.res = true } diff --git a/src/core/actions/eth.ts b/src/core/actions/eth.ts index ccd650e..8ce4979 100644 --- a/src/core/actions/eth.ts +++ b/src/core/actions/eth.ts @@ -98,8 +98,9 @@ export class TransferEthS2M extends TransferAction { ); if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg); + return } - this.setAmountErrorMessage(null); + this.setAmountErrorMessage(null) } } diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index 8fcad64..2312302 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -67,7 +67,7 @@ export function getActionName( export const ACTIONS: { [actionType in ActionType]: typeof Action } = { eth_m2s: TransferEthM2S, eth_s2m: TransferEthS2M, - // eth_s2s: [], + eth_unlock: UnlockEthM, wrap: WrapERC20S, unwrap: UnWrapERC20S, diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index 4345cef..9201aca 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -79,7 +79,7 @@ export async function getCommunityPoolData( } } - log('Getting community pool data', address, chainName1) + // log('Getting community pool data', address, chainName1) const balanceWei = await mainnet.communityPool.balance(address, chainName1) const accountBalanceWei = await mainnet.ethBalance(address) const activeS = await sChain.communityLocker.contract.activeUsers(address) @@ -103,7 +103,7 @@ export async function getCommunityPoolData( recommendedRechargeAmount: recommendedAmount, originalRecommendedRechargeAmount: rraWei, } - log('communityPoolData:', communityPoolData) + // log('communityPoolData:', communityPoolData) return communityPoolData } diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts index 52e196f..7172453 100644 --- a/src/core/dataclasses/StepMetadata.ts +++ b/src/core/dataclasses/StepMetadata.ts @@ -37,7 +37,8 @@ export enum ActionType { wrap = 'wrap', unwrap = 'unwrap', eth_m2s = 'eth_m2s', - eth_s2m = 'eth_s2m' + eth_s2m = 'eth_s2m', + eth_unlock = 'eth_unlock' } export function getActionType( @@ -110,3 +111,19 @@ export class UnwrapStepMetadata extends StepMetadata { super(ActionType.unwrap, from, to) } } + + +export class UnlockStepMetadata extends StepMetadata { + headline: string = 'Unlock on' + text: string = 'ETH should be unlocked on Ethereum.' + btnText: string = 'Unlock' + btnLoadingText: string = 'Unlocking' + onSource: boolean = false + + constructor( + public from: string, + public to: string, + ) { + super(ActionType.eth_unlock, from, to) + } +} diff --git a/src/core/transfer_steps.ts b/src/core/transfer_steps.ts index 2ec2049..a0a9e7d 100644 --- a/src/core/transfer_steps.ts +++ b/src/core/transfer_steps.ts @@ -28,6 +28,7 @@ import { WrapStepMetadata, UnwrapStepMetadata, TransferStepMetadata, + UnlockStepMetadata, StepMetadata, getActionType, } from './dataclasses' @@ -71,7 +72,7 @@ export function getStepsMetadata( steps.push(new TransferStepMetadata(getActionType(toChain, to, token.type), toChain, to)) } if (to === MAINNET_CHAIN_NAME && token.keyname === 'eth') { - // todo: add unlock step! + steps.push(new UnlockStepMetadata(token.chain, toChain)) } log(`Action steps metadata:`) diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 08ddb2d..5cad446 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -139,11 +139,11 @@ export const useMetaportStore = create()((set, get) => ({ set((state) => { state.check(amount, address) return { - amount: amount, + amount: amount } }), - execute: async (address: string, switchNetwork: any, walletClient: WalletClient) => { + execute: async (address: `0x${string}`, switchNetwork: any, walletClient: WalletClient) => { log('Running execute') if (get().stepsMetadata[get().currentStep]) { set({ @@ -207,7 +207,7 @@ export const useMetaportStore = create()((set, get) => ({ }, check: async (amount: string, address: string) => { - if (get().stepsMetadata[get().currentStep]) { + if (get().stepsMetadata[get().currentStep] && address) { set({ loading: true, btnText: 'Checking balance...', @@ -313,6 +313,7 @@ export const useMetaportStore = create()((set, get) => ({ destTokenContract: destTokenContract, destTokenBalance: null, destChains: Object.keys(token.connections), + amount: '' }) }, @@ -323,6 +324,10 @@ export const useMetaportStore = create()((set, get) => ({ destTokenBalance: null, updateDestTokenBalance: async (address: string) => { + if (!address) { + set({ destTokenBalance: null }) + return + } if (get().destTokenContract) { const balance = await get().mpc.tokenBalance(get().destTokenContract, address) set({ destTokenBalance: balance }) @@ -335,6 +340,10 @@ export const useMetaportStore = create()((set, get) => ({ }, updateTokenBalances: async (address: string) => { + if (!address) { + set({ tokenBalances: {} }) + return + } const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address) const chain = get().mpc.ima(get().chainName1) tokenBalances.eth = await chain.ethBalance(address) From e890973c606128edeb1c9422189d847e83eef0da Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 31 Aug 2023 16:04:56 +0100 Subject: [PATCH 039/110] Run prettier --- src/components/SFuelWarning/SFuelWarning.tsx | 11 +- .../TokenListSection/TokenListSection.tsx | 4 +- src/components/WidgetBody/WidgetBody.tsx | 8 +- src/core/actions/eth.ts | 160 ++++++++---------- src/core/actions/index.ts | 6 +- src/core/dataclasses/StepMetadata.ts | 5 +- src/index.ts | 2 +- src/metadata/metaportConfigStaging.ts | 18 +- src/store/MetaportState.ts | 14 +- src/store/SFuelStore.ts | 2 +- 10 files changed, 113 insertions(+), 117 deletions(-) diff --git a/src/components/SFuelWarning/SFuelWarning.tsx b/src/components/SFuelWarning/SFuelWarning.tsx index 72887af..cff8af1 100644 --- a/src/components/SFuelWarning/SFuelWarning.tsx +++ b/src/components/SFuelWarning/SFuelWarning.tsx @@ -29,7 +29,7 @@ import { useAccount } from 'wagmi' import Button from '@mui/material/Button' import LoadingButton from '@mui/lab/LoadingButton' import { Collapse } from '@mui/material' -import LinearProgress from '@mui/material/LinearProgress'; +import LinearProgress from '@mui/material/LinearProgress' import { MAINNET_CHAIN_NAME, SFUEL_TEXT } from '../../core/constants' import { Station } from '../../core/sfuel' @@ -191,9 +191,12 @@ export default function SFuelWarning(props: {}) { return SFUEL_TEXT['sfuel'][sFuelStatus] } - if (loading) return
- -
+ if (loading) + return ( +
+ +
+ ) return ( diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index 4fe340f..9670b54 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -65,7 +65,9 @@ export default function TokenListSection(props: {

diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 9b47304..6f51cc5 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -75,7 +75,13 @@ export function WidgetBody(props) { const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP const showStepper = - !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && sFuelOk && !!address + !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + sFuelOk && + !!address const showCP = !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME const showError = !!errorMessage diff --git a/src/core/actions/eth.ts b/src/core/actions/eth.ts index 8ce4979..7902c76 100644 --- a/src/core/actions/eth.ts +++ b/src/core/actions/eth.ts @@ -21,104 +21,90 @@ * @copyright SKALE Labs 2022-Present */ - -import debug from 'debug'; +import debug from 'debug' import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import { externalEvents } from '../events'; -import { toWei } from '../convertation'; -import { TransferAction, Action } from './action'; -import { checkEthBalance } from './checks'; - - -debug.enable('*'); -const log = debug('metaport:actions:eth'); +import { externalEvents } from '../events' +import { toWei } from '../convertation' +import { TransferAction, Action } from './action' +import { checkEthBalance } from './checks' +debug.enable('*') +const log = debug('metaport:actions:eth') export class TransferEthM2S extends TransferAction { - async execute() { - this.updateState('init') - const amountWei = toWei(this.amount, this.token.meta.decimals) - const sChainBalanceBefore = await this.sChain2.ethBalance(this.address); - const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain - this.updateState('transferETH'); - const tx = await mainnet.eth.deposit( - this.chainName2, - { - address: this.address, - value: amountWei - } - ); - const block = await this.mainnet.provider.getBlock(tx.blockNumber); - this.updateState('transferETHDone', tx.hash, block.timestamp); - await this.sChain2.waitETHBalanceChange(this.address, sChainBalanceBefore); - this.updateState('receivedETH'); - } - - async preAction() { - const checkResBalance = await checkEthBalance( - this.mainnet, - this.address, - this.amount, - this.token - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - this.setAmountErrorMessage(null) + async execute() { + this.updateState('init') + const amountWei = toWei(this.amount, this.token.meta.decimals) + const sChainBalanceBefore = await this.sChain2.ethBalance(this.address) + const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain + this.updateState('transferETH') + const tx = await mainnet.eth.deposit(this.chainName2, { + address: this.address, + value: amountWei, + }) + const block = await this.mainnet.provider.getBlock(tx.blockNumber) + this.updateState('transferETHDone', tx.hash, block.timestamp) + await this.sChain2.waitETHBalanceChange(this.address, sChainBalanceBefore) + this.updateState('receivedETH') + } + + async preAction() { + const checkResBalance = await checkEthBalance( + this.mainnet, + this.address, + this.amount, + this.token, + ) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return } + this.setAmountErrorMessage(null) + } } - export class TransferEthS2M extends TransferAction { - async execute() { - log('TransferEthS2M: started'); - this.updateState('init'); - const amountWei = toWei(this.amount, this.token.meta.decimals); - const lockedETHAmount = await this.mainnet.eth.lockedETHAmount(this.address); - const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain - this.updateState('transferETH'); - const tx = await sChain.eth.withdraw( - amountWei, - { address: this.address } - ); - const block = await this.sChain1.provider.getBlock(tx.blockNumber); - this.updateState('transferETHDone', tx.hash, block.timestamp); - await this.mainnet.eth.waitLockedETHAmountChange(this.address, lockedETHAmount); - this.updateState('receivedETH'); - } - - async preAction() { - const checkResBalance = await checkEthBalance( - this.sChain1, - this.address, - this.amount, - this.token - ); - if (!checkResBalance.res) { - this.setAmountErrorMessage(checkResBalance.msg); - return - } - this.setAmountErrorMessage(null) + async execute() { + log('TransferEthS2M: started') + this.updateState('init') + const amountWei = toWei(this.amount, this.token.meta.decimals) + const lockedETHAmount = await this.mainnet.eth.lockedETHAmount(this.address) + const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain + this.updateState('transferETH') + const tx = await sChain.eth.withdraw(amountWei, { address: this.address }) + const block = await this.sChain1.provider.getBlock(tx.blockNumber) + this.updateState('transferETHDone', tx.hash, block.timestamp) + await this.mainnet.eth.waitLockedETHAmountChange(this.address, lockedETHAmount) + this.updateState('receivedETH') + } + + async preAction() { + const checkResBalance = await checkEthBalance( + this.sChain1, + this.address, + this.amount, + this.token, + ) + if (!checkResBalance.res) { + this.setAmountErrorMessage(checkResBalance.msg) + return } + this.setAmountErrorMessage(null) + } } - export class UnlockEthM extends Action { - static label = 'Unlock ETH' - static buttonText = 'Unlock' - static loadingText = 'Unlocking' - - async execute() { - this.updateState('init'); - const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain - this.updateState('unlock'); - const tx = await mainnet.eth.getMyEth( - { address: this.address } - ); - const block = await this.mainnet.provider.getBlock(tx.blockNumber); - this.updateState('unlockDone', tx.hash, block.timestamp); - } + static label = 'Unlock ETH' + static buttonText = 'Unlock' + static loadingText = 'Unlocking' + + async execute() { + this.updateState('init') + const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain + this.updateState('unlock') + const tx = await mainnet.eth.getMyEth({ address: this.address }) + const block = await this.mainnet.provider.getBlock(tx.blockNumber) + this.updateState('unlockDone', tx.hash, block.timestamp) + } } - diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index 2312302..88f4a87 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -23,11 +23,7 @@ import debug from 'debug' -import { - TransferEthM2S, - TransferEthS2M, - UnlockEthM -} from './eth'; +import { TransferEthM2S, TransferEthS2M, UnlockEthM } from './eth' import { TransferERC20S2S, WrapERC20S, diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts index 7172453..9a87491 100644 --- a/src/core/dataclasses/StepMetadata.ts +++ b/src/core/dataclasses/StepMetadata.ts @@ -38,7 +38,7 @@ export enum ActionType { unwrap = 'unwrap', eth_m2s = 'eth_m2s', eth_s2m = 'eth_s2m', - eth_unlock = 'eth_unlock' + eth_unlock = 'eth_unlock', } export function getActionType( @@ -71,7 +71,7 @@ export abstract class StepMetadata { public type: ActionType, public from: string, public to: string, - ) { } + ) {} } export class TransferStepMetadata extends StepMetadata { @@ -112,7 +112,6 @@ export class UnwrapStepMetadata extends StepMetadata { } } - export class UnlockStepMetadata extends StepMetadata { headline: string = 'Unlock on' text: string = 'ETH should be unlocked on Ethereum.' diff --git a/src/index.ts b/src/index.ts index 42f89c2..5bf1482 100644 --- a/src/index.ts +++ b/src/index.ts @@ -63,5 +63,5 @@ export { getMetaportTheme, useWagmiAccount, PROXY_ENDPOINTS, - chainBg + chainBg, } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 07bf879..d806482 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -16,7 +16,7 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { ], tokens: { eth: { - symbol: 'ETH' + symbol: 'ETH', }, skl: { decimals: '18', @@ -81,8 +81,8 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { // "staging-perfect-parallel-gacrux": {}, // "staging-severe-violet-wezen": {}, // "staging-weepy-fitting-caph": {} - } - } + }, + }, }, erc20: { skl: { @@ -214,10 +214,10 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { address: '0xD2Aaa00700000000000000000000000000000000', chains: { mainnet: { - clone: true - } - } - } + clone: true, + }, + }, + }, }, erc20: { skl: { @@ -311,6 +311,6 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { }, theme: { mode: 'dark', - vibrant: true - } + vibrant: true, + }, } diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 5cad446..b50acf0 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -139,7 +139,7 @@ export const useMetaportStore = create()((set, get) => ({ set((state) => { state.check(amount, address) return { - amount: amount + amount: amount, } }), @@ -305,7 +305,7 @@ export const useMetaportStore = create()((set, get) => ({ token.type, provider, null, - get().chainName1 + get().chainName1, ) set({ token: token, @@ -313,7 +313,7 @@ export const useMetaportStore = create()((set, get) => ({ destTokenContract: destTokenContract, destTokenBalance: null, destChains: Object.keys(token.connections), - amount: '' + amount: '', }) }, @@ -332,7 +332,11 @@ export const useMetaportStore = create()((set, get) => ({ const balance = await get().mpc.tokenBalance(get().destTokenContract, address) set({ destTokenBalance: balance }) } else { - if (get().token && get().token.type === dataclasses.TokenType.eth && get().chainName2 === MAINNET_CHAIN_NAME) { + if ( + get().token && + get().token.type === dataclasses.TokenType.eth && + get().chainName2 === MAINNET_CHAIN_NAME + ) { const chain = get().mpc.mainnet() set({ destTokenBalance: await chain.ethBalance(address) }) } @@ -348,7 +352,7 @@ export const useMetaportStore = create()((set, get) => ({ const chain = get().mpc.ima(get().chainName1) tokenBalances.eth = await chain.ethBalance(address) set({ - tokenBalances: tokenBalances + tokenBalances: tokenBalances, }) }, diff --git a/src/store/SFuelStore.ts b/src/store/SFuelStore.ts index d464896..1aec7ad 100644 --- a/src/store/SFuelStore.ts +++ b/src/store/SFuelStore.ts @@ -69,5 +69,5 @@ export const useSFuelStore = create()((set, get) => ({ setSFuelStatus: (status: 'action' | 'warning' | 'error') => set({ sFuelStatus: status }), sFuelOk: false, - setSFuelOk: (sFuelOk: boolean) => set(() => ({ sFuelOk: sFuelOk })) + setSFuelOk: (sFuelOk: boolean) => set(() => ({ sFuelOk: sFuelOk })), })) From 7ea7643305818ebc53fffe3803701bc62eec68d3 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 5 Sep 2023 16:45:20 +0100 Subject: [PATCH 040/110] Add wrapped tokens module --- .storybook/preview.ts | 10 + package.json | 2 +- .../CommunityPool/CommunityPool.tsx | 4 +- .../DestTokenBalance/DestTokenBalance.tsx | 3 +- .../MetaportProvider/MetaportProvider.tsx | 8 +- src/components/SFuelWarning/SFuelWarning.tsx | 4 +- src/components/TokenList/TokenList.tsx | 11 +- src/components/WidgetBody/WidgetBody.tsx | 33 ++- src/components/WidgetUI/WidgetUI.tsx | 1 - .../WrappedTokens/WrappedTokens.tsx | 200 ++++++++++++++++++ src/components/WrappedTokens/index.ts | 1 + src/core/actions/action.ts | 66 +++--- src/core/actions/erc20.ts | 71 ++----- src/core/actions/index.ts | 2 + src/core/community_pool.ts | 4 +- src/core/constants.ts | 3 +- src/core/dataclasses/StepMetadata.ts | 1 + src/core/interfaces/ChainsMetadata.ts | 1 + src/core/interfaces/Tokens.ts | 2 +- src/core/metaport.ts | 56 ++++- src/index.ts | 10 +- src/store/MetaportState.ts | 79 ++++++- src/store/Store.ts | 16 ++ src/styles/styles.module.scss | 1 + 24 files changed, 471 insertions(+), 118 deletions(-) create mode 100644 src/components/WrappedTokens/WrappedTokens.tsx create mode 100644 src/components/WrappedTokens/index.ts diff --git a/.storybook/preview.ts b/.storybook/preview.ts index f3f8a70..d20699d 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -2,6 +2,16 @@ import type { Preview } from '@storybook/react' const preview: Preview = { parameters: { + grid: true, + backgrounds: { + default: 'dark', + values: [ + { + name: 'dark', + value: '#222425', + } + ] + }, actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { diff --git a/package.json b/package.json index 01296b6..a92e70e 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@mui/icons-material": "^5.8.0", "@mui/lab": "^5.0.0-alpha.88", "@mui/material": "^5.8.1", - "@rainbow-me/rainbowkit": "^1.0.8", + "@rainbow-me/rainbowkit": "^1.0.9", "@skalenetwork/ima-js": "2.0.0-develop.3", "coingecko-api-v3": "^0.0.28", "react-jazzicon": "^1.0.4", diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx index 90a3f65..2a1aaba 100644 --- a/src/components/CommunityPool/CommunityPool.tsx +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -45,7 +45,7 @@ import ErrorIcon from '@mui/icons-material/Error' import { fromWei } from '../../core/convertation' import { withdraw, recharge } from '../../core/community_pool' -import { DEFAULT_ERC20_DECIMALS } from '../../core/constants' +import { BALANCE_UPDATE_INTERVAL_MS, DEFAULT_ERC20_DECIMALS } from '../../core/constants' import { cls } from '../../core/helper' import cmn from '../../styles/cmn.module.scss' @@ -104,7 +104,7 @@ export default function CommunityPool() { updateCPData(address, chainName, chainName2, mpc) const intervalId = setInterval(() => { updateCPData(address, chainName, chainName2, mpc) - }, 10000) + }, BALANCE_UPDATE_INTERVAL_MS) return () => { clearInterval(intervalId) // Clear interval on component unmount diff --git a/src/components/DestTokenBalance/DestTokenBalance.tsx b/src/components/DestTokenBalance/DestTokenBalance.tsx index 9f67408..ad4b9ba 100644 --- a/src/components/DestTokenBalance/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance/DestTokenBalance.tsx @@ -3,6 +3,7 @@ import { useAccount } from 'wagmi' import { TokenBalance } from '../TokenList' import { useMetaportStore } from '../../store/MetaportState' +import { BALANCE_UPDATE_INTERVAL_MS } from '../../core/constants' export default function DestTokenBalance() { const { address } = useAccount() @@ -15,7 +16,7 @@ export default function DestTokenBalance() { updateDestTokenBalance(address) // Fetch users immediately on component mount const intervalId = setInterval(() => { updateDestTokenBalance(address) - }, 10000) + }, BALANCE_UPDATE_INTERVAL_MS) return () => { clearInterval(intervalId) // Clear interval on component unmount } diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index 108458c..2e23c07 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -22,14 +22,14 @@ import { ReactElement, useEffect } from 'react' -import { RainbowKitProvider } from '@rainbow-me/rainbowkit' +import { RainbowKitProvider, darkTheme, lightTheme } from '@rainbow-me/rainbowkit' import { configureChains, createConfig, WagmiConfig } from 'wagmi' import { mainnet, goerli } from 'wagmi/chains' import { jsonRpcProvider } from 'wagmi/providers/jsonRpc' import { connectorsForWallets } from '@rainbow-me/rainbowkit' import { PaletteMode } from '@mui/material' -import { injectedWallet, coinbaseWallet, metaMaskWallet } from '@rainbow-me/rainbowkit/wallets' +import { injectedWallet, coinbaseWallet, metaMaskWallet, enkryptWallet } from '@rainbow-me/rainbowkit/wallets' import { MetaportConfig } from '../../core/interfaces' @@ -81,6 +81,7 @@ const connectors = connectorsForWallets([ groupName: 'Supported Wallets', wallets: [ metaMaskWallet({ chains, projectId: '' }), + enkryptWallet({ chains }), injectedWallet({ chains }), coinbaseWallet({ chains, appName: 'TEST' }), ], @@ -90,7 +91,7 @@ const connectors = connectorsForWallets([ const wagmiConfig = createConfig({ autoConnect: true, connectors, - publicClient: webSocketPublicClient, + publicClient: webSocketPublicClient }) export default function MetaportProvider(props: { @@ -147,6 +148,7 @@ export default function MetaportProvider(props: { }} showRecentTransactions={true} chains={chains} + theme={widgetTheme.mode === 'dark' ? darkTheme() : lightTheme()} > diff --git a/src/components/SFuelWarning/SFuelWarning.tsx b/src/components/SFuelWarning/SFuelWarning.tsx index cff8af1..5f7e8f3 100644 --- a/src/components/SFuelWarning/SFuelWarning.tsx +++ b/src/components/SFuelWarning/SFuelWarning.tsx @@ -31,7 +31,7 @@ import LoadingButton from '@mui/lab/LoadingButton' import { Collapse } from '@mui/material' import LinearProgress from '@mui/material/LinearProgress' -import { MAINNET_CHAIN_NAME, SFUEL_TEXT } from '../../core/constants' +import { BALANCE_UPDATE_INTERVAL_MS, MAINNET_CHAIN_NAME, SFUEL_TEXT } from '../../core/constants' import { Station } from '../../core/sfuel' import { useMetaportStore } from '../../store/MetaportState' @@ -93,7 +93,7 @@ export default function SFuelWarning(props: {}) { updateStationsData() const intervalId = setInterval(() => { updateStationsData() - }, 10000) + }, BALANCE_UPDATE_INTERVAL_MS) return () => { clearInterval(intervalId) // Clear interval on component unmount } diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index 91dc1e1..49c8851 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -13,19 +13,16 @@ import { getAvailableTokensTotal, getDefaultToken } from '../../core/tokens/help import { cls } from '../../core/helper' -import ErrorMessage from '../ErrorMessage' - import TokenListSection from '../TokenListSection' -import TokenBalance from './TokenBalance' import TokenIcon from '../TokenIcon' import styles from '../../styles/styles.module.scss' import cmn from '../../styles/cmn.module.scss' -import { getTokenName } from '../../core/metadata' import { useCollapseStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportState' -import { TokenType, NoTokenPairsMessage } from '../../core/dataclasses' +import { TokenType } from '../../core/dataclasses' +import { BALANCE_UPDATE_INTERVAL_MS } from '../../core/constants' export default function TokenList() { const token = useMetaportStore((state) => state.token) @@ -43,10 +40,10 @@ export default function TokenList() { const { address } = useAccount() useEffect(() => { - updateTokenBalances(address) // Fetch users immediately on component mount + updateTokenBalances(address) const intervalId = setInterval(() => { updateTokenBalances(address) - }, 10000) + }, BALANCE_UPDATE_INTERVAL_MS) return () => { clearInterval(intervalId) // Clear interval on component unmount diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 6f51cc5..5ff2a72 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -17,9 +17,10 @@ import DestTokenBalance from '../DestTokenBalance' import ErrorMessage from '../ErrorMessage' import CommunityPool from '../CommunityPool' import SFuelWarning from '../SFuelWarning' +import SkConnect from '../SkConnect' +import WrappedTokens from '../WrappedTokens' import cmn from '../../styles/cmn.module.scss' -import styles from '../../styles/styles.module.scss' import { cls } from '../../core/helper' import { Collapse } from '@mui/material' import { MAINNET_CHAIN_NAME } from '../../core/constants' @@ -33,6 +34,7 @@ export function WidgetBody(props) { const setExpandedTo = useCollapseStore((state) => state.setExpandedTo) const expandedCP = useCollapseStore((state) => state.expandedCP) + const expandedWT = useCollapseStore((state) => state.expandedWT) const expandedTokens = useCollapseStore((state) => state.expandedTokens) const destChains = useMetaportStore((state) => state.destChains) @@ -50,6 +52,7 @@ export function WidgetBody(props) { const tokenBalances = useMetaportStore((state) => state.tokenBalances) const errorMessage = useMetaportStore((state) => state.errorMessage) + const loading = useMetaportStore((state) => state.loading) const transferInProgress = useMetaportStore((state) => state.transferInProgress) @@ -71,9 +74,9 @@ export function WidgetBody(props) { }, [tokens]) const showFrom = !expandedTo && !expandedTokens && !errorMessage && !expandedCP - const showTo = !expandedFrom && !expandedTokens && !errorMessage && !expandedCP - const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP - const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP + const showTo = !expandedFrom && !expandedTokens && !errorMessage && !expandedCP && !expandedWT + const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP && !expandedWT + const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && !expandedWT const showStepper = !expandedFrom && !expandedTo && @@ -81,9 +84,17 @@ export function WidgetBody(props) { !errorMessage && !expandedCP && sFuelOk && + !expandedWT && !!address const showCP = - !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME + !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME && !expandedWT + const showWT = !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + sFuelOk && + !!address const showError = !!errorMessage const grayBg = 'rgb(136 135 135 / 15%)' @@ -119,7 +130,7 @@ export function WidgetBody(props) { chains={props.config.chains} setChain={setChainName1} disabledChain={chainName2} - disabled={transferInProgress} + disabled={transferInProgress || loading} from={true} /> @@ -149,7 +160,7 @@ export function WidgetBody(props) { chains={destChains} setChain={setChainName2} disabledChain={chainName1} - disabled={transferInProgress} + disabled={transferInProgress || loading} /> @@ -160,6 +171,13 @@ export function WidgetBody(props) { + + + + + + + @@ -168,6 +186,7 @@ export function WidgetBody(props) { + {!address ? : null}
) } diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index 97bacfc..00221c6 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -109,7 +109,6 @@ export function WidgetUI(props: { config: MetaportConfig }) { {/* {address ? :
} */}
- {!address ? : null}
{fabTop ? null : fabButton}
diff --git a/src/components/WrappedTokens/WrappedTokens.tsx b/src/components/WrappedTokens/WrappedTokens.tsx new file mode 100644 index 0000000..0071682 --- /dev/null +++ b/src/components/WrappedTokens/WrappedTokens.tsx @@ -0,0 +1,200 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file WrappedTokens.ts + * @copyright SKALE Labs 2023-Present + */ + +import React, { useEffect, useState } from 'react' + +import { useAccount, useWalletClient, useSwitchNetwork } from 'wagmi' + +import Accordion from '@mui/material/Accordion' +import AccordionSummary from '@mui/material/AccordionSummary' +import AccordionDetails from '@mui/material/AccordionDetails' +import LoadingButton from '@mui/lab/LoadingButton' +import Button from '@mui/material/Button' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import ErrorIcon from '@mui/icons-material/Error' + +import SkPaper from '../SkPaper/SkPaper' +import { TokenBalance } from '../TokenList' +import TokenIcon from '../TokenIcon' + +import { getTokenName } from '../../core/metadata' +import { BALANCE_UPDATE_INTERVAL_MS } from '../../core/constants' + +import { cls, getChainAlias } from '../../core/helper' +import cmn from '../../styles/cmn.module.scss' +import styles from '../../styles/styles.module.scss' + +import { useCollapseStore } from '../../store/Store' +import { useMetaportStore } from '../../store/MetaportState' +import { TokenDataMap } from '../../core/interfaces' + +export default function WrappedTokens() { + const { data: walletClient } = useWalletClient() + const { switchNetworkAsync } = useSwitchNetwork() + + const wrappedTokens = useMetaportStore((state) => state.wrappedTokens) + const updateWrappedTokenBalances = useMetaportStore((state) => state.updateWrappedTokenBalances) + const wrappedTokenBalances = useMetaportStore((state) => state.wrappedTokenBalances) + const wrappedTokenContracts = useMetaportStore((state) => state.wrappedTokenContracts) + const unwrapAll = useMetaportStore((state) => state.unwrapAll) + + const loading = useMetaportStore((state) => state.loading) + const setLoading = useMetaportStore((state) => state.setLoading) + + const currentStep = useMetaportStore((state) => state.currentStep) + const chainName1 = useMetaportStore((state) => state.chainName1) + const mpc = useMetaportStore((state) => state.mpc) + + const { address } = useAccount() + + const expandedWT = useCollapseStore((state) => state.expandedWT) + const setExpandedWT = useCollapseStore((state) => state.setExpandedWT) + + + const [filteredTokens, setFilteredTokens] = useState({}) + + useEffect(() => { + updateWrappedTokenBalances(address) + const intervalId = setInterval(() => { + updateWrappedTokenBalances(address) + }, BALANCE_UPDATE_INTERVAL_MS) + return () => { + clearInterval(intervalId) // Clear interval on component unmount + } + }, [updateWrappedTokenBalances, wrappedTokenContracts, address]) + + useEffect(() => { + setFilteredTokens(Object.keys(wrappedTokenBalances).reduce((acc, key) => { + if (wrappedTokenBalances[key] !== 0n) { + acc[key] = wrappedTokens.erc20[key]; + } + return acc; + }, {})); + }, [wrappedTokens, wrappedTokenBalances]) + + useEffect(() => { + if (Object.keys(filteredTokens).length === 0) { + if (expandedWT && loading) { + setExpandedWT(false) + setLoading(false) + } + } + }, [filteredTokens]) + + const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { + setExpandedWT(isExpanded ? panel : false) + } + + if (Object.keys(filteredTokens).length === 0 || currentStep !== 0) return + return ( +
+ + } + aria-controls="panel1a-content" + id="panel1a-header" + > +
+
+

Wrapped tokens found

+
+
+ + +

+ ❗ You have wrapped tokens on {getChainAlias(mpc.config.skaleNetwork, chainName1)}. + Unwrap them before proceeding with your transfer. +

+
+ {Object.keys(filteredTokens).map((key, _) => ( +
+
+ +
+

+ Wrapped {getTokenName(filteredTokens[key])} +

+
+ +
+
+ ))} + +
+ {loading ? ( + + Unwrapping... + + ) : ( + + )} +
+ +
+
+
+
+
+ + ) +} diff --git a/src/components/WrappedTokens/index.ts b/src/components/WrappedTokens/index.ts new file mode 100644 index 0000000..d63087b --- /dev/null +++ b/src/components/WrappedTokens/index.ts @@ -0,0 +1 @@ +export { default } from './WrappedTokens' diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index 8cfa2f0..f8abe22 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -100,9 +100,7 @@ export class Action { walletClient: WalletClient, ) { this.mpc = mpc - // this.mainnet = mainnet; - // this.sChain1 = sChain1; - // this.sChain2 = sChain2; + this.chainName1 = chainName1 this.chainName2 = chainName2 this.address = address @@ -111,9 +109,6 @@ export class Action { this.tokenId = Number(tokenId) this.token = createTokenData(token.keyname, chainName1, token.type, this.mpc.config) - //! todo: init token here!!!!!, do not pass !!! - - // todo: init token contracts! if (isMainnet(chainName1)) { this.mainnet = this.mpc.mainnet() @@ -129,43 +124,40 @@ export class Action { const provider1 = isMainnet(chainName1) ? this.mainnet.provider : this.sChain1.provider const provider2 = isMainnet(chainName2) ? this.mainnet.provider : this.sChain2.provider - this.sourceToken = mpc.tokenContract( - chainName1, - token.keyname, - token.type, - provider1, - this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, - this.token.wrapper(this.chainName2) ? this.chainName2 : null, - ) - - this.originAddress = this.mpc.originAddress(chainName1, chainName2, token.keyname, token.type) - - if (this.token.wrapper(this.chainName2)) { - this.unwrappedToken = mpc.tokenContract(chainName1, token.keyname, token.type, provider1) - } - - // todo: use wrapper address! - const destWrapperAddress = - this.mpc.config.connections[this.chainName2][this.token.type][this.token.keyname].chains[ - this.chainName1 - ].wrapper - if (this.token.isClone(this.chainName2) && destWrapperAddress) { - this.destToken = mpc.tokenContract( - chainName2, + if (this.chainName2) { + this.sourceToken = mpc.tokenContract( + chainName1, token.keyname, token.type, - provider2, - CustomAbiTokenType.erc20wrap, - this.chainName1, + provider1, + this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, + this.token.wrapper(this.chainName2) ? this.chainName2 : null, ) - } else { - this.destToken = mpc.tokenContract(chainName2, token.keyname, token.type, provider2) + this.originAddress = this.mpc.originAddress(chainName1, chainName2, token.keyname, token.type) + + if (this.token.wrapper(this.chainName2)) { + this.unwrappedToken = mpc.tokenContract(chainName1, token.keyname, token.type, provider1) + } + + const destWrapperAddress = + this.mpc.config.connections[this.chainName2][this.token.type][this.token.keyname].chains[ + this.chainName1 + ].wrapper + if (this.token.isClone(this.chainName2) && destWrapperAddress) { + this.destToken = mpc.tokenContract( + chainName2, + token.keyname, + token.type, + provider2, + CustomAbiTokenType.erc20wrap, + this.chainName1, + ) + } else { + this.destToken = mpc.tokenContract(chainName2, token.keyname, token.type, provider2) + } } - // this.switchMetamaskChain = switchMetamaskChain; - // this.setActiveStep = setActiveStep; - // this.activeStep = activeStep; this.setAmountErrorMessage = setAmountErrorMessage this.setBtnText = setBtnText diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index 6ee8587..41210f7 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -25,6 +25,7 @@ import debug from 'debug' import { MainnetChain, SChain } from '@skalenetwork/ima-js' +import { findFirstWrapperChainName } from '../metaport' import { externalEvents } from '../events' import { toWei } from '../convertation' import { MAX_APPROVE_AMOUNT } from '../constants' @@ -194,48 +195,29 @@ export class WrapERC20S extends Action { } } -// export class UnWrapERC20S2S123 extends Action { -// static label = 'Unwrap' -// static buttonText = 'Unwrap' -// static loadingText = 'Unwrapping' -// async execute() { -// log('UnWrapERC20S2S:execute - starting'); -// const sChain = await this.getConnectedChain(this.sChain2.provider) as SChain; -// this.updateState('unwrap'); -// try { -// const amountWei = toWei(this.amount, this.token.meta.decimals); -// const tx = await sChain.erc20.unwrap( -// this.token.keyname, -// amountWei, -// { address: this.address } -// ); -// const block = await sChain.provider.getBlock(tx.blockNumber); -// this.updateState('unwrapDone', tx.hash, block.timestamp); -// externalEvents.transactionCompleted(tx, block.timestamp, this.chainName2, 'unwrap'); -// externalEvents.unwrapComplete(tx.hash, this.chainName2, this.token.keyname); -// log('UnWrapERC20S2S:execute - tx completed %O', tx); -// } finally { -// // log('UnWrapERC20S2S:execute - switchMetamaskChain back'); -// // this.switchMetamaskChain(true); -// } -// } +export class UnWrapERC20 extends Action { + async execute() { + const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain + this.updateState('unwrap') + const tokenContract = this.mpc.tokenContract( + this.chainName1, + this.token.keyname, + this.token.type, + sChain.provider, + CustomAbiTokenType.erc20wrap, + findFirstWrapperChainName(this.token) + ) + sChain.erc20.addToken(this.token.keyname, tokenContract) + const amountWei = await tokenContract.balanceOf(this.address) + const tx = await sChain.erc20.unwrap(this.token.keyname, amountWei, { address: this.address }) + const block = await sChain.provider.getBlock(tx.blockNumber) + this.updateState('unwrapDone', tx.hash, block.timestamp) + } + + async preAction() { } +} -// async preAction() { -// log('preAction: UnWrapERC20S2S'); -// const tokenContract = this.sChain2.erc20.tokens[this.token.keyname]; -// const checkResBalance = await checkERC20Balance( -// this.address, -// this.amount, -// this.token, -// tokenContract -// ); -// if (!checkResBalance.res) { -// this.setAmountErrorMessage(checkResBalance.msg); -// return -// } -// } -// } export class UnWrapERC20S extends Action { async execute() { @@ -244,15 +226,6 @@ export class UnWrapERC20S extends Action { CustomAbiTokenType.erc20wrap, this.chainName1, )) as SChain - // const token = this.mpc.tokenContract( - // this.chainName2, - // this.token.keyname, - // this.token.type, - // sChain.provider, - // CustomAbiTokenType.erc20wrap, - // this.chainName1 - // ); - // sChain.erc20.addToken(this.token.keyname, token); this.updateState('unwrap') let tx if (this.token.connections[this.chainName2].wrapsSFuel) { diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index 88f4a87..e4b9ed7 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -28,6 +28,7 @@ import { TransferERC20S2S, WrapERC20S, UnWrapERC20S, + UnWrapERC20, TransferERC20M2S, TransferERC20S2M, } from './erc20' @@ -67,6 +68,7 @@ export const ACTIONS: { [actionType in ActionType]: typeof Action } = { wrap: WrapERC20S, unwrap: UnWrapERC20S, + unwrap_stuck: UnWrapERC20, erc20_m2s: TransferERC20M2S, erc20_s2m: TransferERC20S2M, diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index 9201aca..081071f 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -38,7 +38,7 @@ import { MINIMUM_RECHARGE_AMOUNT, COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, DEFAULT_ERROR_MSG, - BALANCE_UPDATE_INTERVAL_SECONDS, + BALANCE_UPDATE_INTERVAL_MS, } from './constants' import { delay } from './helper' import { CHAIN_IDS, isMainnetChainId, getMainnetAbi } from './network' @@ -184,7 +184,7 @@ export async function recharge( let activeM = await mainnetMetamask.communityPool.contract.activeUsers(address, chainHash) let activeS = await sChain.communityLocker.contract.activeUsers(address) active = activeS && activeM - await delay(BALANCE_UPDATE_INTERVAL_SECONDS * 1000) + await delay(BALANCE_UPDATE_INTERVAL_MS) counter++ if (counter >= 10) break } diff --git a/src/core/constants.ts b/src/core/constants.ts index dff456a..bfd26cc 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -100,7 +100,8 @@ export const FAUCET_DATA = faucetJson export const RECHARGE_MULTIPLIER = 1.2 export const MINIMUM_RECHARGE_AMOUNT = 0.005 export const COMMUNITY_POOL_WITHDRAW_GAS_LIMIT = 1500000n -export const BALANCE_UPDATE_INTERVAL_SECONDS = 10 +export const _BALANCE_UPDATE_INTERVAL_SECONDS = 10 +export const BALANCE_UPDATE_INTERVAL_MS = _BALANCE_UPDATE_INTERVAL_SECONDS * 1000 export const SFUEL_RESERVE_AMOUNT = 0.02 diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts index 9a87491..def4b25 100644 --- a/src/core/dataclasses/StepMetadata.ts +++ b/src/core/dataclasses/StepMetadata.ts @@ -36,6 +36,7 @@ export enum ActionType { erc20_s2s = 'erc20_s2s', wrap = 'wrap', unwrap = 'unwrap', + unwrap_stuck = 'unwrap_stuck', eth_m2s = 'eth_m2s', eth_s2m = 'eth_s2m', eth_unlock = 'eth_unlock', diff --git a/src/core/interfaces/ChainsMetadata.ts b/src/core/interfaces/ChainsMetadata.ts index f67b6dd..92a43cc 100644 --- a/src/core/interfaces/ChainsMetadata.ts +++ b/src/core/interfaces/ChainsMetadata.ts @@ -25,6 +25,7 @@ export interface ChainMetadata { alias?: string minSfuelWei?: string faucetUrl?: string + category: string apps?: { [appName: string]: { alias: string diff --git a/src/core/interfaces/Tokens.ts b/src/core/interfaces/Tokens.ts index 262870e..4dbaa6f 100644 --- a/src/core/interfaces/Tokens.ts +++ b/src/core/interfaces/Tokens.ts @@ -32,7 +32,7 @@ export interface Token { export interface ConnectedChain { hub?: string - wrapper?: string + wrapper?: `0x${string}` wrapsSFuel?: boolean clone?: boolean } diff --git a/src/core/metaport.ts b/src/core/metaport.ts index a1b0726..eb6e5a2 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -38,6 +38,7 @@ import { ERC_ABIS } from './contracts' import debug from 'debug' import { MainnetChain, SChain } from '@skalenetwork/ima-js' +import { interfaces } from '../Metaport' const log = debug('ima:test:MainnetChain') @@ -90,6 +91,39 @@ export const createTokensMap = ( return tokens } + +export function createWrappedTokensMap( + chainName1: string, + config: MetaportConfig +): TokenDataTypesMap { + const wrappedTokens: TokenDataTypesMap = getEmptyTokenDataMap() + const tokenType = TokenType.erc20 + if (!chainName1) return wrappedTokens + Object.keys(config.connections[chainName1][tokenType]).forEach((tokenKeyname) => { + const token = config.connections[chainName1][tokenType][tokenKeyname] + const wrapperAddress = findFirstWrapperAddress(token) + if (wrapperAddress) { + addTokenData(tokenKeyname, chainName1, tokenType as TokenType, config, wrappedTokens) + } + }) + return wrappedTokens +} + + +const findFirstWrapperAddress = (token: interfaces.Token): `0x${string}` | null => + Object.values(token.chains).find(chain => 'wrapper' in chain)?.wrapper || null; + + +export const findFirstWrapperChainName = (token: TokenData): string | null => { + for (const [chainName, chain] of Object.entries(token.connections)) { + if (chain.wrapper) { + return chainName; + } + } + return null; +}; + + export default class MetaportCore { private _config: MetaportConfig @@ -122,6 +156,10 @@ export default class MetaportCore { return createTokensMap(from, to, this._config) } + wrappedTokens(chainName: string): TokenDataTypesMap { + return createWrappedTokensMap(chainName, this._config) + } + async tokenBalance(tokenContract: Contract, address: string): Promise { return await tokenContract.balanceOf(address) } @@ -143,11 +181,25 @@ export default class MetaportCore { tokenType: TokenType, chainName: string, provider: Provider, + customAbiTokenType?: CustomAbiTokenType, + // destChainName?: string ): TokenContractsMap { const contracts: TokenContractsMap = {} if (tokens[tokenType]) { Object.keys(tokens[tokenType]).forEach((tokenKeyname) => { - contracts[tokenKeyname] = this.tokenContract(chainName, tokenKeyname, tokenType, provider) + let destChainName; + if (customAbiTokenType === CustomAbiTokenType.erc20wrap) { + destChainName = findFirstWrapperChainName(tokens[tokenType][tokenKeyname]) + if (!destChainName) return + } + contracts[tokenKeyname] = this.tokenContract( + chainName, + tokenKeyname, + tokenType, + provider, + customAbiTokenType, + destChainName + ) }) } return contracts @@ -159,7 +211,7 @@ export default class MetaportCore { tokenType: TokenType, provider: Provider, customAbiTokenType?: CustomAbiTokenType, - destChainName?: string, + destChainName?: string ): Contract | undefined { let type = tokenType const token = this._config.connections[chainName][tokenType][tokenKeyname] diff --git a/src/index.ts b/src/index.ts index 5bf1482..95bdcec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,9 +25,12 @@ import DestTokenBalance from './components/DestTokenBalance' import ErrorMessage from './components/ErrorMessage' import CommunityPool from './components/CommunityPool' import SFuelWarning from './components/SFuelWarning' +import WrappedTokens from './components/WrappedTokens' -import { cls } from './core/helper' +import { cls, CHAINS_META, getChainAlias } from './core/helper' +import MetaportCore from './core/metaport' import { chainBg } from './core/metadata' +import { BASE_EXPLORER_URLS } from './core/constants' import styles from './styles/styles.module.scss' import cmn from './styles/cmn.module.scss' @@ -40,6 +43,7 @@ import { PROXY_ENDPOINTS } from './core/network' export { Metaport, MetaportProvider, + MetaportCore, SkPaper, SkConnect, ChainIcon, @@ -57,11 +61,15 @@ export { ErrorMessage, CommunityPool, SFuelWarning, + WrappedTokens, cls, styles, cmn, getMetaportTheme, useWagmiAccount, PROXY_ENDPOINTS, + BASE_EXPLORER_URLS, + CHAINS_META, chainBg, + getChainAlias } diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index b50acf0..13344ca 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -62,6 +62,14 @@ interface MetaportState { switchNetwork: (chainId: number) => void, walletClient: WalletClient, ) => void + + unwrapAll: ( + address: string, + switchNetwork: (chainId: number) => void, + walletClient: WalletClient, + tokens: interfaces.TokenDataMap + ) => void + check: (amount: string, address: `0x${string}`) => void currentStep: number @@ -87,6 +95,11 @@ interface MetaportState { tokenBalances: interfaces.TokenBalancesMap updateTokenBalances: (address: string) => Promise + wrappedTokens: interfaces.TokenDataTypesMap + wrappedTokenContracts: interfaces.TokenContractsMap + wrappedTokenBalances: interfaces.TokenBalancesMap + updateWrappedTokenBalances: (address: string) => Promise + destTokenContract: Contract destTokenBalance: bigint updateDestTokenBalance: (address: string) => Promise @@ -139,10 +152,50 @@ export const useMetaportStore = create()((set, get) => ({ set((state) => { state.check(amount, address) return { - amount: amount, + amount: amount } }), + + unwrapAll: async ( + address: `0x${string}`, + switchNetwork: any, + walletClient: WalletClient, + tokens: interfaces.TokenDataMap + ) => { + log('Running unwrapAll') + set({ loading: true }) + try { + for (const key of Object.keys(tokens)) { + await new ACTIONS.unwrap_stuck( + get().mpc, + get().chainName1, + null, + address, + get().amount, + get().tokenId, + tokens[key], + get().setAmountErrorMessage, + get().setBtnText, + switchNetwork, + walletClient, + ).execute() + } + } catch (err) { + console.error(err) + const msg = err.message ? err.message : DEFAULT_ERROR_MSG + set({ + errorMessage: new dataclasses.TransactionErrorMessage( + msg, + get().errorMessageClosedFallback + ) + }) + return + } finally { + set({ loading: false }) + } + }, + execute: async (address: `0x${string}`, switchNetwork: any, walletClient: WalletClient) => { log('Running execute') if (get().stepsMetadata[get().currentStep]) { @@ -262,6 +315,13 @@ export const useMetaportStore = create()((set, get) => ({ name, provider, ) + const wrappedTokenContracts = state.mpc.tokenContracts( + tokens, + dataclasses.TokenType.erc20, + name, + provider, + dataclasses.CustomAbiTokenType.erc20wrap + ) return { currentStep: 0, token: null, @@ -270,6 +330,9 @@ export const useMetaportStore = create()((set, get) => ({ tokenContracts: tokenContracts, tokenBalances: {}, destChains: get().mpc.config.chains, + wrappedTokens: get().mpc.wrappedTokens(name), + wrappedTokenContracts: wrappedTokenContracts, + wrappedTokenBalances: {}, ...updState, } }), @@ -317,9 +380,13 @@ export const useMetaportStore = create()((set, get) => ({ }) }, + wrappedTokens: getEmptyTokenDataMap(), tokenContracts: {}, tokenBalances: {}, + wrappedTokenContracts: {}, + wrappedTokenBalances: {}, + destTokenContract: null, destTokenBalance: null, @@ -356,6 +423,16 @@ export const useMetaportStore = create()((set, get) => ({ }) }, + updateWrappedTokenBalances: async (address: string) => { + if (!address) { + set({ wrappedTokenBalances: {} }) + return + } + set({ + wrappedTokenBalances: await get().mpc.tokenBalances(get().wrappedTokenContracts, address) + }) + }, + amountErrorMessage: null, setAmountErrorMessage: (em: string) => set(() => ({ amountErrorMessage: em })), diff --git a/src/store/Store.ts b/src/store/Store.ts index cb63089..e695a72 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -50,6 +50,9 @@ interface CollapseState { expandedCP: string | false setExpandedCP: (expanded: string | false) => void + + expandedWT: string | false + setExpandedWT: (expanded: string | false) => void } export const useCollapseStore = create()((set) => ({ @@ -60,6 +63,7 @@ export const useCollapseStore = create()((set) => ({ expandedTo: false, expandedTokens: false, expandedCP: false, + expandedWT: false })), expandedTo: false, setExpandedTo: (expanded: string | false) => @@ -68,6 +72,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTokens: false, expandedCP: false, + expandedWT: false })), expandedTokens: false, setExpandedTokens: (expanded: string | false) => @@ -76,6 +81,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedCP: false, + expandedWT: false })), expandedCP: false, setExpandedCP: (expanded: string | false) => @@ -84,5 +90,15 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedTokens: false, + expandedWT: false + })), + expandedWT: false, + setExpandedWT: (expanded: string | false) => + set(() => ({ + expandedCP: false, + expandedFrom: false, + expandedTo: false, + expandedTokens: false, + expandedWT: expanded })), })) diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index a788335..d270baa 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -152,6 +152,7 @@ button { .widgetContent { max-height: calc(100vh - 180px); overflow-y: auto; + border-radius: $sk-border-radius-outter !important; } From 4fd600903ced1d86be5ebfc51901ca6c46a45060 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 5 Sep 2023 16:46:23 +0100 Subject: [PATCH 041/110] Run prettier --- .../MetaportProvider/MetaportProvider.tsx | 9 +++-- src/components/WidgetBody/WidgetBody.tsx | 12 +++++-- .../WrappedTokens/WrappedTokens.tsx | 33 +++++++++++-------- src/core/actions/action.ts | 2 -- src/core/actions/erc20.ts | 6 ++-- src/core/metaport.ts | 20 +++++------ src/index.ts | 2 +- src/store/MetaportState.ts | 15 ++++----- src/store/Store.ts | 10 +++--- 9 files changed, 59 insertions(+), 50 deletions(-) diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index 2e23c07..4e8bf84 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -29,7 +29,12 @@ import { jsonRpcProvider } from 'wagmi/providers/jsonRpc' import { connectorsForWallets } from '@rainbow-me/rainbowkit' import { PaletteMode } from '@mui/material' -import { injectedWallet, coinbaseWallet, metaMaskWallet, enkryptWallet } from '@rainbow-me/rainbowkit/wallets' +import { + injectedWallet, + coinbaseWallet, + metaMaskWallet, + enkryptWallet, +} from '@rainbow-me/rainbowkit/wallets' import { MetaportConfig } from '../../core/interfaces' @@ -91,7 +96,7 @@ const connectors = connectorsForWallets([ const wagmiConfig = createConfig({ autoConnect: true, connectors, - publicClient: webSocketPublicClient + publicClient: webSocketPublicClient, }) export default function MetaportProvider(props: { diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 5ff2a72..04fbd48 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -76,7 +76,8 @@ export function WidgetBody(props) { const showFrom = !expandedTo && !expandedTokens && !errorMessage && !expandedCP const showTo = !expandedFrom && !expandedTokens && !errorMessage && !expandedCP && !expandedWT const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP && !expandedWT - const showSwitch = !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && !expandedWT + const showSwitch = + !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && !expandedWT const showStepper = !expandedFrom && !expandedTo && @@ -87,8 +88,13 @@ export function WidgetBody(props) { !expandedWT && !!address const showCP = - !expandedFrom && !expandedTo && !expandedTokens && chainName2 === MAINNET_CHAIN_NAME && !expandedWT - const showWT = !expandedFrom && + !expandedFrom && + !expandedTo && + !expandedTokens && + chainName2 === MAINNET_CHAIN_NAME && + !expandedWT + const showWT = + !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && diff --git a/src/components/WrappedTokens/WrappedTokens.tsx b/src/components/WrappedTokens/WrappedTokens.tsx index 0071682..869b876 100644 --- a/src/components/WrappedTokens/WrappedTokens.tsx +++ b/src/components/WrappedTokens/WrappedTokens.tsx @@ -70,7 +70,6 @@ export default function WrappedTokens() { const expandedWT = useCollapseStore((state) => state.expandedWT) const setExpandedWT = useCollapseStore((state) => state.setExpandedWT) - const [filteredTokens, setFilteredTokens] = useState({}) useEffect(() => { @@ -84,12 +83,14 @@ export default function WrappedTokens() { }, [updateWrappedTokenBalances, wrappedTokenContracts, address]) useEffect(() => { - setFilteredTokens(Object.keys(wrappedTokenBalances).reduce((acc, key) => { - if (wrappedTokenBalances[key] !== 0n) { - acc[key] = wrappedTokens.erc20[key]; - } - return acc; - }, {})); + setFilteredTokens( + Object.keys(wrappedTokenBalances).reduce((acc, key) => { + if (wrappedTokenBalances[key] !== 0n) { + acc[key] = wrappedTokens.erc20[key] + } + return acc + }, {}), + ) }, [wrappedTokens, wrappedTokenBalances]) useEffect(() => { @@ -120,8 +121,12 @@ export default function WrappedTokens() { id="panel1a-header" >
-
-

Wrapped tokens found

+
+ +
+

+ Wrapped tokens found +

@@ -156,7 +161,9 @@ export default function WrappedTokens() {
unwrapAll(address, switchNetworkAsync, walletClient, filteredTokens)} + onClick={() => + unwrapAll(address, switchNetworkAsync, walletClient, filteredTokens) + } > Unwrap all )}
-
- ) } diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index f8abe22..ed7e920 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -157,8 +157,6 @@ export class Action { } } - - this.setAmountErrorMessage = setAmountErrorMessage this.setBtnText = setBtnText this._switchNetwork = switchNetwork diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index 41210f7..61e663c 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -195,7 +195,6 @@ export class WrapERC20S extends Action { } } - export class UnWrapERC20 extends Action { async execute() { const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain @@ -206,7 +205,7 @@ export class UnWrapERC20 extends Action { this.token.type, sChain.provider, CustomAbiTokenType.erc20wrap, - findFirstWrapperChainName(this.token) + findFirstWrapperChainName(this.token), ) sChain.erc20.addToken(this.token.keyname, tokenContract) const amountWei = await tokenContract.balanceOf(this.address) @@ -215,10 +214,9 @@ export class UnWrapERC20 extends Action { this.updateState('unwrapDone', tx.hash, block.timestamp) } - async preAction() { } + async preAction() {} } - export class UnWrapERC20S extends Action { async execute() { const sChain = (await this.getConnectedChain( diff --git a/src/core/metaport.ts b/src/core/metaport.ts index eb6e5a2..4b12a4f 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -91,10 +91,9 @@ export const createTokensMap = ( return tokens } - export function createWrappedTokensMap( chainName1: string, - config: MetaportConfig + config: MetaportConfig, ): TokenDataTypesMap { const wrappedTokens: TokenDataTypesMap = getEmptyTokenDataMap() const tokenType = TokenType.erc20 @@ -109,20 +108,17 @@ export function createWrappedTokensMap( return wrappedTokens } - const findFirstWrapperAddress = (token: interfaces.Token): `0x${string}` | null => - Object.values(token.chains).find(chain => 'wrapper' in chain)?.wrapper || null; - + Object.values(token.chains).find((chain) => 'wrapper' in chain)?.wrapper || null export const findFirstWrapperChainName = (token: TokenData): string | null => { for (const [chainName, chain] of Object.entries(token.connections)) { if (chain.wrapper) { - return chainName; + return chainName } } - return null; -}; - + return null +} export default class MetaportCore { private _config: MetaportConfig @@ -187,7 +183,7 @@ export default class MetaportCore { const contracts: TokenContractsMap = {} if (tokens[tokenType]) { Object.keys(tokens[tokenType]).forEach((tokenKeyname) => { - let destChainName; + let destChainName if (customAbiTokenType === CustomAbiTokenType.erc20wrap) { destChainName = findFirstWrapperChainName(tokens[tokenType][tokenKeyname]) if (!destChainName) return @@ -198,7 +194,7 @@ export default class MetaportCore { tokenType, provider, customAbiTokenType, - destChainName + destChainName, ) }) } @@ -211,7 +207,7 @@ export default class MetaportCore { tokenType: TokenType, provider: Provider, customAbiTokenType?: CustomAbiTokenType, - destChainName?: string + destChainName?: string, ): Contract | undefined { let type = tokenType const token = this._config.connections[chainName][tokenType][tokenKeyname] diff --git a/src/index.ts b/src/index.ts index 95bdcec..c1b6042 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,5 +71,5 @@ export { BASE_EXPLORER_URLS, CHAINS_META, chainBg, - getChainAlias + getChainAlias, } diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 13344ca..ac764f2 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -67,7 +67,7 @@ interface MetaportState { address: string, switchNetwork: (chainId: number) => void, walletClient: WalletClient, - tokens: interfaces.TokenDataMap + tokens: interfaces.TokenDataMap, ) => void check: (amount: string, address: `0x${string}`) => void @@ -152,16 +152,15 @@ export const useMetaportStore = create()((set, get) => ({ set((state) => { state.check(amount, address) return { - amount: amount + amount: amount, } }), - unwrapAll: async ( address: `0x${string}`, switchNetwork: any, walletClient: WalletClient, - tokens: interfaces.TokenDataMap + tokens: interfaces.TokenDataMap, ) => { log('Running unwrapAll') set({ loading: true }) @@ -187,8 +186,8 @@ export const useMetaportStore = create()((set, get) => ({ set({ errorMessage: new dataclasses.TransactionErrorMessage( msg, - get().errorMessageClosedFallback - ) + get().errorMessageClosedFallback, + ), }) return } finally { @@ -320,7 +319,7 @@ export const useMetaportStore = create()((set, get) => ({ dataclasses.TokenType.erc20, name, provider, - dataclasses.CustomAbiTokenType.erc20wrap + dataclasses.CustomAbiTokenType.erc20wrap, ) return { currentStep: 0, @@ -429,7 +428,7 @@ export const useMetaportStore = create()((set, get) => ({ return } set({ - wrappedTokenBalances: await get().mpc.tokenBalances(get().wrappedTokenContracts, address) + wrappedTokenBalances: await get().mpc.tokenBalances(get().wrappedTokenContracts, address), }) }, diff --git a/src/store/Store.ts b/src/store/Store.ts index e695a72..b97b73c 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -63,7 +63,7 @@ export const useCollapseStore = create()((set) => ({ expandedTo: false, expandedTokens: false, expandedCP: false, - expandedWT: false + expandedWT: false, })), expandedTo: false, setExpandedTo: (expanded: string | false) => @@ -72,7 +72,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTokens: false, expandedCP: false, - expandedWT: false + expandedWT: false, })), expandedTokens: false, setExpandedTokens: (expanded: string | false) => @@ -81,7 +81,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedCP: false, - expandedWT: false + expandedWT: false, })), expandedCP: false, setExpandedCP: (expanded: string | false) => @@ -90,7 +90,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedTokens: false, - expandedWT: false + expandedWT: false, })), expandedWT: false, setExpandedWT: (expanded: string | false) => @@ -99,6 +99,6 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedTokens: false, - expandedWT: expanded + expandedWT: expanded, })), })) From abe40aa3b05521ea0fb5f04b6f0af1c6f6c0ff26 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 7 Sep 2023 21:54:35 +0100 Subject: [PATCH 042/110] Add an ability to select apps --- package.json | 2 +- src/components/ChainApps/ChainApps.tsx | 80 ++++++++++++------- src/components/ChainsList/ChainsList.tsx | 60 +++++++++----- .../CommunityPool/CommunityPool.tsx | 2 +- src/components/SFuelWarning/SFuelWarning.tsx | 4 +- src/components/Stepper/SkStepper.tsx | 32 +++++--- .../SwitchDirection/SwitchDirection.tsx | 12 ++- src/components/WidgetBody/WidgetBody.tsx | 19 +++-- .../WrappedTokens/WrappedTokens.tsx | 3 +- src/core/metadata.ts | 2 +- src/index.ts | 6 +- src/store/MetaportState.ts | 43 +++++++--- src/store/SFuelStore.ts | 8 +- src/store/Store.ts | 5 +- src/styles/cmn.module.scss | 11 +++ src/styles/styles.module.scss | 15 ++++ 16 files changed, 209 insertions(+), 95 deletions(-) diff --git a/package.json b/package.json index a92e70e..b1b0a86 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "prettier": "prettier --write \"src/**/*.{ts,tsx,js,mdx}\"", "test": "vitest", "test:cov": "vitest run --coverage", - "prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"" + "dprepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"" }, "devDependencies": { "@storybook/addon-essentials": "7.3.2", diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index 245edd4..67c3196 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -6,10 +6,13 @@ import cmn from '../../styles/cmn.module.scss' import { SkaleNetwork } from '../../core/interfaces' import ChainIcon from '../ChainIcon' +import { Button } from '@mui/material' +import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded' export default function ChainApps(props: { skaleNetwork: SkaleNetwork chain: string + handle?: (schainName: string, app?: string) => void size?: 'sm' | 'md' }) { const apps = getChainAppsMeta(props.chain, props.skaleNetwork) @@ -20,41 +23,58 @@ export default function ChainApps(props: { return (
-
+
{Object.keys(apps).map((key, _) => ( -
props.handle(props.chain, key)} + size="small" + color="inherit" + className={cls([cmn.mleft10, size === 'sm'], [cmn.mleft20, size === 'md'], cmn.mbott5)} > - -

- {getChainAlias(props.skaleNetwork, props.chain, key)} -

-
+ +

+ {getChainAlias(props.skaleNetwork, props.chain, key)} +

+
+ +
+ ))}
diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index 3acfed5..3fa9429 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -4,7 +4,6 @@ import AccordionDetails from '@mui/material/AccordionDetails' import AccordionSummary from '@mui/material/AccordionSummary' import Typography from '@mui/material/Typography' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import Tooltip from '@mui/material/Tooltip' import Button from '@mui/material/Button' import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded' @@ -13,9 +12,10 @@ import ChainIcon from '../ChainIcon' import { MetaportConfig } from '../../core/interfaces' -import { cls, getChainAlias } from '../../core/helper' +import { cls, getChainAlias, getChainAppsMeta } from '../../core/helper' import cmn from '../../styles/cmn.module.scss' import styles from '../../styles/styles.module.scss' +import SkPaper from '../SkPaper/SkPaper' export default function ChainsList(props: { config: MetaportConfig @@ -23,6 +23,8 @@ export default function ChainsList(props: { setExpanded: (expanded: string | false) => void setChain: (chain: string) => void chain: string + setApp: (chain: string) => void + app: string chains: string[] disabledChain: string from?: boolean @@ -36,14 +38,16 @@ export default function ChainsList(props: { const schainNames = [] for (let chain of props.chains) { - if (chain != props.disabledChain && chain != props.chain) { + const isHub = chain == props.chain && getChainAppsMeta(props.chain, props.config.skaleNetwork) + if (chain !== props.disabledChain && (chain != props.chain || isHub)) { schainNames.push(chain) } } - function handle(schainName) { + function handle(schainName: string, app?: string) { props.setExpanded(false) props.setChain(schainName) + props.setApp(app) } const size = props.size ?? 'sm' @@ -76,6 +80,7 @@ export default function ChainsList(props: { skaleNetwork={props.config.skaleNetwork} chainName={props.chain} size={size} + app={props.app} />

- {getChainAlias(props.config.skaleNetwork, props.chain)} + {getChainAlias(props.config.skaleNetwork, props.chain, props.app)}

- {/*
- -
*/} +
+ {props.app ? ( + +

+ on {getChainAlias(props.config.skaleNetwork, props.chain)?.split(' ')[0]} +

+
+ ) : null} +
) : (
@@ -116,9 +138,9 @@ export default function ChainsList(props: { className={cls(cmn.chainsList, cmn.mbott10, cmn.mri10)} style={{ marginLeft: '8px' }} > -
- -
+ {/*
+ +
*/} {schainNames.map((name) => (
- +
))} diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx index 2a1aaba..0d7a316 100644 --- a/src/components/CommunityPool/CommunityPool.tsx +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -79,7 +79,7 @@ export default function CommunityPool() { const { address } = useAccount() let chainName - if (token) { + if (token && chainName2) { chainName = chainName1 if (token.connections[chainName2].hub) chainName = token.connections[chainName2].hub } diff --git a/src/components/SFuelWarning/SFuelWarning.tsx b/src/components/SFuelWarning/SFuelWarning.tsx index 5f7e8f3..7d52a3d 100644 --- a/src/components/SFuelWarning/SFuelWarning.tsx +++ b/src/components/SFuelWarning/SFuelWarning.tsx @@ -74,7 +74,7 @@ export default function SFuelWarning(props: {}) { let hubChain - if (token && token.connections[chainName2].hub) { + if (token && chainName2 && token.connections[chainName2].hub) { hubChain = token.connections[chainName2].hub } @@ -191,7 +191,7 @@ export default function SFuelWarning(props: {}) { return SFUEL_TEXT['sfuel'][sFuelStatus] } - if (loading) + if (loading && chainName2) return (
diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 07c7b84..22859a3 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -22,6 +22,7 @@ import { SkaleNetwork } from '../../core/interfaces' import { useWalletClient } from 'wagmi' import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded' +import TollIcon from '@mui/icons-material/Toll' import { useSwitchNetwork, useAccount } from 'wagmi' import { SUCCESS_EMOJIS } from '../../core/constants' @@ -137,15 +138,28 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { {emoji} Transfer completed

- +
+ + +
)} diff --git a/src/components/SwitchDirection/SwitchDirection.tsx b/src/components/SwitchDirection/SwitchDirection.tsx index 93be651..5c5c08f 100644 --- a/src/components/SwitchDirection/SwitchDirection.tsx +++ b/src/components/SwitchDirection/SwitchDirection.tsx @@ -16,9 +16,14 @@ export default function SwitchDirection() { const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) - const setChainName1 = useMetaportStore((state) => state.setChainName1) const setChainName2 = useMetaportStore((state) => state.setChainName2) + + const appName1 = useMetaportStore((state) => state.appName1) + const appName2 = useMetaportStore((state) => state.appName2) + const setAppName1 = useMetaportStore((state) => state.setAppName1) + const setAppName2 = useMetaportStore((state) => state.setAppName2) + const startOver = useMetaportStore((state) => state.startOver) const loading = useMetaportStore((state) => state.loading) const transferInProgress = useMetaportStore((state) => state.transferInProgress) @@ -55,9 +60,12 @@ export default function SwitchDirection() { } } rotate() - let chain1 = chainName1 + const chain1 = chainName1 + const app1 = appName1 setChainName1(chainName2) + setAppName1(appName2) setChainName2(chain1) + setAppName2(app1) startOver() }} > diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index 04fbd48..17d5a8d 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -40,12 +40,17 @@ export function WidgetBody(props) { const destChains = useMetaportStore((state) => state.destChains) const token = useMetaportStore((state) => state.token) + const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) - const setChainName1 = useMetaportStore((state) => state.setChainName1) const setChainName2 = useMetaportStore((state) => state.setChainName2) + const appName1 = useMetaportStore((state) => state.appName1) + const appName2 = useMetaportStore((state) => state.appName2) + const setAppName1 = useMetaportStore((state) => state.setAppName1) + const setAppName2 = useMetaportStore((state) => state.setAppName2) + const mpc = useMetaportStore((state) => state.mpc) const tokens = useMetaportStore((state) => state.tokens) const setToken = useMetaportStore((state) => state.setToken) @@ -104,8 +109,8 @@ export function WidgetBody(props) { const showError = !!errorMessage const grayBg = 'rgb(136 135 135 / 15%)' - const sourceBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName1) : grayBg - const destBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName2) : grayBg + const sourceBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName1, appName1) : grayBg + const destBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName2, appName2) : grayBg return (
@@ -135,8 +140,10 @@ export function WidgetBody(props) { chain={chainName1} chains={props.config.chains} setChain={setChainName1} + setApp={setAppName1} + app={appName1} disabledChain={chainName2} - disabled={transferInProgress || loading} + disabled={transferInProgress} from={true} /> @@ -165,8 +172,10 @@ export function WidgetBody(props) { chain={chainName2} chains={destChains} setChain={setChainName2} + setApp={setAppName2} + app={appName2} disabledChain={chainName1} - disabled={transferInProgress || loading} + disabled={transferInProgress} /> diff --git a/src/components/WrappedTokens/WrappedTokens.tsx b/src/components/WrappedTokens/WrappedTokens.tsx index 869b876..671df9c 100644 --- a/src/components/WrappedTokens/WrappedTokens.tsx +++ b/src/components/WrappedTokens/WrappedTokens.tsx @@ -64,6 +64,7 @@ export default function WrappedTokens() { const currentStep = useMetaportStore((state) => state.currentStep) const chainName1 = useMetaportStore((state) => state.chainName1) const mpc = useMetaportStore((state) => state.mpc) + const transferInProgress = useMetaportStore((state) => state.transferInProgress) const { address } = useAccount() @@ -106,7 +107,7 @@ export default function WrappedTokens() { setExpandedWT(isExpanded ? panel : false) } - if (Object.keys(filteredTokens).length === 0 || currentStep !== 0) return + if (Object.keys(filteredTokens).length === 0 || currentStep !== 0 || transferInProgress) return return (
void sChain1: SChain @@ -81,11 +81,17 @@ interface MetaportState { chainName1: string chainName2: string - destChains: string[] - setChainName1: (name: string) => void setChainName2: (name: string) => void + appName1: string + appName2: string + + setAppName1: (name: string) => void + setAppName2: (name: string) => void + + destChains: string[] + tokens: interfaces.TokenDataTypesMap token: dataclasses.TokenData @@ -294,6 +300,12 @@ export const useMetaportStore = create()((set, get) => ({ chainName1: '', chainName2: '', + appName1: null, + appName2: null, + + setAppName1: (name: string) => set(() => ({ appName1: name })), + setAppName2: (name: string) => set(() => ({ appName2: name })), + destChains: [], setChainName1: (name: string) => @@ -359,16 +371,21 @@ export const useMetaportStore = create()((set, get) => ({ token: null, setToken: async (token: dataclasses.TokenData) => { - const provider = - get().chainName2 === MAINNET_CHAIN_NAME ? get().mainnetChain.provider : get().sChain2.provider - const destTokenContract = get().mpc.tokenContract( - get().chainName2, - token.keyname, - token.type, - provider, - null, - get().chainName1, - ) + let destTokenContract + if (get().chainName2) { + const provider = + get().chainName2 === MAINNET_CHAIN_NAME + ? get().mainnetChain.provider + : get().sChain2.provider + destTokenContract = get().mpc.tokenContract( + get().chainName2, + token.keyname, + token.type, + provider, + null, + get().chainName1, + ) + } set({ token: token, stepsMetadata: getStepsMetadata(get().mpc.config, token, get().chainName2), diff --git a/src/store/SFuelStore.ts b/src/store/SFuelStore.ts index 1aec7ad..fd36951 100644 --- a/src/store/SFuelStore.ts +++ b/src/store/SFuelStore.ts @@ -22,13 +22,9 @@ */ import { create } from 'zustand' -import { MainnetChain, SChain } from '@skalenetwork/ima-js' +import { Station } from '../core/sfuel' -import * as interfaces from '../core/interfaces' -import { Station, StationData } from '../core/sfuel' -import MetaportCore from '../core/metaport' - -interface SFuelState { +export interface SFuelState { loading: boolean setLoading: (loading: boolean) => void mining: boolean diff --git a/src/store/Store.ts b/src/store/Store.ts index b97b73c..eb8dabf 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -22,10 +22,9 @@ */ import { create } from 'zustand' - import * as interfaces from '../core/interfaces' -interface UIState { +export interface UIState { theme: interfaces.MetaportTheme setTheme: (theme: interfaces.MetaportTheme) => void open: boolean @@ -39,7 +38,7 @@ export const useUIStore = create()((set) => ({ setOpen: (isOpen: boolean) => set(() => ({ open: isOpen })), })) -interface CollapseState { +export interface CollapseState { expandedFrom: string | false setExpandedFrom: (expanded: string | false) => void expandedTo: string | false diff --git a/src/styles/cmn.module.scss b/src/styles/cmn.module.scss index eb499ad..b3cc0e1 100644 --- a/src/styles/cmn.module.scss +++ b/src/styles/cmn.module.scss @@ -160,6 +160,17 @@ letter-spacing: 0.04857em !important; } +.p5 { + font-size: 0.6025rem !important; + letter-spacing: 0.03857em !important; +} + +.pWrap { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + .darkTheme { .pPrim { color: white !important; diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index d270baa..15fb923 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -171,6 +171,16 @@ button { } } +.chainIcons { + width: 20px; + height: 20px; + + svg { + width: 20px; + height: 20px; + } +} + .chainIconsm { width: 26px; height: 26px; @@ -242,6 +252,11 @@ button { box-shadow: none !important; } +.btnApp { + background-color: rgba(0, 0, 0, 0.08) !important; + +} + .btnChain { width: 100%; text-align: left; From ff97ca4c39012239bfb66374c337443507dc8b53 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 8 Sep 2023 16:57:20 +0100 Subject: [PATCH 043/110] Update dependencies --- package.json | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index b1b0a86..0012981 100644 --- a/package.json +++ b/package.json @@ -34,13 +34,13 @@ "dprepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"" }, "devDependencies": { - "@storybook/addon-essentials": "7.3.2", - "@storybook/addon-interactions": "7.3.2", - "@storybook/addon-links": "7.3.2", - "@storybook/addon-styling": "1.3.6", - "@storybook/blocks": "7.3.2", - "@storybook/react": "7.3.2", - "@storybook/react-vite": "7.3.2", + "@storybook/addon-essentials": "7.4.0", + "@storybook/addon-interactions": "7.4.0", + "@storybook/addon-links": "7.4.0", + "@storybook/addon-styling": "1.3.7", + "@storybook/blocks": "7.4.0", + "@storybook/react": "7.4.0", + "@storybook/react-vite": "7.4.0", "@storybook/testing-library": "0.2.0", "@testing-library/react": "14.0.0", "@types/node": "20.4.9", @@ -67,7 +67,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.65.1", - "storybook": "7.3.2", + "storybook": "7.4.0", "typescript": "5.1.6", "vite": "4.4.9", "vite-plugin-dts": "3.5.1", @@ -75,18 +75,17 @@ "vitest": "0.34.1" }, "dependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@fontsource/roboto": "^4.5.7", - "@mui/icons-material": "^5.8.0", - "@mui/lab": "^5.0.0-alpha.88", - "@mui/material": "^5.8.1", - "@rainbow-me/rainbowkit": "^1.0.9", + "@mui/material": "^5.14.8", + "@mui/lab": "^5.0.0-alpha.143", + "@mui/icons-material": "^5.14.8", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@rainbow-me/rainbowkit": "^1.0.11", "@skalenetwork/ima-js": "2.0.0-develop.3", - "coingecko-api-v3": "^0.0.28", + "coingecko-api-v3": "^0.0.29", "react-jazzicon": "^1.0.4", - "viem": "^1.5.3", - "wagmi": "^1.3.9", + "viem": "^1.10.8", + "wagmi": "^1.4.1", "zustand": "^4.4.1" }, "peerDependencies": { @@ -101,4 +100,4 @@ "prettier -w" ] } -} +} \ No newline at end of file From d4b47d08cd8c41f93d2cc0828062041a5213cb1a Mon Sep 17 00:00:00 2001 From: Dmytro Date: Sun, 10 Sep 2023 17:52:08 +0100 Subject: [PATCH 044/110] Refactor tokens and chains state --- src/components/ChainApps/ChainApps.tsx | 1 + src/components/WidgetBody/WidgetBody.tsx | 2 +- src/core/metaport.ts | 74 +++++++++++++++ src/metadata/metaportConfigStaging.ts | 21 ++++- src/store/MetaportState.ts | 112 ++++------------------- 5 files changed, 115 insertions(+), 95 deletions(-) diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps/ChainApps.tsx index 67c3196..d51ec67 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps/ChainApps.tsx @@ -26,6 +26,7 @@ export default function ChainApps(props: {
{Object.keys(apps).map((key, _) => (
diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList/ChainsList.tsx index 3fa9429..aeddc11 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList/ChainsList.tsx @@ -73,7 +73,7 @@ export default function ChainsList(props: { cmn.flex, cmn.flexc, [cmn.mri10, size === 'sm'], - [cmn.mri15, size === 'md'], + [cmn.mri15, size === 'md'] )} > {getChainAlias(props.config.skaleNetwork, props.chain, props.app)} @@ -113,7 +113,7 @@ export default function ChainsList(props: { cmn.mbott5, cmn.mleft10, cmn.mri10, - cmn.pWrap, + cmn.pWrap )} > on {getChainAlias(props.config.skaleNetwork, props.chain)?.split(' ')[0]} @@ -157,7 +157,7 @@ export default function ChainsList(props: { [cmn.mbott5, size === 'sm'], [cmn.mtop10, size === 'md'], [cmn.mbott10, size === 'md'], - cmn.fullWidth, + cmn.fullWidth )} >
{getChainAlias(props.config.skaleNetwork, name)} diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx index 0d7a316..94b18e6 100644 --- a/src/components/CommunityPool/CommunityPool.tsx +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -53,7 +53,7 @@ import styles from '../../styles/styles.module.scss' import { useCPStore } from '../../store/CommunityPoolStore' import { useCollapseStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' export default function CommunityPool() { const { data: walletClient } = useWalletClient() @@ -147,7 +147,7 @@ export default function CommunityPool() { async () => { setLoading(false) setErrorMessage(null) - }, + } ) } @@ -164,7 +164,7 @@ export default function CommunityPool() { async () => { setLoading(false) setErrorMessage(null) - }, + } ) setExpandedCP(false) } @@ -244,7 +244,7 @@ export default function CommunityPool() { cmn.pPrim, [cmn.pDisabled, loading], cmn.flex, - cmn.mri20, + cmn.mri20 )} > ETH diff --git a/src/components/DestTokenBalance/DestTokenBalance.tsx b/src/components/DestTokenBalance/DestTokenBalance.tsx index ad4b9ba..d108d22 100644 --- a/src/components/DestTokenBalance/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance/DestTokenBalance.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react' import { useAccount } from 'wagmi' import { TokenBalance } from '../TokenList' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' import { BALANCE_UPDATE_INTERVAL_MS } from '../../core/constants' export default function DestTokenBalance() { diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index 52d34a5..a57c879 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -16,7 +16,7 @@ const ERROR_ICONS = { 'link-off': , 'public-off': , sentiment: , - error: , + error: } export default function Error(props: { errorMessage: ErrorMessage }) { diff --git a/src/components/Metaport/Metaport.stories.tsx b/src/components/Metaport/Metaport.stories.tsx index 04c5abd..b339ee6 100644 --- a/src/components/Metaport/Metaport.stories.tsx +++ b/src/components/Metaport/Metaport.stories.tsx @@ -6,7 +6,7 @@ METAPORT_CONFIG.mainnetEndpoint = import.meta.env.VITE_MAINNET_ENDPOINT const meta: Meta = { title: 'Functional/Metaport', - component: Metaport, + component: Metaport // decorators: [storyDecorator], } @@ -15,6 +15,6 @@ type Story = StoryObj export const WidgetDemo: Story = { args: { - config: METAPORT_CONFIG, - }, + config: METAPORT_CONFIG + } } diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index 4e8bf84..91b10ec 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -33,7 +33,7 @@ import { injectedWallet, coinbaseWallet, metaMaskWallet, - enkryptWallet, + enkryptWallet } from '@rainbow-me/rainbowkit/wallets' import { MetaportConfig } from '../../core/interfaces' @@ -50,7 +50,7 @@ import { getWidgetTheme, getMuiZIndex } from '../../core/themes' import { cls } from '../../core/helper' import { useUIStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' import MetaportCore from '../../core/metaport' import styles from '../../styles/styles.module.scss' @@ -69,16 +69,16 @@ const { chains, webSocketPublicClient } = configureChains( constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), constructWagmiChain('mainnet', 'elated-tan-skat'), - constructWagmiChain('mainnet', 'affectionate-immediate-pollux'), + constructWagmiChain('mainnet', 'affectionate-immediate-pollux') ], [ jsonRpcProvider({ rpc: (chain) => ({ http: chain.rpcUrls.default.http[0], - webSocket: getWebSocketUrl(chain), - }), - }), - ], + webSocket: getWebSocketUrl(chain) + }) + }) + ] ) const connectors = connectorsForWallets([ @@ -88,15 +88,15 @@ const connectors = connectorsForWallets([ metaMaskWallet({ chains, projectId: '' }), enkryptWallet({ chains }), injectedWallet({ chains }), - coinbaseWallet({ chains, appName: 'TEST' }), - ], - }, + coinbaseWallet({ chains, appName: 'TEST' }) + ] + } ]) const wagmiConfig = createConfig({ autoConnect: true, connectors, - publicClient: webSocketPublicClient, + publicClient: webSocketPublicClient }) export default function MetaportProvider(props: { @@ -131,15 +131,15 @@ export default function MetaportProvider(props: { palette: { mode: widgetTheme.mode as PaletteMode, background: { - paper: widgetTheme.background, + paper: widgetTheme.background }, primary: { - main: widgetTheme.primary, + main: widgetTheme.primary }, secondary: { - main: widgetTheme.background, - }, - }, + main: widgetTheme.background + } + } }) if (!metaportTheme) return
@@ -149,7 +149,7 @@ export default function MetaportProvider(props: { { // Note: If your app doesn't use authentication, you // can remove all 'authenticationStatus' checks @@ -65,8 +65,8 @@ export default function SkConnect() { style: { opacity: 0, pointerEvents: 'none', - userSelect: 'none', - }, + userSelect: 'none' + } })} > {(() => { diff --git a/src/components/SkPaper/SkPaper.tsx b/src/components/SkPaper/SkPaper.tsx index de06ad8..e122ec0 100644 --- a/src/components/SkPaper/SkPaper.tsx +++ b/src/components/SkPaper/SkPaper.tsx @@ -40,7 +40,7 @@ export default function SkPaper(props: { }) { const metaportTheme = useUIStore((state) => state.theme) const localStyle = { - background: props.background ?? metaportTheme.background, + background: props.background ?? metaportTheme.background } return (
{props.children} diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 22859a3..c2ce37c 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -14,7 +14,7 @@ import styles from '../../styles/styles.module.scss' import localStyles from './SkStepper.module.scss' import ChainIcon from '../ChainIcon' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' import { useCPStore } from '../../store/CommunityPoolStore' import { Collapse } from '@mui/material' import { SkaleNetwork } from '../../core/interfaces' @@ -36,7 +36,6 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { const stepsMetadata = useMetaportStore((state) => state.stepsMetadata) const currentStep = useMetaportStore((state) => state.currentStep) const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage) - const actionBtnDisabled = useMetaportStore((state) => state.actionBtnDisabled) const loading = useMetaportStore((state) => state.loading) const btnText = useMetaportStore((state) => state.btnText) @@ -103,7 +102,6 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { disabled={ !!( amountErrorMessage || - actionBtnDisabled || loading || amount == '' || !cpData.exitGasOk @@ -132,7 +130,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { cmn.pPrim, cmn.flexg, cmn.pCent, - cmn.mtop20, + cmn.mtop20 )} > {emoji} Transfer completed diff --git a/src/components/SwitchDirection/SwitchDirection.tsx b/src/components/SwitchDirection/SwitchDirection.tsx index 5c5c08f..f82dca5 100644 --- a/src/components/SwitchDirection/SwitchDirection.tsx +++ b/src/components/SwitchDirection/SwitchDirection.tsx @@ -7,7 +7,7 @@ import cmn from '../../styles/cmn.module.scss' import { cls } from '../../core/helper' import { useUIStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' export default function SwitchDirection() { const myElement = useRef(null) @@ -37,7 +37,7 @@ export default function SwitchDirection() { style={{ background: metaportTheme.background, borderRadius: '50%', - zIndex: metaportTheme.zIndex, + zIndex: metaportTheme.zIndex }} > { diff --git a/src/components/TokenList/TokenBalance.tsx b/src/components/TokenList/TokenBalance.tsx index be28657..860219f 100644 --- a/src/components/TokenList/TokenBalance.tsx +++ b/src/components/TokenList/TokenBalance.tsx @@ -40,7 +40,7 @@ export default function TokenBalance(props: { [cmn.pPrim, props.primary], cmn.flex, cmn.flexcv, - cmn.mri5, + cmn.mri5 )} > {balance} {props.symbol} diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList/TokenList.tsx index 49c8851..6b23ce6 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList/TokenList.tsx @@ -20,7 +20,7 @@ import styles from '../../styles/styles.module.scss' import cmn from '../../styles/cmn.module.scss' import { useCollapseStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' import { TokenType } from '../../core/dataclasses' import { BALANCE_UPDATE_INTERVAL_MS } from '../../core/constants' @@ -97,7 +97,7 @@ export default function TokenList() { [cmn.pDisabled, noTokens], cmn.flex, cmn.flexg, - cmn.mri10, + cmn.mri10 )} > {tokensText} diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection/TokenListSection.tsx index 9670b54..063c407 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection/TokenListSection.tsx @@ -58,7 +58,7 @@ export default function TokenListSection(props: { cmn.flex, cmn.flexg, cmn.mri10, - cmn.mleft10, + cmn.mleft10 )} > {getTokenName(props.tokens[key])} diff --git a/src/components/WidgetBody/WidgetBody.tsx b/src/components/WidgetBody/WidgetBody.tsx index a427db9..7a057cb 100644 --- a/src/components/WidgetBody/WidgetBody.tsx +++ b/src/components/WidgetBody/WidgetBody.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react' import { useAccount } from 'wagmi' import { useCollapseStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' import { useSFuelStore } from '../../store/SFuelStore' import { useUIStore } from '../../store/Store' diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index 00221c6..e3bab31 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -32,7 +32,7 @@ import CloseIcon from '@mui/icons-material/Close' import skaleLogo from './skale_logo_short.svg' import { useUIStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' import SkPaper from '../SkPaper' import WidgetBody from '../WidgetBody' @@ -80,7 +80,7 @@ export function WidgetUI(props: { config: MetaportConfig }) { {isOpen ? ( ) : ( diff --git a/src/components/WrappedTokens/WrappedTokens.tsx b/src/components/WrappedTokens/WrappedTokens.tsx index 671df9c..6825005 100644 --- a/src/components/WrappedTokens/WrappedTokens.tsx +++ b/src/components/WrappedTokens/WrappedTokens.tsx @@ -45,7 +45,7 @@ import cmn from '../../styles/cmn.module.scss' import styles from '../../styles/styles.module.scss' import { useCollapseStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportState' +import { useMetaportStore } from '../../store/MetaportStore' import { TokenDataMap } from '../../core/interfaces' export default function WrappedTokens() { @@ -90,7 +90,7 @@ export default function WrappedTokens() { acc[key] = wrappedTokens.erc20[key] } return acc - }, {}), + }, {}) ) }, [wrappedTokens, wrappedTokenBalances]) @@ -138,7 +138,10 @@ export default function WrappedTokens() {

{Object.keys(filteredTokens).map((key, _) => ( -
+
Wrapped {getTokenName(filteredTokens[key])} diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index ed7e920..4b7cea4 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -97,7 +97,7 @@ export class Action { setAmountErrorMessage: (amountErrorMessage: string) => void, setBtnText: (btnText: string) => void, switchNetwork: (chainId: number | bigint) => Chain | undefined, - walletClient: WalletClient, + walletClient: WalletClient ) { this.mpc = mpc @@ -131,7 +131,7 @@ export class Action { token.type, provider1, this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, - this.token.wrapper(this.chainName2) ? this.chainName2 : null, + this.token.wrapper(this.chainName2) ? this.chainName2 : null ) this.originAddress = this.mpc.originAddress(chainName1, chainName2, token.keyname, token.type) @@ -150,7 +150,7 @@ export class Action { token.type, provider2, CustomAbiTokenType.erc20wrap, - this.chainName1, + this.chainName1 ) } else { this.destToken = mpc.tokenContract(chainName2, token.keyname, token.type, provider2) @@ -189,10 +189,10 @@ export class Action { address: this.address, amount: this.amount, amountWei: this.amountWei, - tokenId: this.tokenId, + tokenId: this.tokenId }, transactionHash, - timestamp, + timestamp ) this.setBtnText(LOADING_BUTTON_TEXT[currentState]) } @@ -200,7 +200,7 @@ export class Action { async getConnectedChain( provider: Provider, customAbiTokenType?: CustomAbiTokenType, - destChainName?: string, + destChainName?: string ): Promise { let chain: MainnetChain | SChain this.updateState('switch') @@ -227,7 +227,7 @@ export class Action { this.token.type, chain.provider, customAbiTokenType, - destChainName, + destChainName ) chain.erc20.addToken(this.token.keyname, token) return chain diff --git a/src/core/actions/actionState.ts b/src/core/actions/actionState.ts index 415e888..913aff9 100644 --- a/src/core/actions/actionState.ts +++ b/src/core/actions/actionState.ts @@ -63,5 +63,5 @@ export const LOADING_BUTTON_TEXT: LoadingButtonTextMap = { unwrapDone: 'Tokens unwrapped', switch: 'Waiting for network switch', unlock: 'Unlocking ETH', - unlockDone: 'ETH unlocked', + unlockDone: 'ETH unlocked' } diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index eac8498..1470b8b 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -38,7 +38,7 @@ export async function checkEthBalance( // TODO: optimize balance checks chain: MainnetChain | SChain, address: string, amount: string, - tokenData: TokenData, + tokenData: TokenData ): Promise { const checkRes: interfaces.CheckRes = { res: false } @@ -69,7 +69,7 @@ export async function checkERC20Balance( address: string, amount: string, tokenData: TokenData, - tokenContract: Contract, + tokenContract: Contract ): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes @@ -93,7 +93,7 @@ export async function checkERC20Balance( export async function checkSFuelBalance( address: string, amount: string, - sChain: SChain, + sChain: SChain ): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes @@ -120,7 +120,7 @@ export async function checkERC20Allowance( approvalAddress: string, amount: string, tokenData: TokenData, - tokenContract: Contract, + tokenContract: Contract ): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes @@ -141,7 +141,7 @@ export async function checkERC721( address: string, approvalAddress: string, tokenId: number, - tokenContract: Contract, + tokenContract: Contract ): Promise { let approvedAddress: string const checkRes: interfaces.CheckRes = { res: true, approved: false } @@ -176,7 +176,7 @@ export async function checkERC1155( tokenId: number, amount: string, tokenData: TokenData, - tokenContract: Contract, + tokenContract: Contract ): Promise { const checkRes: interfaces.CheckRes = { res: true, approved: false } if (!tokenId || !amount) return checkRes diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index 61e663c..fa39cc2 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -45,12 +45,12 @@ export class TransferERC20S2S extends TransferAction { this.sChain1.erc20.address, this.amount, this.token, - this.sourceToken, + this.sourceToken ) const sChain = (await this.getConnectedChain( this.sChain1.provider, this.token.wrapper(this.chainName2) ? CustomAbiTokenType.erc20wrap : null, - this.token.wrapper(this.chainName2) ? this.chainName2 : null, + this.token.wrapper(this.chainName2) ? this.chainName2 : null )) as SChain if (!checkResAllowance.res) { this.updateState('approve') @@ -59,8 +59,8 @@ export class TransferERC20S2S extends TransferAction { MAX_APPROVE_AMOUNT, sChain.erc20.address, { - address: this.address, - }, + address: this.address + } ) const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) @@ -86,7 +86,7 @@ export class TransferERC20S2S extends TransferAction { balanceOnDestination = await this.sChain2.getERC20Balance(this.destToken, this.address) } const tx = await sChain.erc20.transferToSchain(this.chainName2, this.originAddress, amountWei, { - address: this.address, + address: this.address }) const block = await sChain.provider.getBlock(tx.blockNumber) this.updateState('transferDone', tx.hash, block.timestamp) @@ -103,7 +103,7 @@ export class TransferERC20S2S extends TransferAction { this.address, this.amount, this.token, - this.sourceToken, + this.sourceToken ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) @@ -119,7 +119,7 @@ export class WrapSFuelERC20S extends Action { this.updateState('wrap') const tx = await this.sChain1.erc20.fundExit(this.token.keyname, { address: this.address, - value: this.amountWei, + value: this.amountWei }) const block = await this.sChain1.provider.getBlock(tx.blockNumber) this.updateState('wrapDone', tx.hash, block.timestamp) @@ -146,7 +146,7 @@ export class WrapERC20S extends Action { this.token.connections[this.chainName2].wrapper, this.amount, this.token, - this.unwrappedToken, + this.unwrappedToken ) const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain const wrapperToken = this.mpc.tokenContract( @@ -155,7 +155,7 @@ export class WrapERC20S extends Action { this.token.type, sChain.provider, CustomAbiTokenType.erc20wrap, - this.chainName2, + this.chainName2 ) sChain.erc20.addToken(`wrap_${this.token.keyname}`, wrapperToken) if (!checkResAllowance.res) { @@ -165,8 +165,8 @@ export class WrapERC20S extends Action { MAX_APPROVE_AMOUNT, this.token.address, { - address: this.address, - }, + address: this.address + } ) const txBlock = await this.sChain1.provider.getBlock(approveTx.blockNumber) this.updateState('approveWrapDone', approveTx.hash, txBlock.timestamp) @@ -174,7 +174,7 @@ export class WrapERC20S extends Action { this.updateState('wrap') const amountWei = toWei(this.amount, this.token.meta.decimals) const tx = await sChain.erc20.wrap(`wrap_${this.token.keyname}`, amountWei, { - address: this.address, + address: this.address }) const block = await this.sChain1.provider.getBlock(tx.blockNumber) this.updateState('wrapDone', tx.hash, block.timestamp) @@ -185,7 +185,7 @@ export class WrapERC20S extends Action { this.address, this.amount, this.token, - this.unwrappedToken, + this.unwrappedToken ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) @@ -205,7 +205,7 @@ export class UnWrapERC20 extends Action { this.token.type, sChain.provider, CustomAbiTokenType.erc20wrap, - findFirstWrapperChainName(this.token), + findFirstWrapperChainName(this.token) ) sChain.erc20.addToken(this.token.keyname, tokenContract) const amountWei = await tokenContract.balanceOf(this.address) @@ -222,7 +222,7 @@ export class UnWrapERC20S extends Action { const sChain = (await this.getConnectedChain( this.sChain2.provider, CustomAbiTokenType.erc20wrap, - this.chainName1, + this.chainName1 )) as SChain this.updateState('unwrap') let tx @@ -246,7 +246,7 @@ export class UnWrapERC20S extends Action { this.address, this.amount, this.token, - tokenContract, + tokenContract ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) @@ -265,13 +265,13 @@ export class TransferERC20M2S extends TransferAction { this.mainnet.erc20.address, this.amount, this.token, - this.sourceToken, + this.sourceToken ) const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain if (!checkResAllowance.res) { this.updateState('approve') const approveTx = await mainnet.erc20.approve(this.token.keyname, MAX_APPROVE_AMOUNT, { - address: this.address, + address: this.address }) const txBlock = await mainnet.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) @@ -281,7 +281,7 @@ export class TransferERC20M2S extends TransferAction { // const destTokenContract = this.sChain2.erc20.tokens[this.token.keyname]; const balanceOnDestination = await this.sChain2.getERC20Balance(this.destToken, this.address) const tx = await await mainnet.erc20.deposit(this.chainName2, this.token.keyname, amountWei, { - address: this.address, + address: this.address }) const block = await mainnet.provider.getBlock(tx.blockNumber) this.updateState('transferDone', tx.hash, block.timestamp) @@ -295,7 +295,7 @@ export class TransferERC20M2S extends TransferAction { this.chainName1, this.chainName2, this.token.keyname, - false, + false ) } @@ -304,7 +304,7 @@ export class TransferERC20M2S extends TransferAction { this.address, this.amount, this.token, - this.sourceToken, + this.sourceToken ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) @@ -324,7 +324,7 @@ export class TransferERC20S2M extends TransferAction { this.sChain1.erc20.address, this.amount, this.token, - this.sourceToken, + this.sourceToken ) const sChain = (await this.getConnectedChain(this.sChain1.provider)) as SChain if (!checkResAllowance.res) { @@ -334,8 +334,8 @@ export class TransferERC20S2M extends TransferAction { MAX_APPROVE_AMOUNT, sChain.erc20.address, { - address: this.address, - }, + address: this.address + } ) const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) @@ -358,7 +358,7 @@ export class TransferERC20S2M extends TransferAction { this.chainName1, this.chainName2, this.token.keyname, - false, + false ) } @@ -367,7 +367,7 @@ export class TransferERC20S2M extends TransferAction { this.address, this.amount, this.token, - this.sourceToken, + this.sourceToken ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) diff --git a/src/core/actions/eth.ts b/src/core/actions/eth.ts index 7902c76..d1fd378 100644 --- a/src/core/actions/eth.ts +++ b/src/core/actions/eth.ts @@ -41,7 +41,7 @@ export class TransferEthM2S extends TransferAction { this.updateState('transferETH') const tx = await mainnet.eth.deposit(this.chainName2, { address: this.address, - value: amountWei, + value: amountWei }) const block = await this.mainnet.provider.getBlock(tx.blockNumber) this.updateState('transferETHDone', tx.hash, block.timestamp) @@ -54,7 +54,7 @@ export class TransferEthM2S extends TransferAction { this.mainnet, this.address, this.amount, - this.token, + this.token ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) @@ -84,7 +84,7 @@ export class TransferEthS2M extends TransferAction { this.sChain1, this.address, this.amount, - this.token, + this.token ) if (!checkResBalance.res) { this.setAmountErrorMessage(checkResBalance.msg) diff --git a/src/core/actions/index.ts b/src/core/actions/index.ts index e4b9ed7..be0ddcd 100644 --- a/src/core/actions/index.ts +++ b/src/core/actions/index.ts @@ -30,7 +30,7 @@ import { UnWrapERC20S, UnWrapERC20, TransferERC20M2S, - TransferERC20S2M, + TransferERC20S2M } from './erc20' import { Action } from './action' @@ -45,7 +45,7 @@ const log = debug('metaport:actions') export function getActionName( chainName1: string, chainName2: string, - tokenType: TokenType, + tokenType: TokenType ): string { if (!chainName1 || !chainName2 || !tokenType) return log(`Getting action name: ${chainName1} ${chainName2} ${tokenType}`) @@ -64,6 +64,7 @@ export function getActionName( export const ACTIONS: { [actionType in ActionType]: typeof Action } = { eth_m2s: TransferEthM2S, eth_s2m: TransferEthS2M, + eth_s2s: TransferERC20S2S, eth_unlock: UnlockEthM, wrap: WrapERC20S, @@ -72,7 +73,7 @@ export const ACTIONS: { [actionType in ActionType]: typeof Action } = { erc20_m2s: TransferERC20M2S, erc20_s2m: TransferERC20S2M, - erc20_s2s: TransferERC20S2S, + erc20_s2s: TransferERC20S2S // erc721_m2s: [TransferERC721M2S], // erc721_s2m: [TransferERC721S2M], diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index 081071f..7666340 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -38,7 +38,7 @@ import { MINIMUM_RECHARGE_AMOUNT, COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, DEFAULT_ERROR_MSG, - BALANCE_UPDATE_INTERVAL_MS, + BALANCE_UPDATE_INTERVAL_MS } from './constants' import { delay } from './helper' import { CHAIN_IDS, isMainnetChainId, getMainnetAbi } from './network' @@ -56,7 +56,7 @@ export function getEmptyCommunityPoolData(): CommunityPoolData { balance: null, accountBalance: null, recommendedRechargeAmount: null, - originalRecommendedRechargeAmount: null, + originalRecommendedRechargeAmount: null } } @@ -65,7 +65,7 @@ export async function getCommunityPoolData( chainName1: string, chainName2: string, mainnet: MainnetChain, - sChain: SChain, + sChain: SChain ): Promise { if (chainName2 !== MAINNET_CHAIN_NAME) { // log('not a S2M transfer, skipping community pool check') @@ -75,7 +75,7 @@ export async function getCommunityPoolData( balance: null, accountBalance: null, recommendedRechargeAmount: null, - originalRecommendedRechargeAmount: null, + originalRecommendedRechargeAmount: null } } @@ -88,7 +88,7 @@ export async function getCommunityPoolData( const rraWei = await mainnet.communityPool.contract.getRecommendedRechargeAmount( chainHash, - address, + address ) const rraEther = fromWei(rraWei as string, DEFAULT_ERC20_DECIMALS) @@ -101,7 +101,7 @@ export async function getCommunityPoolData( balance: balanceWei, accountBalance: accountBalanceWei, recommendedRechargeAmount: recommendedAmount, - originalRecommendedRechargeAmount: rraWei, + originalRecommendedRechargeAmount: rraWei } // log('communityPoolData:', communityPoolData) return communityPoolData @@ -110,7 +110,7 @@ export async function getCommunityPoolData( export async function connectedMainnetChain( mpc: MetaportCore, walletClient: WalletClient, - switchNetwork: (chainId: number | bigint) => Promise, + switchNetwork: (chainId: number | bigint) => Promise ): Promise { const currentChainId = walletClient.chain.id const chainId = CHAIN_IDS[mpc.config.skaleNetwork] @@ -136,7 +136,7 @@ export async function withdraw( switchNetwork: (chainId: number | bigint) => Promise, setLoading: (loading: string | false) => void, setErrorMessage: (errorMessage: dataclasses.ErrorMessage) => void, - errorMessageClosedFallback: () => void, + errorMessageClosedFallback: () => void ) { setLoading('withdraw') try { @@ -144,7 +144,7 @@ export async function withdraw( const mainnetMetamask = await connectedMainnetChain(mpc, walletClient, switchNetwork) await mainnetMetamask.communityPool.withdraw(chainName, amount, { address: address, - customGasLimit: COMMUNITY_POOL_WITHDRAW_GAS_LIMIT, + customGasLimit: COMMUNITY_POOL_WITHDRAW_GAS_LIMIT }) setLoading(false) } catch (err) { @@ -163,7 +163,7 @@ export async function recharge( switchNetwork: (chainId: number | bigint) => Promise, setLoading: (loading: string | false) => void, setErrorMessage: (errorMessage: dataclasses.ErrorMessage) => void, - errorMessageClosedFallback: () => void, + errorMessageClosedFallback: () => void ) { setLoading('recharge') try { @@ -173,7 +173,7 @@ export async function recharge( const mainnetMetamask = await connectedMainnetChain(mpc, walletClient, switchNetwork) await mainnetMetamask.communityPool.recharge(chainName, address, { address: address, - value: toWei(amount, DEFAULT_ERC20_DECIMALS), + value: toWei(amount, DEFAULT_ERC20_DECIMALS) }) setLoading('activate') let active = false diff --git a/src/core/constants.ts b/src/core/constants.ts index bfd26cc..472c179 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -52,14 +52,14 @@ export const MAINNET_EXPLORER_URLS: { [skaleNetwork: string]: string } = { mainnet: 'https://etherscan.io', staging: 'https://goerli.etherscan.io/', legacy: 'https://goerli.etherscan.io/', - regression: 'https://goerli.etherscan.io/', + regression: 'https://goerli.etherscan.io/' } export const BASE_EXPLORER_URLS = { mainnet: 'explorer.mainnet.skalenodes.com', staging: 'explorer.staging-v3.skalenodes.com', legacy: 'explorer.staging-v3.skalenodes.com', - regression: 'regression-explorer.skalenodes.com', + regression: 'regression-explorer.skalenodes.com' } // ETA constants @@ -80,12 +80,12 @@ export const SFUEL_CHECKS_INTERVAL = 8 export const SFUEL_TEXT = { sfuel: { warning: 'You need sFUEL on the destination chain', - error: 'You need sFUEL to perform a transfer', + error: 'You need sFUEL to perform a transfer' }, gas: { warning: 'You need ETH on the destination chain', - error: 'You need ETH to perform a transfer', - }, + error: 'You need ETH to perform a transfer' + } } // faucet constants diff --git a/src/core/contracts.ts b/src/core/contracts.ts index 9ada13d..3dd8614 100644 --- a/src/core/contracts.ts +++ b/src/core/contracts.ts @@ -45,17 +45,17 @@ export const ERC_ABIS: { [tokenType in CustomAbiTokenType | TokenType]: { ['abi' sfuelwrap: sFuelWrapperAbi, erc721: erc721Abi, erc721meta: erc721MetaAbi, - erc1155: erc1155Abi, + erc1155: erc1155Abi } export const IMA_ADDRESSES = { mainnet: mainnetAddresses, staging: stagingAddresses, legacy: legacyAddresses, - regression: regressionAddresses, + regression: regressionAddresses } export const IMA_ABIS = { mainnet: mainnetAbi, - schain: sChainAbi, + schain: sChainAbi } diff --git a/src/core/dataclasses/Position.ts b/src/core/dataclasses/Position.ts index 16abbf0..9757dc0 100644 --- a/src/core/dataclasses/Position.ts +++ b/src/core/dataclasses/Position.ts @@ -38,5 +38,5 @@ export const Positions: PositionMap = { topLeft: { top: DEFAULT_MP_MARGIN, left: DEFAULT_MP_MARGIN, right: 'auto', bottom: 'auto' }, topRight: { top: DEFAULT_MP_MARGIN, left: 'auto', right: DEFAULT_MP_MARGIN, bottom: 'auto' }, bottomRight: { top: 'auto', left: 'auto', right: DEFAULT_MP_MARGIN, bottom: DEFAULT_MP_MARGIN }, - bottomLeft: { top: 'auto', left: DEFAULT_MP_MARGIN, right: 'auto', bottom: DEFAULT_MP_MARGIN }, + bottomLeft: { top: 'auto', left: DEFAULT_MP_MARGIN, right: 'auto', bottom: DEFAULT_MP_MARGIN } } diff --git a/src/core/dataclasses/StepMetadata.ts b/src/core/dataclasses/StepMetadata.ts index def4b25..31b0b4b 100644 --- a/src/core/dataclasses/StepMetadata.ts +++ b/src/core/dataclasses/StepMetadata.ts @@ -39,13 +39,14 @@ export enum ActionType { unwrap_stuck = 'unwrap_stuck', eth_m2s = 'eth_m2s', eth_s2m = 'eth_s2m', - eth_unlock = 'eth_unlock', + eth_s2s = 'eth_s2s', + eth_unlock = 'eth_unlock' } export function getActionType( chainName1: string, chainName2: string, - tokenType: TokenType, + tokenType: TokenType ): ActionType { if (!chainName1 || !chainName2 || !tokenType) return let postfix = S2S_POSTFIX @@ -71,7 +72,7 @@ export abstract class StepMetadata { constructor( public type: ActionType, public from: string, - public to: string, + public to: string ) {} } @@ -92,7 +93,7 @@ export class WrapStepMetadata extends StepMetadata { constructor( public from: string, - public to: string, + public to: string ) { super(ActionType.wrap, from, to) } @@ -107,7 +108,7 @@ export class UnwrapStepMetadata extends StepMetadata { constructor( public from: string, - public to: string, + public to: string ) { super(ActionType.unwrap, from, to) } @@ -122,7 +123,7 @@ export class UnlockStepMetadata extends StepMetadata { constructor( public from: string, - public to: string, + public to: string ) { super(ActionType.eth_unlock, from, to) } diff --git a/src/core/dataclasses/TokenData.ts b/src/core/dataclasses/TokenData.ts index f5694d2..88f17f6 100644 --- a/src/core/dataclasses/TokenData.ts +++ b/src/core/dataclasses/TokenData.ts @@ -39,7 +39,7 @@ export class TokenData { tokenKeyname: string, metadata: TokenMetadata, connections: ConnectedChainMap, - chain: string, + chain: string ) { this.address = address this.meta = metadata diff --git a/src/core/dataclasses/TokenType.ts b/src/core/dataclasses/TokenType.ts index d41e9c0..5b7d717 100644 --- a/src/core/dataclasses/TokenType.ts +++ b/src/core/dataclasses/TokenType.ts @@ -26,10 +26,10 @@ export enum TokenType { erc20 = 'erc20', erc721 = 'erc721', erc721meta = 'erc721meta', - erc1155 = 'erc1155', + erc1155 = 'erc1155' } export enum CustomAbiTokenType { erc20wrap = 'erc20wrap', - sfuelwrap = 'sfuelwrap', + sfuelwrap = 'sfuelwrap' } diff --git a/src/core/dataclasses/TransferRequestStatus.ts b/src/core/dataclasses/TransferRequestStatus.ts index 42c56a0..cd4ffad 100644 --- a/src/core/dataclasses/TransferRequestStatus.ts +++ b/src/core/dataclasses/TransferRequestStatus.ts @@ -27,5 +27,5 @@ export enum TransferRequestStatus { IN_PROGRESS = 2, IN_PROGRESS_HUB = 3, DONE = 4, - ERROR = 5, + ERROR = 5 } diff --git a/src/core/dataclasses/View.ts b/src/core/dataclasses/View.ts index b6e8d89..6c4632d 100644 --- a/src/core/dataclasses/View.ts +++ b/src/core/dataclasses/View.ts @@ -26,5 +26,5 @@ export enum View { UNWRAP = 'UNWRAP', TRANSFER_REQUEST_SUMMARY = 'TRANSFER_REQUEST_SUMMARY', TRANSFER_REQUEST_STEPS = 'TRANSFER_REQUEST_STEPS', - ERROR = 'ERROR', + ERROR = 'ERROR' } diff --git a/src/core/ethers.ts b/src/core/ethers.ts index 8c2fa91..b748877 100644 --- a/src/core/ethers.ts +++ b/src/core/ethers.ts @@ -18,7 +18,7 @@ export function useEthersSigner({ chainId }: { chainId?: number } = {}) { const { data: walletClient } = useWalletClient({ chainId }) return React.useMemo( () => (walletClient ? walletClientToSigner(walletClient) : undefined), - [walletClient], + [walletClient] ) } @@ -27,11 +27,11 @@ export function publicClientToProvider(publicClient: PublicClient) { const network = { chainId: chain.id, name: chain.name, - ensAddress: chain.contracts?.ensRegistry?.address, + ensAddress: chain.contracts?.ensRegistry?.address } if (transport.type === 'fallback') { const providers = (transport.transports as ReturnType[]).map( - ({ value }) => new JsonRpcProvider(value?.url, network), + ({ value }) => new JsonRpcProvider(value?.url, network) ) if (providers.length === 1) return providers[0] return new FallbackProvider(providers) diff --git a/src/core/events.ts b/src/core/events.ts index 3f66ae1..fdf3285 100644 --- a/src/core/events.ts +++ b/src/core/events.ts @@ -39,7 +39,7 @@ export namespace externalEvents { dispatchEvent('metaport_balance', { tokenSymbol: tokenSymbol, schainName: schainName, - balance: _balance, + balance: _balance }) } @@ -48,14 +48,14 @@ export namespace externalEvents { chainName1: string, chainName2: string, tokenSymbol: string, - unwrap: boolean = false, + unwrap: boolean = false ) { dispatchEvent('metaport_transferComplete', { tokenSymbol: tokenSymbol, from: chainName1, to: chainName2, tx: tx, - unwrap: unwrap, + unwrap: unwrap }) } @@ -67,17 +67,17 @@ export namespace externalEvents { txData: any, timestamp: string | number, chainName: string, - txName: string, + txName: string ): void { log('WARNING: Event metaport_transactionCompleted will be removed in the next version') dispatchEvent('metaport_transactionCompleted', { tx: { gasUsed: txData.gasUsed, - transactionHash: txData.transactionHash, + transactionHash: txData.transactionHash }, timestamp, chainName, - txName, + txName }) } @@ -93,14 +93,14 @@ export namespace externalEvents { tokenId: number }, transactionHash?: string, - timestamp?: string | number, + timestamp?: string | number ): void { dispatchEvent('metaport_actionStateUpdated', { actionState, actionName, actionData, transactionHash, - timestamp, + timestamp }) } @@ -108,13 +108,13 @@ export namespace externalEvents { dispatchEvent('metaport_unwrapComplete', { tokenSymbol: tokenSymbol, chain: chainName1, - tx: tx, + tx: tx }) } export function ethUnlocked(tx: string) { dispatchEvent('metaport_ethUnlocked', { - tx: tx, + tx: tx }) } @@ -127,13 +127,13 @@ export namespace internalEvents { export function updateParams(params) { dispatchEvent('_metaport_updateParams', { tokens: params.tokens, - chains: params.chains, + chains: params.chains }) } export function transfer(params: interfaces.TransferParams): void { dispatchEvent('_metaport_transfer', { - params: params, + params: params }) } @@ -141,7 +141,7 @@ export namespace internalEvents { dispatchEvent('_metaport_wrap', { amount: params.amount, chain: params.chain, - tokens: params.tokens, + tokens: params.tokens }) } @@ -149,7 +149,7 @@ export namespace internalEvents { dispatchEvent('_metaport_unwrap', { amount: params.amount, chain: params.chain, - tokens: params.tokens, + tokens: params.tokens }) } @@ -157,7 +157,7 @@ export namespace internalEvents { dispatchEvent('_metaport_swap', { amount: params.amount, chain: params.chain, - tokens: params.tokens, // todo! + tokens: params.tokens // todo! }) } @@ -176,13 +176,13 @@ export namespace internalEvents { export function requestBalance(params) { dispatchEvent('_metaport_requestBalance', { schainName: params.schainName, - tokenSymbol: params.tokenSymbol, + tokenSymbol: params.tokenSymbol }) } export function setTheme(theme) { dispatchEvent('_metaport_setTheme', { - theme: theme, + theme: theme }) } } diff --git a/src/core/explorer.ts b/src/core/explorer.ts index f51fcb6..1c60292 100644 --- a/src/core/explorer.ts +++ b/src/core/explorer.ts @@ -25,7 +25,7 @@ import { HTTPS_PREFIX, MAINNET_CHAIN_NAME, MAINNET_EXPLORER_URLS, - BASE_EXPLORER_URLS, + BASE_EXPLORER_URLS } from './constants' import { SkaleNetwork } from './interfaces' diff --git a/src/core/faucet.ts b/src/core/faucet.ts index 8bcbefd..93ba4f7 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -57,7 +57,7 @@ function getFuncData(chainName: string, address: string, skaleNetwork: string) { export async function getSFuel( chainName: string, address: AddressType, - mpc: MetaportCore, + mpc: MetaportCore ): Promise { const endpoint = mpc.endpoint(chainName) const miner = new SkalePowMiner() @@ -71,6 +71,6 @@ export async function getSFuel( to, data, nonce, - gasPrice: mineFreeGasResult, + gasPrice: mineFreeGasResult }) } diff --git a/src/core/fee_calculator.ts b/src/core/fee_calculator.ts index a9e5e2a..ab9d6c4 100644 --- a/src/core/fee_calculator.ts +++ b/src/core/fee_calculator.ts @@ -40,7 +40,7 @@ export async function getTransactionFee(): Promise { const client = new CoinGeckoClient({ timeout: 10000, - autoRetry: true, + autoRetry: true }) const res = await client.simplePrice({ ids: 'ethereum', vs_currencies: 'usd' }) const ethToUsdRate = res.ethereum.usd diff --git a/src/core/helper.ts b/src/core/helper.ts index 0f89f6a..34fd62a 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -37,7 +37,7 @@ export const CHAINS_META = { mainnet: mainnetMeta, staging: stagingMeta, legacy: legacyMeta, - regression: regressionMeta, + regression: regressionMeta } export function cls(...args: any): string { diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 9be3b9f..19b5bea 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -45,7 +45,7 @@ const CHAIN_ICONS = { mainnet: MAINNET_CHAIN_ICONS, staging: STAGING_CHAIN_ICONS, legacy: LEGACY_CHAIN_ICONS, - regression: REGRESSION_CHAIN_ICONS, + regression: REGRESSION_CHAIN_ICONS } export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: string) { diff --git a/src/core/metaport.ts b/src/core/metaport.ts index 2dce059..013adac 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -28,7 +28,7 @@ import { TokenDataTypesMap, Token, TokenContractsMap, - TokenBalancesMap, + TokenBalancesMap } from './interfaces' import { TokenType, TokenData, CustomAbiTokenType } from './dataclasses' @@ -49,7 +49,7 @@ export const createTokenData = ( tokenKeyname: string, chainName: string, tokenType: TokenType, - config: MetaportConfig, + config: MetaportConfig ): TokenData => { const configToken: Token = config.connections[chainName][tokenType][tokenKeyname] return new TokenData( @@ -58,7 +58,7 @@ export const createTokenData = ( tokenKeyname, config.tokens[tokenKeyname], configToken.chains, - chainName, + chainName ) } @@ -67,7 +67,7 @@ export const addTokenData = ( chainName: string, tokenType: TokenType, config: MetaportConfig, - tokens: TokenDataTypesMap, + tokens: TokenDataTypesMap ) => { tokens[tokenType][tokenKeyname] = createTokenData(tokenKeyname, chainName, tokenType, config) } @@ -75,7 +75,7 @@ export const addTokenData = ( export const createTokensMap = ( chainName1: string, chainName2: string | null | undefined, - config: MetaportConfig, + config: MetaportConfig ): TokenDataTypesMap => { const tokens = getEmptyTokenDataMap() log(`updating tokens map for ${chainName1} -> ${chainName2}`) @@ -96,7 +96,7 @@ export const createTokensMap = ( export function createWrappedTokensMap( chainName1: string, - config: MetaportConfig, + config: MetaportConfig ): TokenDataTypesMap { const wrappedTokens: TokenDataTypesMap = getEmptyTokenDataMap() const tokenType = TokenType.erc20 @@ -165,7 +165,7 @@ export default class MetaportCore { async tokenBalances( tokenContracts: TokenContractsMap, - address: string, + address: string ): Promise { const balances: TokenBalancesMap = {} const tokenKeynames = Object.keys(tokenContracts) @@ -180,8 +180,7 @@ export default class MetaportCore { tokenType: TokenType, chainName: string, provider: Provider, - customAbiTokenType?: CustomAbiTokenType, - // destChainName?: string + customAbiTokenType?: CustomAbiTokenType ): TokenContractsMap { const contracts: TokenContractsMap = {} if (tokens[tokenType]) { @@ -197,7 +196,7 @@ export default class MetaportCore { tokenType, provider, customAbiTokenType, - destChainName, + destChainName ) }) } @@ -210,7 +209,7 @@ export default class MetaportCore { tokenType: TokenType, provider: Provider, customAbiTokenType?: CustomAbiTokenType, - destChainName?: string, + destChainName?: string ): Contract | undefined { const token = this._config.connections[chainName][tokenType][tokenKeyname] if (!token.address) return @@ -224,7 +223,7 @@ export default class MetaportCore { chainName1: string, chainName2: string, tokenKeyname: string, - tokenType: TokenType, + tokenType: TokenType ) { let token = this._config.connections[chainName1][tokenType][tokenKeyname] const isClone = token.chains[chainName2].clone @@ -304,9 +303,9 @@ export default class MetaportCore { CustomAbiTokenType.erc20wrap ) - const prevTokenKeyname = prevToken?.keyname; - const prevTokenType = prevToken?.type; - const token = prevTokenKeyname ? tokens[prevTokenType][prevTokenKeyname] : null; + const prevTokenKeyname = prevToken?.keyname + const prevTokenType = prevToken?.type + const token = prevTokenKeyname ? tokens[prevTokenType][prevTokenKeyname] : null return { ima1, diff --git a/src/core/miner.ts b/src/core/miner.ts index d889df9..abb4fcd 100644 --- a/src/core/miner.ts +++ b/src/core/miner.ts @@ -38,7 +38,7 @@ export default class SkalePowMiner { public async mineGasForTransaction( nonce: string | number, gas: string | number, - from: string, + from: string ): Promise { let address = from nonce = isHexString(nonce) ? getNumber(nonce) : (nonce as number) diff --git a/src/core/network.ts b/src/core/network.ts index 40aaba5..b991c03 100644 --- a/src/core/network.ts +++ b/src/core/network.ts @@ -33,14 +33,14 @@ export { proxyEndpoints as PROXY_ENDPOINTS } const PROTOCOL: { [protocol in 'http' | 'ws']: string } = { http: 'https://', - ws: 'wss://', + ws: 'wss://' } export const CHAIN_IDS: { [network in SkaleNetwork]: number } = { staging: 5, legacy: 5, regression: 5, - mainnet: 5, + mainnet: 5 } export function isMainnetChainId(chainId: number | BigInt, skaleNetwork: SkaleNetwork): boolean { @@ -50,7 +50,7 @@ export function isMainnetChainId(chainId: number | BigInt, skaleNetwork: SkaleNe export function getChainEndpoint( mainnetEndpoint: string, network: SkaleNetwork, - chainName: string, + chainName: string ): string { if (chainName === MAINNET_CHAIN_NAME) return mainnetEndpoint return getSChainEndpoint(network, chainName) @@ -59,7 +59,7 @@ export function getChainEndpoint( export function getSChainEndpoint( network: SkaleNetwork, sChainName: string, - protocol: 'http' | 'ws' = 'http', + protocol: 'http' | 'ws' = 'http' ): string { return ( PROTOCOL[protocol] + @@ -90,7 +90,7 @@ export function getMainnetAbi(network: string) { export function initIMA( mainnetEndpoint: string, network: SkaleNetwork, - chainName: string, + chainName: string ): MainnetChain | SChain { if (chainName === MAINNET_CHAIN_NAME) return initMainnet(mainnetEndpoint, network) return initSChain(network, chainName) diff --git a/src/core/sfuel.ts b/src/core/sfuel.ts index 965fb23..bec48f0 100644 --- a/src/core/sfuel.ts +++ b/src/core/sfuel.ts @@ -48,7 +48,7 @@ export class Station { constructor( public chainName: string, - public mpc: MetaportCore, + public mpc: MetaportCore ) { this.chainName = chainName this.mpc = mpc diff --git a/src/core/themes.ts b/src/core/themes.ts index 3f2603a..b5c7b74 100644 --- a/src/core/themes.ts +++ b/src/core/themes.ts @@ -31,15 +31,15 @@ const defaultThemes = { background: '#000000', mode: 'dark', position: Positions.bottomRight, - zIndex: DEFAULT_MP_Z_INDEX, + zIndex: DEFAULT_MP_Z_INDEX }, light: { primary: '#173CFF', background: '#EFEFEF', mode: 'light', position: Positions.bottomRight, - zIndex: DEFAULT_MP_Z_INDEX, - }, + zIndex: DEFAULT_MP_Z_INDEX + } } // warning: order is important here @@ -51,7 +51,7 @@ const MUI_ELEMENTS = [ 'drawer', 'modal', 'snackbar', - 'tooltip', + 'tooltip' ] const INDEX_STEP = 50 diff --git a/src/core/transfer_steps.ts b/src/core/transfer_steps.ts index a0a9e7d..8e1c5d3 100644 --- a/src/core/transfer_steps.ts +++ b/src/core/transfer_steps.ts @@ -30,7 +30,7 @@ import { TransferStepMetadata, UnlockStepMetadata, StepMetadata, - getActionType, + getActionType } from './dataclasses' import { MetaportConfig } from './interfaces/index' @@ -43,7 +43,7 @@ const log = debug('metaport:core:transferSteps') export function getStepsMetadata( config: MetaportConfig, token: TokenData, - to: string, + to: string ): StepMetadata[] { const steps: StepMetadata[] = [] if (token === undefined || token === null || to === null || to === '') return steps @@ -59,7 +59,7 @@ export function getStepsMetadata( steps.push(new WrapStepMetadata(token.chain, to)) } steps.push( - new TransferStepMetadata(getActionType(token.chain, toChain, token.type), token.chain, toChain), + new TransferStepMetadata(getActionType(token.chain, toChain, token.type), token.chain, toChain) ) if (hubTokenOptions.wrapper && !isCloneToClone) { steps.push(new UnwrapStepMetadata(token.chain, toChain)) @@ -72,7 +72,7 @@ export function getStepsMetadata( steps.push(new TransferStepMetadata(getActionType(toChain, to, token.type), toChain, to)) } if (to === MAINNET_CHAIN_NAME && token.keyname === 'eth') { - steps.push(new UnlockStepMetadata(token.chain, toChain)) + steps.push(new UnlockStepMetadata(token.chain, to)) } log(`Action steps metadata:`) diff --git a/src/core/wagmi_network.ts b/src/core/wagmi_network.ts index 2491124..2c89ef8 100644 --- a/src/core/wagmi_network.ts +++ b/src/core/wagmi_network.ts @@ -43,17 +43,17 @@ export function constructWagmiChain(network: SkaleNetwork, chainName: string): C nativeCurrency: { decimals: 18, name: 'sFUEL', - symbol: 'sFUEL', + symbol: 'sFUEL' }, rpcUrls: { public: { http: [endpointHttp], webSocket: [endpointWs] }, - default: { http: [endpointHttp], webSocket: [endpointWs] }, + default: { http: [endpointHttp], webSocket: [endpointWs] } }, blockExplorers: { etherscan: { name: 'SKALE Explorer', url: explorerUrl }, - default: { name: 'SKALE Explorer', url: explorerUrl }, + default: { name: 'SKALE Explorer', url: explorerUrl } }, - contracts: {}, + contracts: {} } as const satisfies Chain } diff --git a/src/index.ts b/src/index.ts index 6351997..bcd01c8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export { interfaces, dataclasses } from './Metaport' -export { useMetaportStore, type MetaportState } from './store/MetaportState' +export { useMetaportStore } from './store/MetaportStore' +export { type MetaportState } from './store/MetaportState' export { useUIStore, useCollapseStore, type UIState, type CollapseState } from './store/Store' export { useSFuelStore, type SFuelState } from './store/SFuelStore' @@ -71,5 +72,5 @@ export { BASE_EXPLORER_URLS, CHAINS_META, chainBg, - getChainAlias, + getChainAlias } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 62ff005..1499a45 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -12,63 +12,63 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-faint-slimy-achird', // Nebula 'staging-perfect-parallel-gacrux', // Test Chain 1 'staging-severe-violet-wezen', // Test Chain 2 - 'staging-weepy-fitting-caph', // Tank War Zone + 'staging-weepy-fitting-caph' // Tank War Zone ], tokens: { eth: { - symbol: 'ETH', + symbol: 'ETH' }, skl: { decimals: '18', name: 'SKALE', - symbol: 'SKL', + symbol: 'SKL' }, usdc: { decimals: '6', symbol: 'USDC', - name: 'USD Coin', + name: 'USD Coin' }, usdt: { decimals: '6', symbol: 'USDT', - name: 'Tether USD', + name: 'Tether USD' }, wbtc: { decimals: '18', symbol: 'WBTC', - name: 'WBTC', + name: 'WBTC' }, _SPACE_1: { name: 'SKALE Space', symbol: 'SPACE', iconUrl: - 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png', + 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png' }, _SKALIENS_1: { name: 'SKALIENS Collection', symbol: 'SKALIENS', iconUrl: - 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png', + 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png' }, ruby: { name: 'Ruby Token', iconUrl: 'https://ruby.exchange/images/tokens/ruby-square.png', - symbol: 'RUBY', + symbol: 'RUBY' }, dai: { name: 'DAI Stablecoin', - symbol: 'DAI', + symbol: 'DAI' }, usdp: { name: 'Pax Dollar', symbol: 'USDP', - iconUrl: 'https://ruby.exchange/images/tokens/usdp-square.png', + iconUrl: 'https://ruby.exchange/images/tokens/usdp-square.png' }, hmt: { name: 'Human Token', symbol: 'HMT', - iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png', - }, + iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png' + } }, connections: { mainnet: { @@ -78,13 +78,12 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-legal-crazy-castor': {}, 'staging-utter-unripe-menkar': { hub: 'staging-legal-crazy-castor' - } - // "staging-faint-slimy-achird": {}, - // "staging-perfect-parallel-gacrux": {}, - // "staging-severe-violet-wezen": {}, - // "staging-weepy-fitting-caph": {} - }, - }, + }, + // 'staging-faint-slimy-achird': { + // hub: 'staging-legal-crazy-castor' + // } + } + } }, erc20: { skl: { @@ -92,69 +91,69 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { chains: { 'staging-legal-crazy-castor': {}, 'staging-utter-unripe-menkar': { - hub: 'staging-legal-crazy-castor', + hub: 'staging-legal-crazy-castor' }, 'staging-faint-slimy-achird': { - hub: 'staging-legal-crazy-castor', - }, - }, + hub: 'staging-legal-crazy-castor' + } + } }, ruby: { address: '0xd66641E25E9D36A995682572eaD74E24C11Bb422', chains: { - 'staging-legal-crazy-castor': {}, - }, + 'staging-legal-crazy-castor': {} + } }, dai: { address: '0x83B38f79cFFB47CF74f7eC8a5F8D7DD69349fBf7', chains: { - 'staging-legal-crazy-castor': {}, - }, + 'staging-legal-crazy-castor': {} + } }, usdp: { address: '0x66259E472f8d09083ecB51D42F9F872A61001426', chains: { - 'staging-legal-crazy-castor': {}, - }, + 'staging-legal-crazy-castor': {} + } }, usdt: { address: '0xD1E44e3afd6d3F155e7704c67705E3bAC2e491b6', chains: { - 'staging-legal-crazy-castor': {}, - }, + 'staging-legal-crazy-castor': {} + } }, usdc: { address: '0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9', chains: { 'staging-legal-crazy-castor': {}, 'staging-utter-unripe-menkar': { - hub: 'staging-legal-crazy-castor', - }, - }, + hub: 'staging-legal-crazy-castor' + } + } }, wbtc: { address: '0xd80BC0126A38c9F7b915e1B2B9f78280639cadb3', chains: { - 'staging-legal-crazy-castor': {}, - }, + 'staging-legal-crazy-castor': {} + } }, hmt: { address: '0x4058d058ff62ED347dB8a69c43Ae9C67268B50b0', - chains: {}, - }, + chains: {} + } }, erc721meta: { _SPACE_1: { address: '0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6', - chains: {}, - }, + chains: {} + } }, erc1155: { _SKALIENS_1: { address: '0x6cb73D413970ae9379560aA45c769b417Fbf33D6', - chains: {}, - }, - }, + chains: {} + } + } }, 'staging-utter-unripe-menkar': { // Calypso connections @@ -165,7 +164,7 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-legal-crazy-castor': { clone: true }, - 'mainnet': { + mainnet: { clone: true, hub: 'staging-legal-crazy-castor' } @@ -177,51 +176,66 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { address: '0x7E1B8750C21AebC3bb2a0bDf40be104C609a9852', chains: { 'staging-legal-crazy-castor': { - clone: true, + clone: true }, 'staging-faint-slimy-achird': { hub: 'staging-legal-crazy-castor', - clone: true, + clone: true }, mainnet: { hub: 'staging-legal-crazy-castor', - clone: true, - }, - }, + clone: true + } + } }, usdc: { address: '0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6', chains: { 'staging-legal-crazy-castor': { - clone: true, + clone: true }, mainnet: { hub: 'staging-legal-crazy-castor', - clone: true, - }, - }, - }, - }, + clone: true + } + } + } + } }, 'staging-faint-slimy-achird': { + // Nebula connections + // eth: { + // eth: { + // address: '0x', + // chains: { + // 'staging-legal-crazy-castor': { + // clone: true + // }, + // mainnet: { + // hub: 'staging-legal-crazy-castor', + // clone: true + // }, + // } + // } + // }, erc20: { skl: { address: '0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d', chains: { 'staging-legal-crazy-castor': { - clone: true, + clone: true }, mainnet: { hub: 'staging-legal-crazy-castor', - clone: true, + clone: true }, 'staging-utter-unripe-menkar': { hub: 'staging-legal-crazy-castor', - clone: true, - }, - }, - }, - }, + clone: true + } + } + } + } }, 'staging-legal-crazy-castor': { // Europa connections @@ -230,84 +244,87 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { address: '0xD2Aaa00700000000000000000000000000000000', chains: { mainnet: { - clone: true, + clone: true }, 'staging-utter-unripe-menkar': { wrapper: '0xa270484784f043e159f74C03B691F80B6F6e3c24' - } - }, - }, + }, + // 'staging-faint-slimy-achird': { + // wrapper: '0xa270484784f043e159f74C03B691F80B6F6e3c24' + // } + } + } }, erc20: { skl: { address: '0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6', chains: { mainnet: { - clone: true, + clone: true }, 'staging-utter-unripe-menkar': { - wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6', + wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6' }, 'staging-faint-slimy-achird': { - wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6', - }, - }, + wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6' + } + } }, ruby: { address: '0xf06De9214B1Db39fFE9db2AebFA74E52f1e46e39', chains: { mainnet: { - clone: true, - }, - }, + clone: true + } + } }, dai: { address: '0x3595E2f313780cb2f23e197B8e297066fd410d30', chains: { mainnet: { - clone: true, - }, - }, + clone: true + } + } }, usdp: { address: '0xe0E2cb3A5d6f94a5bc2D00FAa3e64460A9D241E1', chains: { mainnet: { - clone: true, - }, - }, + clone: true + } + } }, usdt: { address: '0xa388F9783d8E5B0502548061c3b06bf4300Fc0E1', chains: { mainnet: { - clone: true, - }, - }, + clone: true + } + } }, usdc: { address: '0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33', chains: { mainnet: { - clone: true, + clone: true }, 'staging-utter-unripe-menkar': { - wrapper: '0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0', - }, - }, + wrapper: '0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0' + } + } }, wbtc: { address: '0xf5E880E1066DDc90471B9BAE6f183D5344fd289F', chains: { mainnet: { - clone: true, - }, - }, - }, - }, + clone: true + } + } + } + } }, 'staging-severe-violet-wezen': { - erc20: {}, + erc20: {} }, 'staging-perfect-parallel-gacrux': { erc20: {}, @@ -325,11 +342,11 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { // "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", // "chains": {} // } - }, - }, + } + } }, theme: { mode: 'dark', - vibrant: true, - }, + vibrant: true + } } diff --git a/src/store/CommunityPoolStore.ts b/src/store/CommunityPoolStore.ts index 18d219d..4f185ce 100644 --- a/src/store/CommunityPoolStore.ts +++ b/src/store/CommunityPoolStore.ts @@ -60,7 +60,7 @@ export const useCPStore = create()((set, get) => ({ address: string, chainName1: string, chainName2: string, - mpc: MetaportCore, + mpc: MetaportCore ) => { if (!chainName1 || !chainName2) return if (!get().mainnet) { @@ -74,12 +74,12 @@ export const useCPStore = create()((set, get) => ({ chainName1, chainName2, get().mainnet, - get().sChain, + get().sChain ) set({ chainName: chainName1, cpData: cpData, - amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : '', + amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : '' }) - }, + } })) diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 28e0f52..469644f 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -17,27 +17,18 @@ */ /** - * @file MetaportState.ts + * @file IMetaportState.ts * @copyright SKALE Labs 2023-Present */ -import debug from 'debug' - import { Contract } from 'ethers' - +import { WalletClient } from 'viem' import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import { create } from 'zustand' import MetaportCore from '../core/metaport' import * as interfaces from '../core/interfaces' import * as dataclasses from '../core/dataclasses' -import { getEmptyTokenDataMap } from '../core/tokens/helper' -import { MAINNET_CHAIN_NAME, DEFAULT_ERROR_MSG } from '../core/constants' -import { ACTIONS } from '../core/actions' -import { WalletClient } from 'viem' -debug.enable('*') -const log = debug('metaport:state') export interface MetaportState { ima1: MainnetChain | SChain @@ -58,14 +49,14 @@ export interface MetaportState { execute: ( address: string, switchNetwork: (chainId: number) => void, - walletClient: WalletClient, + walletClient: WalletClient ) => void unwrapAll: ( address: string, switchNetwork: (chainId: number) => void, walletClient: WalletClient, - tokens: interfaces.TokenDataMap, + tokens: interfaces.TokenDataMap ) => void check: (amount: string, address: `0x${string}`) => void @@ -114,9 +105,6 @@ export interface MetaportState { errorMessage: dataclasses.ErrorMessage setErrorMessage: (errorMessage: dataclasses.ErrorMessage) => void - actionBtnDisabled: boolean - setActionBtnDisabled: (actionBtnDisabled: boolean) => void - loading: boolean setLoading: (loading: boolean) => void @@ -129,265 +117,3 @@ export interface MetaportState { errorMessageClosedFallback: () => void startOver: () => void } - -export const useMetaportStore = create()((set, get) => ({ - ima1: null, - ima2: null, - setIma1: (ima: MainnetChain | SChain) => set(() => ({ ima1: ima })), - setIma2: (ima: MainnetChain | SChain) => set(() => ({ ima2: ima })), - - mpc: null, - setMpc: (mpc: MetaportCore) => set(() => ({ mpc: mpc })), - - tokenId: null, - setTokenId: (tokenId: number) => - set(() => { - return { - tokenId: tokenId, - } - }), - - amount: '', - setAmount: (amount: string, address: `0x${string}`) => - set((state) => { - state.check(amount, address) - return { - amount: amount, - } - }), - - unwrapAll: async ( - address: `0x${string}`, - switchNetwork: any, - walletClient: WalletClient, - tokens: interfaces.TokenDataMap, - ) => { - log('Running unwrapAll') - set({ loading: true }) - try { - for (const key of Object.keys(tokens)) { - await new ACTIONS.unwrap_stuck( - get().mpc, - get().chainName1, - null, - address, - get().amount, - get().tokenId, - tokens[key], - get().setAmountErrorMessage, - get().setBtnText, - switchNetwork, - walletClient, - ).execute() - } - } catch (err) { - console.error(err) - const msg = err.message ? err.message : DEFAULT_ERROR_MSG - set({ - errorMessage: new dataclasses.TransactionErrorMessage( - msg, - get().errorMessageClosedFallback, - ), - }) - return - } finally { - set({ loading: false }) - } - }, - - execute: async (address: `0x${string}`, switchNetwork: any, walletClient: WalletClient) => { - log('Running execute') - if (get().stepsMetadata[get().currentStep]) { - set({ - loading: true, - transferInProgress: true, - }) - try { - const stepMetadata = get().stepsMetadata[get().currentStep] - const actionClass = ACTIONS[stepMetadata.type] - await new actionClass( - get().mpc, - stepMetadata.from, - stepMetadata.to, - address, - get().amount, - get().tokenId, - get().token, - get().setAmountErrorMessage, - get().setBtnText, - switchNetwork, - walletClient, - ).execute() - } catch (err) { - console.error(err) - const msg = err.message ? err.message : DEFAULT_ERROR_MSG - set({ - errorMessage: new dataclasses.TransactionErrorMessage( - msg, - get().errorMessageClosedFallback, - ), - }) - return - } finally { - set({ loading: false }) - } - set({ - transferInProgress: get().currentStep + 1 !== get().stepsMetadata.length, - currentStep: get().currentStep + 1, - }) - } - }, - - errorMessageClosedFallback() { - set({ - loading: false, - errorMessage: undefined, - transferInProgress: get().currentStep !== 0, - }) - }, - - startOver() { - set({ - loading: false, - errorMessage: undefined, - amount: '', - tokenId: null, - currentStep: 0, - transferInProgress: false, - destTokenBalance: null, - }) - }, - - check: async (amount: string, address: string) => { - if (get().stepsMetadata[get().currentStep] && address) { - set({ - loading: true, - btnText: 'Checking balance...', - }) - const stepMetadata = get().stepsMetadata[get().currentStep] - const actionClass = ACTIONS[stepMetadata.type] - await new actionClass( - get().mpc, - stepMetadata.from, - stepMetadata.to, - address, - amount, - get().tokenId, - get().token, - get().setAmountErrorMessage, - get().setBtnText, - null, - null, - ).preAction() - } - set({ loading: false }) - }, - - currentStep: 0, - setCurrentStep: (currentStep: number) => set(() => ({ currentStep: currentStep })), - - stepsMetadata: [], - setStepsMetadata: (steps: dataclasses.StepMetadata[]) => set(() => ({ stepsMetadata: steps })), - - chainName1: '', - chainName2: '', - - appName1: null, - appName2: null, - - setAppName1: (name: string) => set(() => ({ appName1: name })), - setAppName2: (name: string) => set(() => ({ appName2: name })), - - destChains: [], - - setChainName1: (name: string) => { - set(get().mpc.chainChanged(name, get().chainName2, get().token)) - }, - setChainName2: (name: string) => { - set(get().mpc.chainChanged(get().chainName1, name, get().token)) - }, - - tokens: getEmptyTokenDataMap(), - - token: null, - - setToken: async (token: dataclasses.TokenData) => { - set(get().mpc.tokenChanged( - get().chainName1, - get().ima2, - token, - get().chainName2 - )) - }, - - wrappedTokens: getEmptyTokenDataMap(), - tokenContracts: {}, - tokenBalances: {}, - - wrappedTokenContracts: {}, - wrappedTokenBalances: {}, - - destTokenContract: null, - destTokenBalance: null, - - updateDestTokenBalance: async (address: string) => { - if (!address) { - set({ destTokenBalance: null }) - return - } - if (get().destTokenContract) { - const balance = await get().mpc.tokenBalance(get().destTokenContract, address) - set({ destTokenBalance: balance }) - } else { - if ( - get().token && - get().token.type === dataclasses.TokenType.eth && - get().chainName2 === MAINNET_CHAIN_NAME - ) { - set({ destTokenBalance: await get().ima2.ethBalance(address) }) - } - } - }, - - updateTokenBalances: async (address: string) => { - if (!address) { - set({ tokenBalances: {} }) - return - } - const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address) - if (get().chainName1 === MAINNET_CHAIN_NAME || get().chainName2 === MAINNET_CHAIN_NAME) { - tokenBalances.eth = await get().ima1.ethBalance(address) - } - set({ - tokenBalances: tokenBalances, - }) - }, - - updateWrappedTokenBalances: async (address: string) => { - if (!address) { - set({ wrappedTokenBalances: {} }) - return - } - set({ - wrappedTokenBalances: await get().mpc.tokenBalances(get().wrappedTokenContracts, address), - }) - }, - - amountErrorMessage: null, - setAmountErrorMessage: (em: string) => set(() => ({ amountErrorMessage: em })), - - errorMessage: null, - setErrorMessage: (em: dataclasses.ErrorMessage) => set(() => ({ errorMessage: em })), - - actionBtnDisabled: false, - setActionBtnDisabled: (disabled: boolean) => set(() => ({ actionBtnDisabled: disabled })), - - loading: false, - setLoading: (loading: boolean) => set(() => ({ loading: loading })), - - transferInProgress: false, - setTransferInProgress: (inProgress: boolean) => set(() => ({ transferInProgress: inProgress })), - - btnText: null, - setBtnText: (btnText: string) => set(() => ({ btnText: btnText })), -})) diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts new file mode 100644 index 0000000..817da97 --- /dev/null +++ b/src/store/MetaportStore.ts @@ -0,0 +1,283 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file MetaportState.ts + * @copyright SKALE Labs 2023-Present + */ + +import debug from 'debug' +import { WalletClient } from 'viem' +import { create } from 'zustand' +import { MainnetChain, SChain } from '@skalenetwork/ima-js' + +import { MetaportState } from './MetaportState' + +import MetaportCore from '../core/metaport' +import * as interfaces from '../core/interfaces' +import * as dataclasses from '../core/dataclasses' +import { getEmptyTokenDataMap } from '../core/tokens/helper' +import { MAINNET_CHAIN_NAME, DEFAULT_ERROR_MSG } from '../core/constants' +import { ACTIONS } from '../core/actions' + + +debug.enable('*') +const log = debug('metaport:state') + + +export const useMetaportStore = create()((set, get) => ({ + ima1: null, + ima2: null, + setIma1: (ima: MainnetChain | SChain) => set(() => ({ ima1: ima })), + setIma2: (ima: MainnetChain | SChain) => set(() => ({ ima2: ima })), + + mpc: null, + setMpc: (mpc: MetaportCore) => set(() => ({ mpc: mpc })), + + tokenId: null, + setTokenId: (tokenId: number) => set(() => { return { tokenId: tokenId } }), + + amount: '', + setAmount: (amount: string, address: `0x${string}`) => + set((state) => { + state.check(amount, address) + return { + amount: amount + } + }), + + unwrapAll: async ( + address: `0x${string}`, + switchNetwork: any, + walletClient: WalletClient, + tokens: interfaces.TokenDataMap + ) => { + log('Running unwrapAll') + set({ loading: true }) + try { + for (const key of Object.keys(tokens)) { + await new ACTIONS.unwrap_stuck( + get().mpc, + get().chainName1, + null, + address, + get().amount, + get().tokenId, + tokens[key], + get().setAmountErrorMessage, + get().setBtnText, + switchNetwork, + walletClient + ).execute() + } + } catch (err) { + console.error(err) + const msg = err.message ? err.message : DEFAULT_ERROR_MSG + set({ + errorMessage: new dataclasses.TransactionErrorMessage(msg, get().errorMessageClosedFallback) + }) + return + } finally { + set({ loading: false }) + } + }, + + execute: async (address: `0x${string}`, switchNetwork: any, walletClient: WalletClient) => { + log('Running execute') + if (get().stepsMetadata[get().currentStep]) { + set({ + loading: true, + transferInProgress: true + }) + try { + const stepMetadata = get().stepsMetadata[get().currentStep] + const actionClass = ACTIONS[stepMetadata.type] + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + get().amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + switchNetwork, + walletClient + ).execute() + } catch (err) { + console.error(err) + const msg = err.message ? err.message : DEFAULT_ERROR_MSG + set({ + errorMessage: new dataclasses.TransactionErrorMessage( + msg, + get().errorMessageClosedFallback + ) + }) + return + } finally { + set({ loading: false }) + } + set({ + transferInProgress: get().currentStep + 1 !== get().stepsMetadata.length, + currentStep: get().currentStep + 1 + }) + } + }, + + errorMessageClosedFallback() { + set({ loading: false, errorMessage: undefined, transferInProgress: get().currentStep !== 0 }) + }, + + startOver() { + set({ + loading: false, + errorMessage: undefined, + amount: '', + tokenId: null, + currentStep: 0, + transferInProgress: false, + destTokenBalance: null + }) + }, + + check: async (amount: string, address: string) => { + if (get().stepsMetadata[get().currentStep] && address) { + set({ + loading: true, + btnText: 'Checking balance...' + }) + const stepMetadata = get().stepsMetadata[get().currentStep] + const actionClass = ACTIONS[stepMetadata.type] + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + null, + null + ).preAction() + } + set({ loading: false }) + }, + + currentStep: 0, + setCurrentStep: (currentStep: number) => set(() => ({ currentStep: currentStep })), + + stepsMetadata: [], + setStepsMetadata: (steps: dataclasses.StepMetadata[]) => set(() => ({ stepsMetadata: steps })), + + chainName1: '', + chainName2: '', + + appName1: null, + appName2: null, + + setAppName1: (name: string) => set(() => ({ appName1: name })), + setAppName2: (name: string) => set(() => ({ appName2: name })), + + destChains: [], + + setChainName1: (name: string) => { + set(get().mpc.chainChanged(name, get().chainName2, get().token)) + }, + setChainName2: (name: string) => { + set(get().mpc.chainChanged(get().chainName1, name, get().token)) + }, + + tokens: getEmptyTokenDataMap(), + + token: null, + + setToken: async (token: dataclasses.TokenData) => { + set(get().mpc.tokenChanged(get().chainName1, get().ima2, token, get().chainName2)) + }, + + wrappedTokens: getEmptyTokenDataMap(), + tokenContracts: {}, + tokenBalances: {}, + + wrappedTokenContracts: {}, + wrappedTokenBalances: {}, + + destTokenContract: null, + destTokenBalance: null, + + updateDestTokenBalance: async (address: string) => { + if (!address) { + set({ destTokenBalance: null }) + return + } + if (get().destTokenContract) { + const balance = await get().mpc.tokenBalance(get().destTokenContract, address) + set({ destTokenBalance: balance }) + } else { + if ( + get().token && + get().token.type === dataclasses.TokenType.eth && + get().chainName2 === MAINNET_CHAIN_NAME + ) { + set({ destTokenBalance: await get().ima2.ethBalance(address) }) + } + } + }, + + updateTokenBalances: async (address: string) => { + if (!address) { + set({ tokenBalances: {} }) + return + } + const tokenBalances = await get().mpc.tokenBalances(get().tokenContracts, address) + if (get().chainName1 === MAINNET_CHAIN_NAME) { + tokenBalances.eth = await get().ima1.ethBalance(address) + } + set({ + tokenBalances: tokenBalances + }) + }, + + updateWrappedTokenBalances: async (address: string) => { + if (!address) { + set({ wrappedTokenBalances: {} }) + return + } + set({ + wrappedTokenBalances: await get().mpc.tokenBalances(get().wrappedTokenContracts, address) + }) + }, + + amountErrorMessage: null, + setAmountErrorMessage: (em: string) => set(() => ({ amountErrorMessage: em })), + + errorMessage: null, + setErrorMessage: (em: dataclasses.ErrorMessage) => set(() => ({ errorMessage: em })), + + loading: false, + setLoading: (loading: boolean) => set(() => ({ loading: loading })), + + transferInProgress: false, + setTransferInProgress: (inProgress: boolean) => set(() => ({ transferInProgress: inProgress })), + + btnText: null, + setBtnText: (btnText: string) => set(() => ({ btnText: btnText })) +})) diff --git a/src/store/SFuelStore.ts b/src/store/SFuelStore.ts index fd36951..3bbc3f0 100644 --- a/src/store/SFuelStore.ts +++ b/src/store/SFuelStore.ts @@ -65,5 +65,5 @@ export const useSFuelStore = create()((set, get) => ({ setSFuelStatus: (status: 'action' | 'warning' | 'error') => set({ sFuelStatus: status }), sFuelOk: false, - setSFuelOk: (sFuelOk: boolean) => set(() => ({ sFuelOk: sFuelOk })), + setSFuelOk: (sFuelOk: boolean) => set(() => ({ sFuelOk: sFuelOk })) })) diff --git a/src/store/Store.ts b/src/store/Store.ts index eb8dabf..fb0b9b3 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -35,7 +35,7 @@ export const useUIStore = create()((set) => ({ theme: null, setTheme: (theme: interfaces.MetaportTheme) => set(() => ({ theme: theme })), open: false, - setOpen: (isOpen: boolean) => set(() => ({ open: isOpen })), + setOpen: (isOpen: boolean) => set(() => ({ open: isOpen })) })) export interface CollapseState { @@ -62,7 +62,7 @@ export const useCollapseStore = create()((set) => ({ expandedTo: false, expandedTokens: false, expandedCP: false, - expandedWT: false, + expandedWT: false })), expandedTo: false, setExpandedTo: (expanded: string | false) => @@ -71,7 +71,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTokens: false, expandedCP: false, - expandedWT: false, + expandedWT: false })), expandedTokens: false, setExpandedTokens: (expanded: string | false) => @@ -80,7 +80,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedCP: false, - expandedWT: false, + expandedWT: false })), expandedCP: false, setExpandedCP: (expanded: string | false) => @@ -89,7 +89,7 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedTokens: false, - expandedWT: false, + expandedWT: false })), expandedWT: false, setExpandedWT: (expanded: string | false) => @@ -98,6 +98,6 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedTokens: false, - expandedWT: expanded, - })), + expandedWT: expanded + })) })) From 4dcf13b80ff61497e53e0eb8752b4fb44bc06f6a Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 11 Sep 2023 13:27:25 +0100 Subject: [PATCH 047/110] Add unwrap for eth token --- .../WrappedTokens/WrappedTokens.tsx | 2 +- src/core/metadata.ts | 7 ------ src/core/metaport.ts | 25 ++++++++++++++++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/components/WrappedTokens/WrappedTokens.tsx b/src/components/WrappedTokens/WrappedTokens.tsx index 6825005..f7121b1 100644 --- a/src/components/WrappedTokens/WrappedTokens.tsx +++ b/src/components/WrappedTokens/WrappedTokens.tsx @@ -87,7 +87,7 @@ export default function WrappedTokens() { setFilteredTokens( Object.keys(wrappedTokenBalances).reduce((acc, key) => { if (wrappedTokenBalances[key] !== 0n) { - acc[key] = wrappedTokens.erc20[key] + acc[key] = wrappedTokens.erc20[key] ?? wrappedTokens.eth[key] } return acc }, {}) diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 19b5bea..4009297 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -33,13 +33,6 @@ import * as REGRESSION_CHAIN_ICONS from '../meta/regression/icons' import * as icons from '../icons' -// const icons = { -// eth: { default: '' }, -// skl: { -// default: -// 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIj48Y2lyY2xlIGZpbGw9IiMwMDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPjxnIGZpbGw9IiNGRkYiPjxwYXRoIGQ9Ik0yMi41MTQgOC40OTJ2Ljk5MUg5LjgxdjEzLjAzNGgxMi43MDRWMjQuNWwtNy40Mi0uMDU3LTcuNDUtLjA4NS0uMDg2LTguNDQzTDcuNSA3LjVoMTUuMDE0eiIvPjxwYXRoIGQ9Ik0yMy42OTggMTAuOWMxLjEyNi4zMTIgMi4xMDggMS4xOSAyLjQyNSAyLjE4Mi4xNzMuNTk1LjA4Ny42NTEtLjkyNC42NTEtLjc4IDAtMS4yMTItLjE3LTEuNDcyLS41NjYtLjQzMy0uNzA5LTIuMzk3LS43OTQtMi45NzQtLjExNC0uNjM1Ljc2NS4wNTggMS4zMzIgMi4zMSAxLjg0MiAxLjEyNi4yNTUgMi4zMS42OCAyLjYyNy45NjMgMS40NDQgMS4yNzUuODY2IDQuMDgtMS4wMSA0Ljg0NS0xLjI3LjUxLTMuMzUuNTEtNC42MiAwLS44NjYtLjM2OC0xLjg3Ny0xLjY0My0xLjg3Ny0yLjQzNiAwLS41MSAxLjg3Ny0uMzEyIDIuMzY4LjI4MyAxLjA0IDEuMTYyIDMuNDY0Ljk5MiAzLjYzOC0uMjU1LjE0NC0uOTYzLS40MDUtMS4zODgtMi4wNS0xLjU4Ny0yLjY4NS0uMzY4LTMuNjY3LTEuMTktMy42NjctMy4wNiAwLTIuMjEgMi40MjUtMy41MTMgNS4yMjYtMi43NDh6Ii8+PC9nPjwvZz48L3N2Zz4=', -// }, -// } // TODO: storybook fix const CHAIN_ICONS = { mainnet: MAINNET_CHAIN_ICONS, diff --git a/src/core/metaport.ts b/src/core/metaport.ts index 013adac..874e481 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -108,6 +108,13 @@ export function createWrappedTokensMap( addTokenData(tokenKeyname, chainName1, tokenType as TokenType, config, wrappedTokens) } }) + const ethToken = config.connections[chainName1].eth?.eth + if (ethToken) { + const wrapperAddress = findFirstWrapperAddress(ethToken) + if (wrapperAddress) { + addTokenData('eth', chainName1, TokenType.eth, config, wrappedTokens) + } + } return wrappedTokens } @@ -291,10 +298,6 @@ export default class MetaportCore { const tokens = this.tokens(chainName1, chainName2) const tokenContracts = this.tokenContracts(tokens, TokenType.erc20, chainName1, ima1.provider) - if (tokens.eth.eth && chainName1 !== MAINNET_CHAIN_NAME) { - tokenContracts.eth = this.tokenContract(chainName1, 'eth', TokenType.eth, ima1.provider) - } - const wrappedTokenContracts = this.tokenContracts( tokens, TokenType.erc20, @@ -303,6 +306,20 @@ export default class MetaportCore { CustomAbiTokenType.erc20wrap ) + if (tokens.eth?.eth && chainName1 !== MAINNET_CHAIN_NAME) { + tokenContracts.eth = this.tokenContract(chainName1, 'eth', TokenType.eth, ima1.provider) + + const destChainName = findFirstWrapperChainName(tokens.eth.eth) + wrappedTokenContracts.eth = this.tokenContract( + chainName1, + 'eth', + TokenType.eth, + ima1.provider, + CustomAbiTokenType.erc20wrap, + destChainName + ) + } + const prevTokenKeyname = prevToken?.keyname const prevTokenType = prevToken?.type const token = prevTokenKeyname ? tokens[prevTokenType][prevTokenKeyname] : null From c9c04fe64485845a0c61468459c3b15a2aeadabb Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 11 Sep 2023 17:07:24 +0100 Subject: [PATCH 048/110] Restructure CSS imports --- src/components/AddToken.tsx | 42 +++++++++++++++++++ .../AmountErrorMessage.tsx | 8 ++-- src/components/AmountErrorMessage/index.ts | 1 - src/components/AmountInput/AmountInput.tsx | 3 +- src/components/{ChainApps => }/ChainApps.tsx | 15 ++++--- src/components/ChainApps/index.ts | 1 - src/components/{ChainIcon => }/ChainIcon.tsx | 9 ++-- src/components/ChainIcon/index.ts | 1 - .../{ChainsList => }/ChainsList.tsx | 15 +++---- src/components/ChainsList/index.ts | 1 - .../CommunityPool/CommunityPool.tsx | 4 +- src/components/ErrorMessage/ErrorMessage.tsx | 4 +- .../MetaportProvider/MetaportProvider.tsx | 5 +-- src/components/SFuelWarning/SFuelWarning.tsx | 6 +-- src/components/SkConnect/SkConnect.tsx | 5 +-- src/components/SkPaper/SkPaper.tsx | 5 +-- src/components/Stepper/SkStepper.tsx | 34 ++++++--------- .../SwitchDirection/SwitchDirection.tsx | 4 +- src/components/TokenList/TokenBalance.tsx | 3 +- src/components/TokenList/TokenList.tsx | 6 +-- .../TokenListSection/TokenListSection.tsx | 4 +- src/components/TransferETA/TransferETA.tsx | 7 ++-- src/components/TransferETF/TransferETF.tsx | 4 +- src/components/WidgetBody/WidgetBody.tsx | 3 +- src/components/WidgetUI/WidgetUI.tsx | 5 +-- .../WrappedTokens/WrappedTokens.tsx | 5 +-- src/core/css.ts | 36 ++++++++++++++++ src/core/helper.ts | 8 ---- src/core/metaport.ts | 18 ++++---- src/index.ts | 5 +-- 30 files changed, 148 insertions(+), 119 deletions(-) create mode 100644 src/components/AddToken.tsx rename src/components/{AmountErrorMessage => }/AmountErrorMessage.tsx (74%) delete mode 100644 src/components/AmountErrorMessage/index.ts rename src/components/{ChainApps => }/ChainApps.tsx (89%) delete mode 100644 src/components/ChainApps/index.ts rename src/components/{ChainIcon => }/ChainIcon.tsx (76%) delete mode 100644 src/components/ChainIcon/index.ts rename src/components/{ChainsList => }/ChainsList.tsx (95%) delete mode 100644 src/components/ChainsList/index.ts create mode 100644 src/core/css.ts diff --git a/src/components/AddToken.tsx b/src/components/AddToken.tsx new file mode 100644 index 0000000..eee5a27 --- /dev/null +++ b/src/components/AddToken.tsx @@ -0,0 +1,42 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file AddToken.ts + * @copyright SKALE Labs 2023-Present + */ + +import { cls, cmn, styles } from '../core/css' + +import Button from '@mui/material/Button' +import TollIcon from '@mui/icons-material/Toll' + + +export default function AddToken(props: {}) { + return ( + + ) +} diff --git a/src/components/AmountErrorMessage/AmountErrorMessage.tsx b/src/components/AmountErrorMessage.tsx similarity index 74% rename from src/components/AmountErrorMessage/AmountErrorMessage.tsx rename to src/components/AmountErrorMessage.tsx index d134370..6d5d6c6 100644 --- a/src/components/AmountErrorMessage/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage.tsx @@ -1,10 +1,9 @@ -import React from 'react' import Collapse from '@mui/material/Collapse' -import { cls } from '../../core/helper' -import cmn from '../../styles/cmn.module.scss' +import { cls, cmn } from '../core/css' -import { useMetaportStore } from '../../store/MetaportStore' + +import { useMetaportStore } from '../store/MetaportStore' export default function AmountErrorMessage() { const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage) @@ -20,7 +19,6 @@ export default function AmountErrorMessage() { cmn.flexg, cmn.mtop10, cmn.mleft10 - // cmn.upp )} > 🔴 {amountErrorMessage} diff --git a/src/components/AmountErrorMessage/index.ts b/src/components/AmountErrorMessage/index.ts deleted file mode 100644 index 0944661..0000000 --- a/src/components/AmountErrorMessage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AmountErrorMessage' diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index ae3aeab..440cb33 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -3,8 +3,7 @@ import { useAccount } from 'wagmi' import TextField from '@mui/material/TextField' -import { cls } from '../../core/helper' -import cmn from '../../styles/cmn.module.scss' +import { cls, cmn } from '../../core/css' import localStyles from './AmountInput.module.scss' import TokenList from '../TokenList' diff --git a/src/components/ChainApps/ChainApps.tsx b/src/components/ChainApps.tsx similarity index 89% rename from src/components/ChainApps/ChainApps.tsx rename to src/components/ChainApps.tsx index c3d1920..ae33d8f 100644 --- a/src/components/ChainApps/ChainApps.tsx +++ b/src/components/ChainApps.tsx @@ -1,14 +1,13 @@ -import React from 'react' -import { cls, getChainAppsMeta, getChainAlias } from '../../core/helper' - -import styles from '../../styles/styles.module.scss' -import cmn from '../../styles/cmn.module.scss' -import { SkaleNetwork } from '../../core/interfaces' - -import ChainIcon from '../ChainIcon' import { Button } from '@mui/material' import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded' +import { cls, cmn, styles } from '../core/css' +import { SkaleNetwork } from '../core/interfaces' +import { getChainAppsMeta, getChainAlias } from '../core/helper' + +import ChainIcon from './ChainIcon' + + export default function ChainApps(props: { skaleNetwork: SkaleNetwork chain: string diff --git a/src/components/ChainApps/index.ts b/src/components/ChainApps/index.ts deleted file mode 100644 index 47b61bd..0000000 --- a/src/components/ChainApps/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ChainApps' diff --git a/src/components/ChainIcon/ChainIcon.tsx b/src/components/ChainIcon.tsx similarity index 76% rename from src/components/ChainIcon/ChainIcon.tsx rename to src/components/ChainIcon.tsx index 739ef13..b1e1e97 100644 --- a/src/components/ChainIcon/ChainIcon.tsx +++ b/src/components/ChainIcon.tsx @@ -1,10 +1,9 @@ -import React from 'react' import OfflineBoltRoundedIcon from '@mui/icons-material/OfflineBoltRounded' -import { SkaleNetwork } from '../../core/interfaces' -import { chainIconPath } from '../../core/metadata' +import { SkaleNetwork } from '../core/interfaces' +import { chainIconPath } from '../core/metadata' + +import { cls, styles } from '../core/css' -import { cls } from '../../core/helper' -import styles from '../../styles/styles.module.scss' export default function ChainIcon(props: { skaleNetwork: SkaleNetwork diff --git a/src/components/ChainIcon/index.ts b/src/components/ChainIcon/index.ts deleted file mode 100644 index afe24d4..0000000 --- a/src/components/ChainIcon/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ChainIcon' diff --git a/src/components/ChainsList/ChainsList.tsx b/src/components/ChainsList.tsx similarity index 95% rename from src/components/ChainsList/ChainsList.tsx rename to src/components/ChainsList.tsx index aeddc11..85fc3dc 100644 --- a/src/components/ChainsList/ChainsList.tsx +++ b/src/components/ChainsList.tsx @@ -7,15 +7,16 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import Button from '@mui/material/Button' import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded' -import ChainApps from '../ChainApps' -import ChainIcon from '../ChainIcon' +import ChainApps from './ChainApps' +import ChainIcon from './ChainIcon' -import { MetaportConfig } from '../../core/interfaces' +import { MetaportConfig } from '../core/interfaces' + +import { getChainAlias, getChainAppsMeta } from '../core/helper' +import { cls, cmn, styles } from '../core/css' + +import SkPaper from './SkPaper/SkPaper' -import { cls, getChainAlias, getChainAppsMeta } from '../../core/helper' -import cmn from '../../styles/cmn.module.scss' -import styles from '../../styles/styles.module.scss' -import SkPaper from '../SkPaper/SkPaper' export default function ChainsList(props: { config: MetaportConfig diff --git a/src/components/ChainsList/index.ts b/src/components/ChainsList/index.ts deleted file mode 100644 index 04f8e63..0000000 --- a/src/components/ChainsList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ChainsList' diff --git a/src/components/CommunityPool/CommunityPool.tsx b/src/components/CommunityPool/CommunityPool.tsx index 94b18e6..09fb295 100644 --- a/src/components/CommunityPool/CommunityPool.tsx +++ b/src/components/CommunityPool/CommunityPool.tsx @@ -47,9 +47,7 @@ import { fromWei } from '../../core/convertation' import { withdraw, recharge } from '../../core/community_pool' import { BALANCE_UPDATE_INTERVAL_MS, DEFAULT_ERC20_DECIMALS } from '../../core/constants' -import { cls } from '../../core/helper' -import cmn from '../../styles/cmn.module.scss' -import styles from '../../styles/styles.module.scss' +import { cls, cmn, styles } from '../../core/css' import { useCPStore } from '../../store/CommunityPoolStore' import { useCollapseStore } from '../../store/Store' diff --git a/src/components/ErrorMessage/ErrorMessage.tsx b/src/components/ErrorMessage/ErrorMessage.tsx index a57c879..4c57631 100644 --- a/src/components/ErrorMessage/ErrorMessage.tsx +++ b/src/components/ErrorMessage/ErrorMessage.tsx @@ -1,9 +1,7 @@ import React from 'react' import Button from '@mui/material/Button' -import { cls } from '../../core/helper' -import cmn from '../../styles/cmn.module.scss' -import styles from '../../styles/styles.module.scss' +import { cls, cmn, styles } from '../../core/css' import { ErrorMessage } from '../../core/dataclasses' diff --git a/src/components/MetaportProvider/MetaportProvider.tsx b/src/components/MetaportProvider/MetaportProvider.tsx index 91b10ec..64153c6 100644 --- a/src/components/MetaportProvider/MetaportProvider.tsx +++ b/src/components/MetaportProvider/MetaportProvider.tsx @@ -47,15 +47,12 @@ import { constructWagmiChain, getWebSocketUrl } from '../../core/wagmi_network' import { getWidgetTheme, getMuiZIndex } from '../../core/themes' -import { cls } from '../../core/helper' +import { cls, cmn, styles } from '../../core/css' import { useUIStore } from '../../store/Store' import { useMetaportStore } from '../../store/MetaportStore' import MetaportCore from '../../core/metaport' -import styles from '../../styles/styles.module.scss' -import cmn from '../../styles/cmn.module.scss' - const { chains, webSocketPublicClient } = configureChains( [ mainnet, diff --git a/src/components/SFuelWarning/SFuelWarning.tsx b/src/components/SFuelWarning/SFuelWarning.tsx index 48a60a1..d4e21f4 100644 --- a/src/components/SFuelWarning/SFuelWarning.tsx +++ b/src/components/SFuelWarning/SFuelWarning.tsx @@ -37,13 +37,13 @@ import { Station } from '../../core/sfuel' import { useMetaportStore } from '../../store/MetaportStore' import { useSFuelStore } from '../../store/SFuelStore' -import { cls } from '../../core/helper' -import cmn from '../../styles/cmn.module.scss' -import styles from '../../styles/styles.module.scss' +import { cls, cmn, styles } from '../../core/css' + debug.enable('*') const log = debug('metaport:components:SFuel') + export default function SFuelWarning(props: {}) { const mpc = useMetaportStore((state) => state.mpc) const chainName1 = useMetaportStore((state) => state.chainName1) diff --git a/src/components/SkConnect/SkConnect.tsx b/src/components/SkConnect/SkConnect.tsx index 23d1dc4..0ffb169 100644 --- a/src/components/SkConnect/SkConnect.tsx +++ b/src/components/SkConnect/SkConnect.tsx @@ -27,10 +27,7 @@ import Jazzicon, { jsNumberForAddress } from 'react-jazzicon' import Button from '@mui/material/Button' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { cls } from '../../core/helper' - -import styles from '../../styles/styles.module.scss' -import cmn from '../../styles/cmn.module.scss' +import { cls, cmn, styles } from '../../core/css' import skaleLogoFull from '../WidgetUI/skale_logo.svg' import { useMetaportStore } from '../../store/MetaportStore' diff --git a/src/components/SkPaper/SkPaper.tsx b/src/components/SkPaper/SkPaper.tsx index e122ec0..11509ba 100644 --- a/src/components/SkPaper/SkPaper.tsx +++ b/src/components/SkPaper/SkPaper.tsx @@ -22,10 +22,7 @@ */ import React, { ReactElement } from 'react' -import { cls } from '../../core/helper' - -import styles from '../../styles/styles.module.scss' -import cmn from '../../styles/cmn.module.scss' +import { cls, cmn, styles } from '../../core/css' import { useUIStore } from '../../store/Store' diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index c2ce37c..c2d5921 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useState } from 'react' +import { useEffect, useState } from 'react' +import { useWalletClient, useSwitchNetwork, useAccount } from 'wagmi' import Box from '@mui/material/Box' import Stepper from '@mui/material/Stepper' @@ -7,26 +8,24 @@ import StepLabel from '@mui/material/StepLabel' import StepContent from '@mui/material/StepContent' import Button from '@mui/material/Button' import LoadingButton from '@mui/lab/LoadingButton' +import Collapse from '@mui/material/Collapse' -import { cls, getChainAlias, getRandom } from '../../core/helper' -import cmn from '../../styles/cmn.module.scss' -import styles from '../../styles/styles.module.scss' +import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded' +import TollIcon from '@mui/icons-material/Toll' + +import { getChainAlias, getRandom } from '../../core/helper' +import { cls, cmn, styles } from '../../core/css' import localStyles from './SkStepper.module.scss' + import ChainIcon from '../ChainIcon' +import AddToken from '../AddToken' import { useMetaportStore } from '../../store/MetaportStore' import { useCPStore } from '../../store/CommunityPoolStore' -import { Collapse } from '@mui/material' import { SkaleNetwork } from '../../core/interfaces' - -import { useWalletClient } from 'wagmi' - -import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded' -import TollIcon from '@mui/icons-material/Toll' - -import { useSwitchNetwork, useAccount } from 'wagmi' import { SUCCESS_EMOJIS } from '../../core/constants' + export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { const { address } = useAccount() const { switchNetworkAsync } = useSwitchNetwork() @@ -137,16 +136,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) {

- + ) } diff --git a/src/components/AmountErrorMessage.tsx b/src/components/AmountErrorMessage.tsx index 6d5d6c6..f7a4627 100644 --- a/src/components/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage.tsx @@ -2,7 +2,6 @@ import Collapse from '@mui/material/Collapse' import { cls, cmn } from '../core/css' - import { useMetaportStore } from '../store/MetaportStore' export default function AmountErrorMessage() { diff --git a/src/components/ChainApps.tsx b/src/components/ChainApps.tsx index ae33d8f..4016e8e 100644 --- a/src/components/ChainApps.tsx +++ b/src/components/ChainApps.tsx @@ -7,7 +7,6 @@ import { getChainAppsMeta, getChainAlias } from '../core/helper' import ChainIcon from './ChainIcon' - export default function ChainApps(props: { skaleNetwork: SkaleNetwork chain: string diff --git a/src/components/ChainIcon.tsx b/src/components/ChainIcon.tsx index b1e1e97..024ce44 100644 --- a/src/components/ChainIcon.tsx +++ b/src/components/ChainIcon.tsx @@ -4,7 +4,6 @@ import { chainIconPath } from '../core/metadata' import { cls, styles } from '../core/css' - export default function ChainIcon(props: { skaleNetwork: SkaleNetwork chainName: string diff --git a/src/components/ChainsList.tsx b/src/components/ChainsList.tsx index 07777b3..1bc770a 100644 --- a/src/components/ChainsList.tsx +++ b/src/components/ChainsList.tsx @@ -17,7 +17,6 @@ import { cls, cmn, styles } from '../core/css' import SkPaper from './SkPaper' - export default function ChainsList(props: { config: MetaportConfig expanded: string | false diff --git a/src/components/SFuelWarning.tsx b/src/components/SFuelWarning.tsx index c077e2d..fe5e320 100644 --- a/src/components/SFuelWarning.tsx +++ b/src/components/SFuelWarning.tsx @@ -39,11 +39,9 @@ import { useSFuelStore } from '../store/SFuelStore' import { cls, cmn, styles } from '../core/css' - debug.enable('*') const log = debug('metaport:components:SFuel') - export default function SFuelWarning(props: {}) { const mpc = useMetaportStore((state) => state.mpc) const chainName1 = useMetaportStore((state) => state.chainName1) diff --git a/src/components/SkConnect.tsx b/src/components/SkConnect.tsx index 38d5800..4667c92 100644 --- a/src/components/SkConnect.tsx +++ b/src/components/SkConnect.tsx @@ -31,7 +31,6 @@ import { cls, cmn, styles } from '../core/css' import { useMetaportStore } from '../store/MetaportStore' - export default function SkConnect() { const transferInProgress = useMetaportStore((state) => state.transferInProgress) return ( diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index c2d5921..d0a4076 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -25,7 +25,6 @@ import { useCPStore } from '../../store/CommunityPoolStore' import { SkaleNetwork } from '../../core/interfaces' import { SUCCESS_EMOJIS } from '../../core/constants' - export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { const { address } = useAccount() const { switchNetworkAsync } = useSwitchNetwork() @@ -41,6 +40,11 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { const execute = useMetaportStore((state) => state.execute) const startOver = useMetaportStore((state) => state.startOver) + const token = useMetaportStore((state) => state.token) + const mpc = useMetaportStore((state) => state.mpc) + const chainName2 = useMetaportStore((state) => state.chainName2) + const ima2 = useMetaportStore((state) => state.ima2) + const amount = useMetaportStore((state) => state.amount) const cpData = useCPStore((state) => state.cpData) @@ -99,12 +103,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { className={cls(styles.btnAction, cmn.mtop5)} onClick={() => execute(address, switchNetworkAsync, walletClient)} disabled={ - !!( - amountErrorMessage || - loading || - amount == '' || - !cpData.exitGasOk - ) + !!(amountErrorMessage || loading || amount == '' || !cpData.exitGasOk) } > {step.btnText} @@ -136,7 +135,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) {

- + +
+ ) +} diff --git a/src/components/MetaportProvider.tsx b/src/components/MetaportProvider.tsx index 5621ba2..43cbadf 100644 --- a/src/components/MetaportProvider.tsx +++ b/src/components/MetaportProvider.tsx @@ -36,7 +36,7 @@ import { enkryptWallet } from '@rainbow-me/rainbowkit/wallets' -import { MetaportConfig } from '../core/interfaces' +import { MetaportConfig, ActionStateUpdate } from '../core/interfaces' import { StyledEngineProvider } from '@mui/material/styles' import { createTheme, ThemeProvider } from '@mui/material/styles' @@ -105,6 +105,7 @@ export default function MetaportProvider(props: { const setTheme = useUIStore((state) => state.setTheme) const setMpc = useMetaportStore((state) => state.setMpc) + const addTransaction = useMetaportStore((state) => state.addTransaction) const setOpen = useUIStore((state) => state.setOpen) const metaportTheme = useUIStore((state) => state.theme) @@ -113,6 +114,7 @@ export default function MetaportProvider(props: { useEffect(() => { setOpen(props.config.openOnLoad) + window.addEventListener('metaport_actionStateUpdated', actionStateUpdated, false) }, []) useEffect(() => { @@ -123,6 +125,28 @@ export default function MetaportProvider(props: { setMpc(new MetaportCore(props.config)) }, [setMpc]) + function actionStateUpdated(e: CustomEvent) { + const actionStateUpdate: ActionStateUpdate = e.detail + if (actionStateUpdate.transactionHash) { + let chainName = actionStateUpdate.actionData.chainName1 + if ( + actionStateUpdate.actionState === 'transferETHDone' || + actionStateUpdate.actionState === 'unwrapDone' + ) { + chainName = actionStateUpdate.actionData.chainName2 + } + addTransaction({ + tx: { + transactionHash: actionStateUpdate.transactionHash, + gasUsed: 1000 + }, + timestamp: actionStateUpdate.timestamp, + chainName, + txName: actionStateUpdate.actionState + }) + } + } + let theme = createTheme({ zIndex: getMuiZIndex(widgetTheme), palette: { diff --git a/src/components/SkConnect.tsx b/src/components/SkConnect.tsx index 4667c92..0e1dc37 100644 --- a/src/components/SkConnect.tsx +++ b/src/components/SkConnect.tsx @@ -93,14 +93,8 @@ export default function SkConnect() { ) } return ( -
-
- {/* */} -
+
+
{account.displayName} - {/* {account.displayBalance - ? ` (${account.displayBalance})` - : ''} */} - +
diff --git a/src/components/TransactionData/TransactionData.module.scss b/src/components/TransactionData/TransactionData.module.scss new file mode 100644 index 0000000..ad6f404 --- /dev/null +++ b/src/components/TransactionData/TransactionData.module.scss @@ -0,0 +1,26 @@ +@import '../../styles/variables'; + +.transactionDataIcon { + width: 30px; + height: 30px; + border-radius: 50%; + + svg, + img { + width: 15px !important; + height: 15px !important; + } +} + + +.sk__openExplorerBtn { + svg { + width: 15px !important; + height: 15px !important; + } + + background-color: $sk-paper-color !important; + + height: 30px; + width: 30px; +} \ No newline at end of file diff --git a/src/components/TransactionData/TransactionData.tsx b/src/components/TransactionData/TransactionData.tsx new file mode 100644 index 0000000..3afe1fd --- /dev/null +++ b/src/components/TransactionData/TransactionData.tsx @@ -0,0 +1,138 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @file TransactionData.ts + * @copyright SKALE Labs 2023-Present + */ + +import { ReactElement } from 'react' + +import IconButton from '@mui/material/IconButton' + +import MoveUpIcon from '@mui/icons-material/MoveUp' +import MoveDownIcon from '@mui/icons-material/MoveDown' +import LockOpenIcon from '@mui/icons-material/LockOpen' +import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward' +import OpenInNewIcon from '@mui/icons-material/OpenInNew' +import DoneRoundedIcon from '@mui/icons-material/DoneRounded' + +import { getTxUrl } from '../../core/explorer' +import { ActionState, MetaportConfig, TransactionHistory } from '../../core/interfaces' + +import localStyles from './TransactionData.module.scss' +import { cls, styles, cmn } from '../../core/css' + +type ActionStateIconMap = { + [key in ActionState]: ReactElement | null +} + +type ActionStateAliasMap = { + [key in ActionState]: string | null +} + +const actionIcons: ActionStateIconMap = { + approveDone: , + transferDone: , + transferETHDone: , + approveWrapDone: , + wrapDone: , + unwrapDone: , + unlockDone: , + receivedETH: null, + init: null, + approve: null, + transfer: null, + received: null, + transferETH: null, + approveWrap: null, + wrap: null, + unwrap: null, + switch: null, + unlock: null +} + +const actionAliases: ActionStateAliasMap = { + approveDone: 'Approve', + transferDone: 'Transfer', + transferETHDone: 'Transfer ETH', + approveWrapDone: 'Approve wrap', + wrapDone: 'Wrap', + unwrapDone: 'Unwrap', + unlockDone: 'Unlock ETH', + receivedETH: null, + init: null, + approve: null, + transfer: null, + received: null, + transferETH: null, + approveWrap: null, + wrap: null, + unwrap: null, + switch: null, + unlock: null +} + +export default function TransactionData(props: { + transactionData: TransactionHistory + config: MetaportConfig +}) { + const explorerUrl = getTxUrl( + props.transactionData.chainName, + props.config.skaleNetwork, + props.transactionData.tx.transactionHash + ) + return ( +
+
+
+ {actionIcons[props.transactionData.txName]} +
+
+
+
+

+ {actionAliases[props.transactionData.txName]} +

+

+ {new Date(props.transactionData.timestamp * 1000).toUTCString()} +

+
+
+
+ + + +
+
+ ) +} diff --git a/src/components/TransactionData/index.ts b/src/components/TransactionData/index.ts new file mode 100644 index 0000000..2824e61 --- /dev/null +++ b/src/components/TransactionData/index.ts @@ -0,0 +1 @@ +export { default } from './TransactionData' diff --git a/src/components/TransactionsHistory.tsx b/src/components/TransactionsHistory.tsx new file mode 100644 index 0000000..9fd8b18 --- /dev/null +++ b/src/components/TransactionsHistory.tsx @@ -0,0 +1,171 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file TransactionsHistory.ts + * @copyright SKALE Labs 2023-Present + */ + +import { Collapse } from '@mui/material' +import Button from '@mui/material/Button' + +import ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded' +import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded' +import CloseRoundedIcon from '@mui/icons-material/CloseRounded' + +import TokenIcon from './TokenIcon' +import ChainIcon from './ChainIcon' +import TransactionData from './TransactionData' + +import { useMetaportStore } from '../store/MetaportStore' +import { useCollapseStore } from '../store/Store' +import { cls, styles, cmn } from '../core/css' +import { interfaces, SkPaper } from '../Metaport' + +import { getChainAlias } from '../core/helper' + +export default function TransactionsHistory() { + const transactionsHistory = useMetaportStore((state) => state.transactionsHistory) + const transfersHistory = useMetaportStore((state) => state.transfersHistory) + + const clearTransactionsHistory = useMetaportStore((state) => state.clearTransactionsHistory) + const mpc = useMetaportStore((state) => state.mpc) + const expandedTH = useCollapseStore((state) => state.expandedTH) + const setExpandedTH = useCollapseStore((state) => state.setExpandedTH) + + function clearTransferHistory() { + clearTransactionsHistory() + setExpandedTH(false) + } + + if (transactionsHistory.length === 0 && transfersHistory.length === 0) return + return ( + +
+ + +
+ {transactionsHistory.length !== 0 ? ( + +

+ Current transfer +

+ + {transactionsHistory.map((transactionData: any) => ( + + ))} + +
+ ) : null} +
+ {transfersHistory.map((transferHistory: interfaces.TransferHistory, key: number) => ( + +
+ +

+ {getChainAlias(mpc.config.skaleNetwork, transferHistory.chainName1)} +

+ + +

+ {getChainAlias(mpc.config.skaleNetwork, transferHistory.chainName2)} +

+
+
+
+ +
+

+ {transferHistory.amount} {transferHistory.tokenKeyname} +

+

+ •{' '} + {transferHistory.address.substring(0, 6) + + '...' + + transferHistory.address.substring(transferHistory.address.length - 4)} +

+
+ + {transferHistory.transactions.map((transactionData: any) => ( + + ))} + +
+ ))} +
+
+ ) +} diff --git a/src/components/WidgetBody.tsx b/src/components/WidgetBody.tsx index 4d4c481..949bca2 100644 --- a/src/components/WidgetBody.tsx +++ b/src/components/WidgetBody.tsx @@ -19,6 +19,8 @@ import CommunityPool from './CommunityPool' import SFuelWarning from './SFuelWarning' import SkConnect from './SkConnect' import WrappedTokens from './WrappedTokens' +import TransactionsHistory from './TransactionsHistory' +import HistoryButton from './HistoryButton' import { cls, cmn } from '../core/css' import { Collapse } from '@mui/material' @@ -35,6 +37,7 @@ export function WidgetBody(props) { const expandedCP = useCollapseStore((state) => state.expandedCP) const expandedWT = useCollapseStore((state) => state.expandedWT) const expandedTokens = useCollapseStore((state) => state.expandedTokens) + const expandedTH = useCollapseStore((state) => state.expandedTH) const destChains = useMetaportStore((state) => state.destChains) @@ -77,11 +80,19 @@ export function WidgetBody(props) { } }, [tokens]) - const showFrom = !expandedTo && !expandedTokens && !errorMessage && !expandedCP - const showTo = !expandedFrom && !expandedTokens && !errorMessage && !expandedCP && !expandedWT - const showInput = !expandedFrom && !expandedTo && !errorMessage && !expandedCP && !expandedWT + const showFrom = !expandedTo && !expandedTokens && !errorMessage && !expandedCP && !expandedTH + const showTo = + !expandedFrom && !expandedTokens && !errorMessage && !expandedCP && !expandedWT && !expandedTH + const showInput = + !expandedFrom && !expandedTo && !errorMessage && !expandedCP && !expandedWT && !expandedTH const showSwitch = - !expandedFrom && !expandedTo && !expandedTokens && !errorMessage && !expandedCP && !expandedWT + !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + !expandedWT && + !expandedTH const showStepper = !expandedFrom && !expandedTo && @@ -90,11 +101,13 @@ export function WidgetBody(props) { !expandedCP && sFuelOk && !expandedWT && + !expandedTH && !!address const showCP = !expandedFrom && !expandedTo && !expandedTokens && + !expandedTH && chainName2 === MAINNET_CHAIN_NAME && !expandedWT const showWT = @@ -103,8 +116,17 @@ export function WidgetBody(props) { !expandedTokens && !errorMessage && !expandedCP && + !expandedTH && sFuelOk && !!address + const showTH = + !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + !expandedWT && + !!address const showError = !!errorMessage const grayBg = 'rgb(136 135 135 / 15%)' @@ -113,6 +135,16 @@ export function WidgetBody(props) { return (
+ {!!address ? ( +
+
+
+ +
+ +
+ ) : null} + @@ -192,6 +224,12 @@ export function WidgetBody(props) { + + + + + + diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index 287c78f..c241a6a 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -98,12 +98,10 @@ export function WidgetUI(props: { config: MetaportConfig }) {
- {!!address ? : null} - {/* {address ? :
} */}
diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index 4b7cea4..b6fc0e9 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -32,13 +32,14 @@ import { TokenData, CustomAbiTokenType } from '../dataclasses' import MetaportCore, { createTokenData } from '../metaport' import { externalEvents } from '../events' import { toWei } from '../convertation' -import { ActionState, LOADING_BUTTON_TEXT } from './actionState' +import { LOADING_BUTTON_TEXT } from './actionState' import { isMainnet } from '../helper' import { IMA_ABIS } from '../contracts' import { isMainnetChainId, getMainnetAbi } from '../network' import { walletClientToSigner } from '../ethers' +import { interfaces } from '../../Metaport' debug.enable('*') const log = debug('metaport:actions') @@ -161,29 +162,15 @@ export class Action { this.setBtnText = setBtnText this._switchNetwork = switchNetwork this.walletClient = walletClient - - // if (this.tokenData) this.wrap = !!this.token.unwrappedSymbol && !this.token.clone; } - // tokenContract( - // provider: Provider, - // source: boolean = true - // ): Contract { - // return this.mpc.tokenContract( - // source ? this.chainName1 : this.chainName2, - // this.token.keyname, - // this.token.type, - // provider - // ) - // } - - updateState(currentState: ActionState, transactionHash?: string, timestamp?: string | number) { + updateState(currentState: interfaces.ActionState, transactionHash?: string, timestamp?: number) { log(`actionStateUpd: ${this.constructor.name} - ${currentState} - ${this.token.keyname} \ - ${this.chainName1} -> ${this.chainName2}`) - externalEvents.actionStateUpdated( - this.constructor.name, - currentState, - { + externalEvents.actionStateUpdated({ + actionName: this.constructor.name, + actionState: currentState, + actionData: { chainName1: this.chainName1, chainName2: this.chainName2, address: this.address, @@ -193,7 +180,7 @@ export class Action { }, transactionHash, timestamp - ) + }) this.setBtnText(LOADING_BUTTON_TEXT[currentState]) } @@ -233,9 +220,3 @@ export class Action { return chain } } - -export abstract class TransferAction extends Action { - transferComplete(tx): void { - externalEvents.transferComplete(tx, this.chainName1, this.chainName2, this.token.keyname, false) - } -} diff --git a/src/core/actions/actionState.ts b/src/core/actions/actionState.ts index 913aff9..788e901 100644 --- a/src/core/actions/actionState.ts +++ b/src/core/actions/actionState.ts @@ -21,25 +21,7 @@ * @copyright SKALE Labs 2023-Present */ -export type ActionState = - | 'init' - | 'approve' - | 'approveDone' - | 'transfer' - | 'transferDone' - | 'received' - | 'transferETH' - | 'transferETHDone' - | 'receivedETH' - | 'approveWrap' - | 'approveWrapDone' - | 'wrap' - | 'wrapDone' - | 'unwrap' - | 'unwrapDone' - | 'switch' - | 'unlock' - | 'unlockDone' +import { ActionState } from '../interfaces' type LoadingButtonTextMap = { [key in ActionState]: string diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index fa39cc2..eff3335 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -30,14 +30,14 @@ import { externalEvents } from '../events' import { toWei } from '../convertation' import { MAX_APPROVE_AMOUNT } from '../constants' -import { TransferAction, Action } from '../actions/action' +import { Action } from '../actions/action' import { checkERC20Balance, checkERC20Allowance, checkSFuelBalance } from './checks' import { CustomAbiTokenType } from '../dataclasses' debug.enable('*') const log = debug('metaport:actions:erc20') -export class TransferERC20S2S extends TransferAction { +export class TransferERC20S2S extends Action { async execute() { this.updateState('init') const checkResAllowance = await checkERC20Allowance( @@ -64,7 +64,6 @@ export class TransferERC20S2S extends TransferAction { ) const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) - externalEvents.transactionCompleted(approveTx, txBlock.timestamp, this.chainName1, 'approve') log('ApproveERC20S:execute - tx completed: %O', approveTx) } @@ -123,7 +122,6 @@ export class WrapSFuelERC20S extends Action { }) const block = await this.sChain1.provider.getBlock(tx.blockNumber) this.updateState('wrapDone', tx.hash, block.timestamp) - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'wrapsfuel') log('WrapSFuelERC20S:execute - tx completed %O', tx) } @@ -235,7 +233,6 @@ export class UnWrapERC20S extends Action { log('UnWrapERC20S:execute - tx completed %O', tx) const block = await sChain.provider.getBlock(tx.blockNumber) this.updateState('unwrapDone', tx.hash, block.timestamp) - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'unwrap') externalEvents.unwrapComplete(tx, this.chainName2, this.token.keyname) } @@ -255,7 +252,7 @@ export class UnWrapERC20S extends Action { } } -export class TransferERC20M2S extends TransferAction { +export class TransferERC20M2S extends Action { async execute() { this.updateState('init') @@ -285,18 +282,10 @@ export class TransferERC20M2S extends TransferAction { }) const block = await mainnet.provider.getBlock(tx.blockNumber) this.updateState('transferDone', tx.hash, block.timestamp) - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'deposit') log('TransferERC20M2S:execute - tx completed %O', tx) await this.sChain2.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination) this.updateState('received') log('TransferERC20M2S:execute - tokens received to destination chain') - externalEvents.transferComplete( - tx.hash, - this.chainName1, - this.chainName2, - this.token.keyname, - false - ) } async preAction() { @@ -314,7 +303,7 @@ export class TransferERC20M2S extends TransferAction { } } -export class TransferERC20S2M extends TransferAction { +export class TransferERC20S2M extends Action { async execute() { this.updateState('init') // check approve + approve @@ -339,7 +328,6 @@ export class TransferERC20S2M extends TransferAction { ) const txBlock = await sChain.provider.getBlock(approveTx.blockNumber) this.updateState('approveDone', approveTx.hash, txBlock.timestamp) - externalEvents.transactionCompleted(approveTx, txBlock.timestamp, this.chainName1, 'approve') log('ApproveERC20S:execute - tx completed: %O', approveTx) } this.updateState('transfer') @@ -348,18 +336,10 @@ export class TransferERC20S2M extends TransferAction { const tx = await sChain.erc20.withdraw(this.originAddress, amountWei, { address: this.address }) const block = await sChain.provider.getBlock(tx.blockNumber) this.updateState('transferDone', tx.hash, block.timestamp) - externalEvents.transactionCompleted(tx, block.timestamp, this.chainName1, 'withdraw') log('TransferERC20S2M:execute - tx completed %O', tx) this.mainnet.waitERC20BalanceChange(this.destToken, this.address, balanceOnDestination) this.updateState('received') log('TransferERC20S2M:execute - tokens received to destination chain') - externalEvents.transferComplete( - tx.hash, - this.chainName1, - this.chainName2, - this.token.keyname, - false - ) } async preAction() { diff --git a/src/core/actions/eth.ts b/src/core/actions/eth.ts index d1fd378..b0dbcff 100644 --- a/src/core/actions/eth.ts +++ b/src/core/actions/eth.ts @@ -24,15 +24,14 @@ import debug from 'debug' import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import { externalEvents } from '../events' import { toWei } from '../convertation' -import { TransferAction, Action } from './action' +import { Action } from './action' import { checkEthBalance } from './checks' debug.enable('*') const log = debug('metaport:actions:eth') -export class TransferEthM2S extends TransferAction { +export class TransferEthM2S extends Action { async execute() { this.updateState('init') const amountWei = toWei(this.amount, this.token.meta.decimals) @@ -64,7 +63,7 @@ export class TransferEthM2S extends TransferAction { } } -export class TransferEthS2M extends TransferAction { +export class TransferEthS2M extends Action { async execute() { log('TransferEthS2M: started') this.updateState('init') diff --git a/src/core/events.ts b/src/core/events.ts index fdf3285..a5023c4 100644 --- a/src/core/events.ts +++ b/src/core/events.ts @@ -23,7 +23,6 @@ import debug from 'debug' import * as interfaces from './interfaces/index' -import { ActionState } from './actions/actionState' debug.enable('*') const log = debug('metaport:core:events') @@ -63,45 +62,8 @@ export namespace externalEvents { dispatchEvent('metaport_transferRequestCompleted', { transferRequest: transferRequest }) } - export function transactionCompleted( - txData: any, - timestamp: string | number, - chainName: string, - txName: string - ): void { - log('WARNING: Event metaport_transactionCompleted will be removed in the next version') - dispatchEvent('metaport_transactionCompleted', { - tx: { - gasUsed: txData.gasUsed, - transactionHash: txData.transactionHash - }, - timestamp, - chainName, - txName - }) - } - - export function actionStateUpdated( - actionName: string, - actionState: ActionState, - actionData: { - chainName1: string - chainName2: string - address: string - amount: string - amountWei: bigint - tokenId: number - }, - transactionHash?: string, - timestamp?: string | number - ): void { - dispatchEvent('metaport_actionStateUpdated', { - actionState, - actionName, - actionData, - transactionHash, - timestamp - }) + export function actionStateUpdated(actionStateUpdate: interfaces.ActionStateUpdate): void { + dispatchEvent('metaport_actionStateUpdated', actionStateUpdate) } export function unwrapComplete(tx: string, chainName1: string, tokenSymbol: string) { diff --git a/src/core/interfaces/ActionState.ts b/src/core/interfaces/ActionState.ts new file mode 100644 index 0000000..e8e3563 --- /dev/null +++ b/src/core/interfaces/ActionState.ts @@ -0,0 +1,42 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file ActionState.ts + * @copyright SKALE Labs 2023-Present + */ + +export type ActionState = + | 'init' + | 'approve' + | 'approveDone' + | 'transfer' + | 'transferDone' + | 'received' + | 'transferETH' + | 'transferETHDone' + | 'receivedETH' + | 'approveWrap' + | 'approveWrapDone' + | 'wrap' + | 'wrapDone' + | 'unwrap' + | 'unwrapDone' + | 'switch' + | 'unlock' + | 'unlockDone' diff --git a/src/core/interfaces/ActionStateUpdate.ts b/src/core/interfaces/ActionStateUpdate.ts new file mode 100644 index 0000000..3a796bd --- /dev/null +++ b/src/core/interfaces/ActionStateUpdate.ts @@ -0,0 +1,39 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file ActionStateUpdate.ts + * @copyright SKALE Labs 2023-Present + */ + +import { ActionState } from './ActionState' + +export interface ActionStateUpdate { + actionName: string + actionState: ActionState + actionData: { + chainName1: string + chainName2: string + address: string + amount: string + amountWei: bigint + tokenId: number + } + transactionHash?: string + timestamp?: number +} diff --git a/src/core/interfaces/TransactionHistory.ts b/src/core/interfaces/TransactionHistory.ts index 0b03e6a..109014a 100644 --- a/src/core/interfaces/TransactionHistory.ts +++ b/src/core/interfaces/TransactionHistory.ts @@ -21,6 +21,8 @@ * @copyright SKALE Labs 2023-Present */ +import { AddressType } from '.' + interface TxData { gasUsed: number transactionHash: string @@ -32,3 +34,12 @@ export interface TransactionHistory { chainName: string txName: string } + +export interface TransferHistory { + transactions: TransactionHistory[] + tokenKeyname: string + address: AddressType + chainName1: string + chainName2: string + amount: string +} diff --git a/src/core/interfaces/index.ts b/src/core/interfaces/index.ts index b8a8543..b64abb8 100644 --- a/src/core/interfaces/index.ts +++ b/src/core/interfaces/index.ts @@ -31,5 +31,7 @@ export * from './CheckRes' export * from './TransactionHistory' export * from './CommunityPoolData' export * from './TokenMetadata' +export * from './ActionStateUpdate' +export * from './ActionState' export type AddressType = `0x${string}` diff --git a/src/index.ts b/src/index.ts index cda7d20..db94f55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,8 @@ import ErrorMessage from './components/ErrorMessage' import CommunityPool from './components/CommunityPool' import SFuelWarning from './components/SFuelWarning' import WrappedTokens from './components/WrappedTokens' +import HistoryButton from './components/HistoryButton' +import TransactionsHistory from './components/TransactionsHistory' import { CHAINS_META, getChainAlias } from './core/helper' import { cls, styles, cmn } from './core/css' @@ -62,6 +64,8 @@ export { CommunityPool, SFuelWarning, WrappedTokens, + TransactionsHistory, + HistoryButton, cls, styles, cmn, diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 8aa92f8..060eb0d 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -113,6 +113,14 @@ export interface MetaportState { btnText: string setBtnText: (btnText: string) => void + transactionsHistory: interfaces.TransactionHistory[] + setTransactionsHistory: (transactionsHistory: interfaces.TransactionHistory[]) => void + addTransaction: (transaction: interfaces.TransactionHistory) => void + clearTransactionsHistory: () => void + + transfersHistory: interfaces.TransferHistory[] + setTransfersHistory: (transfersHistory: interfaces.TransferHistory[]) => void + errorMessageClosedFallback: () => void startOver: () => void } diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts index 7f71fa6..d34ba86 100644 --- a/src/store/MetaportStore.ts +++ b/src/store/MetaportStore.ts @@ -134,8 +134,25 @@ export const useMetaportStore = create()((set, get) => ({ } finally { set({ loading: false }) } + + const isTransferFinished = get().currentStep + 1 === get().stepsMetadata.length + + if (isTransferFinished) { + get().setTransfersHistory([ + ...get().transfersHistory, + { + transactions: get().transactionsHistory, + chainName1: get().chainName1, + chainName2: get().chainName2, + amount: get().amount, + tokenKeyname: get().token.keyname, + address: address + } + ]) + } + set({ - transferInProgress: get().currentStep + 1 !== get().stepsMetadata.length, + transferInProgress: !isTransferFinished, currentStep: get().currentStep + 1 }) } @@ -280,5 +297,27 @@ export const useMetaportStore = create()((set, get) => ({ setTransferInProgress: (inProgress: boolean) => set(() => ({ transferInProgress: inProgress })), btnText: null, - setBtnText: (btnText: string) => set(() => ({ btnText: btnText })) + setBtnText: (btnText: string) => set(() => ({ btnText: btnText })), + + transactionsHistory: [], + setTransactionsHistory: (transactionsHistory: interfaces.TransactionHistory[]) => { + set({ transactionsHistory: transactionsHistory }) + }, + + addTransaction(transaction: interfaces.TransactionHistory): void { + const history = get().transactionsHistory + history.push(transaction) + set({ transactionsHistory: [...history] }) + }, + clearTransactionsHistory(): void { + set({ transactionsHistory: [], transfersHistory: [] }) + }, + + transfersHistory: [], + setTransfersHistory: (transfersHistory: interfaces.TransferHistory[]) => { + set({ + transfersHistory: transfersHistory, + transactionsHistory: [] + }) + } })) diff --git a/src/store/Store.ts b/src/store/Store.ts index fb0b9b3..4b2a269 100644 --- a/src/store/Store.ts +++ b/src/store/Store.ts @@ -52,6 +52,9 @@ export interface CollapseState { expandedWT: string | false setExpandedWT: (expanded: string | false) => void + + expandedTH: boolean + setExpandedTH: (expanded: boolean) => void } export const useCollapseStore = create()((set) => ({ @@ -62,7 +65,8 @@ export const useCollapseStore = create()((set) => ({ expandedTo: false, expandedTokens: false, expandedCP: false, - expandedWT: false + expandedWT: false, + expandedTH: false })), expandedTo: false, setExpandedTo: (expanded: string | false) => @@ -71,7 +75,8 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTokens: false, expandedCP: false, - expandedWT: false + expandedWT: false, + expandedTH: false })), expandedTokens: false, setExpandedTokens: (expanded: string | false) => @@ -80,7 +85,8 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedCP: false, - expandedWT: false + expandedWT: false, + expandedTH: false })), expandedCP: false, setExpandedCP: (expanded: string | false) => @@ -89,7 +95,8 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedTokens: false, - expandedWT: false + expandedWT: false, + expandedTH: false })), expandedWT: false, setExpandedWT: (expanded: string | false) => @@ -98,6 +105,17 @@ export const useCollapseStore = create()((set) => ({ expandedFrom: false, expandedTo: false, expandedTokens: false, - expandedWT: expanded + expandedWT: expanded, + expandedTH: false + })), + expandedTH: false, + setExpandedTH: (expanded: boolean) => + set(() => ({ + expandedCP: false, + expandedFrom: false, + expandedTo: false, + expandedTokens: false, + expandedWT: false, + expandedTH: expanded })) })) diff --git a/src/styles/cmn.module.scss b/src/styles/cmn.module.scss index b3cc0e1..e721ca9 100644 --- a/src/styles/cmn.module.scss +++ b/src/styles/cmn.module.scss @@ -27,11 +27,50 @@ flex-wrap: wrap; } +.rotate180 { + transform: rotate(180deg); +} + +.bord { + border: 1px $sk-paper-color solid; +} + +.bordtop { + border-top: 1px $sk-paper-color solid; +} + +.bordbott { + border-top: 1px $sk-paper-color solid; +} + +.bordleft { + border-left: 1px $sk-paper-color solid; +} + +.bordri { + border-right: 1px $sk-paper-color solid; +} .padd10 { padding: 10px !important; } +.pleft10 { + padding-left: 10px !important; +} + +.pri10 { + padding-right: 10px !important; +} + +.ptop10 { + padding-top: 10px !important; +} + + +.ptop20 { + padding-top: 20px !important; +} .mtop10 { margin-top: 10px !important; @@ -49,6 +88,10 @@ margin-left: 10px !important; } +.mleft15 { + margin-left: 15px !important; +} + .mleft20 { margin-left: 20px !important; } @@ -105,6 +148,11 @@ padding-top: 20px !important; } + +.ptop15 { + padding-top: 15px !important; +} + .nop { padding: 0 !important; } diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index 15fb923..6f6c1f7 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -54,9 +54,58 @@ color: $sk-disabled-dark; } + .action_transferDone, + .action_transferETHDone { + background-image: linear-gradient(45deg, #8A463C 0%, #b02d50 100%); + } + + .action_wrapDone { + background-image: linear-gradient(160deg, #237bc3 0%, #1a3b94 100%); + } + + .action_unwrapDone { + background-image: linear-gradient(160deg, #1a3b94 0%, #237bc3 100%); + } + + .action_unlockDone { + background-image: linear-gradient(45deg, #009688 0%, #0c5f57 100%); + } + + .action_approveDone { + background-image: linear-gradient(135deg, #29b1a9 0%, #14532a 100%); + } + + .action_approveWrapDone { + background-image: linear-gradient(135deg, #14532a 0%, #29b1a9 100%); + } } .lightTheme { + .action_transferDone, + .action_transferETHDone { + background-image: linear-gradient(45deg, #f7c9b1 0%, #f8d4da 100%); + } + + .action_wrapDone { + background-image: linear-gradient(160deg, #9dcff1 0%, #8ab1dd 100%); + } + + .action_unwrapDone { + background-image: linear-gradient(160deg, #8ab1dd 0%, #9dcff1 100%); + } + + .action_unlockDone { + background-image: linear-gradient(45deg, #92f7e1 0%, #76afaa 100%); + } + + .action_approveDone { + background-image: linear-gradient(135deg, #89e0ce 0%, #58a47c 100%); + } + + .action_approveWrapDone { + background-image: linear-gradient(135deg, #58a47c 0%, #89e0ce 100%); + } + .skaleLogoLg { filter: invert(1); } @@ -254,7 +303,6 @@ button { .btnApp { background-color: rgba(0, 0, 0, 0.08) !important; - } .btnChain { From 788fefd46b3ae5b95872ebfa63bc6f823b9a2b41 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 15 Sep 2023 15:22:58 +0100 Subject: [PATCH 052/110] Add dynamic skale chains support, move chains metadata --- src/components/ChainApps.tsx | 2 +- src/components/ChainsList.tsx | 2 +- src/components/MetaportProvider.tsx | 81 ++++++++----------- src/components/Stepper/SkStepper.tsx | 3 +- .../TransactionData/TransactionData.tsx | 2 +- src/components/TransactionsHistory.tsx | 2 +- src/components/WrappedTokens.tsx | 3 +- src/core/helper.ts | 39 --------- src/core/interfaces/TransactionHistory.ts | 7 +- src/core/metadata.ts | 46 ++++++++++- src/core/wagmi_network.ts | 2 +- src/index.ts | 2 +- 12 files changed, 89 insertions(+), 102 deletions(-) diff --git a/src/components/ChainApps.tsx b/src/components/ChainApps.tsx index 4016e8e..a4069e2 100644 --- a/src/components/ChainApps.tsx +++ b/src/components/ChainApps.tsx @@ -3,7 +3,7 @@ import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRigh import { cls, cmn, styles } from '../core/css' import { SkaleNetwork } from '../core/interfaces' -import { getChainAppsMeta, getChainAlias } from '../core/helper' +import { getChainAppsMeta, getChainAlias } from '../core/metadata' import ChainIcon from './ChainIcon' diff --git a/src/components/ChainsList.tsx b/src/components/ChainsList.tsx index 1bc770a..7756bb8 100644 --- a/src/components/ChainsList.tsx +++ b/src/components/ChainsList.tsx @@ -12,7 +12,7 @@ import ChainIcon from './ChainIcon' import { MetaportConfig } from '../core/interfaces' -import { getChainAlias, getChainAppsMeta } from '../core/helper' +import { getChainAlias, getChainAppsMeta } from '../core/metadata' import { cls, cmn, styles } from '../core/css' import SkPaper from './SkPaper' diff --git a/src/components/MetaportProvider.tsx b/src/components/MetaportProvider.tsx index 43cbadf..20e3ee8 100644 --- a/src/components/MetaportProvider.tsx +++ b/src/components/MetaportProvider.tsx @@ -53,54 +53,44 @@ import { useUIStore } from '../store/Store' import { useMetaportStore } from '../store/MetaportStore' import MetaportCore from '../core/metaport' -const { chains, webSocketPublicClient } = configureChains( - [ - mainnet, - goerli, - constructWagmiChain('staging', 'staging-legal-crazy-castor'), - constructWagmiChain('staging', 'staging-utter-unripe-menkar'), - constructWagmiChain('staging', 'staging-faint-slimy-achird'), - constructWagmiChain('staging', 'staging-perfect-parallel-gacrux'), - constructWagmiChain('staging', 'staging-severe-violet-wezen'), - constructWagmiChain('staging', 'staging-weepy-fitting-caph'), - - constructWagmiChain('mainnet', 'honorable-steel-rasalhague'), - constructWagmiChain('mainnet', 'elated-tan-skat'), - constructWagmiChain('mainnet', 'affectionate-immediate-pollux') - ], - [ - jsonRpcProvider({ - rpc: (chain) => ({ - http: chain.rpcUrls.default.http[0], - webSocket: getWebSocketUrl(chain) - }) - }) - ] -) - -const connectors = connectorsForWallets([ - { - groupName: 'Supported Wallets', - wallets: [ - metaMaskWallet({ chains, projectId: '' }), - enkryptWallet({ chains }), - injectedWallet({ chains }), - coinbaseWallet({ chains, appName: 'TEST' }) - ] - } -]) - -const wagmiConfig = createConfig({ - autoConnect: true, - connectors, - publicClient: webSocketPublicClient -}) - export default function MetaportProvider(props: { config: MetaportConfig className?: string children?: ReactElement | ReactElement[] }) { + const skaleChains = props.config.chains.map((chain) => + constructWagmiChain(props.config.skaleNetwork, chain) + ) + const { chains, webSocketPublicClient } = configureChains( + [mainnet, goerli, ...skaleChains], + [ + jsonRpcProvider({ + rpc: (chain) => ({ + http: chain.rpcUrls.default.http[0], + webSocket: getWebSocketUrl(chain) + }) + }) + ] + ) + + const connectors = connectorsForWallets([ + { + groupName: 'Supported Wallets', + wallets: [ + metaMaskWallet({ chains, projectId: '' }), + enkryptWallet({ chains }), + injectedWallet({ chains }), + coinbaseWallet({ chains, appName: 'SKALE Bridge' }) + ] + } + ]) + + const wagmiConfig = createConfig({ + autoConnect: true, + connectors, + publicClient: webSocketPublicClient + }) + const widgetTheme = getWidgetTheme(props.config.theme) const setTheme = useUIStore((state) => state.setTheme) @@ -136,10 +126,7 @@ export default function MetaportProvider(props: { chainName = actionStateUpdate.actionData.chainName2 } addTransaction({ - tx: { - transactionHash: actionStateUpdate.transactionHash, - gasUsed: 1000 - }, + transactionHash: actionStateUpdate.transactionHash, timestamp: actionStateUpdate.timestamp, chainName, txName: actionStateUpdate.actionState diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index d0a4076..ecef961 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -13,7 +13,8 @@ import Collapse from '@mui/material/Collapse' import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded' import TollIcon from '@mui/icons-material/Toll' -import { getChainAlias, getRandom } from '../../core/helper' +import { getRandom } from '../../core/helper' +import { getChainAlias } from '../../core/metadata' import { cls, cmn, styles } from '../../core/css' import localStyles from './SkStepper.module.scss' diff --git a/src/components/TransactionData/TransactionData.tsx b/src/components/TransactionData/TransactionData.tsx index 3afe1fd..b6b7cd0 100644 --- a/src/components/TransactionData/TransactionData.tsx +++ b/src/components/TransactionData/TransactionData.tsx @@ -95,7 +95,7 @@ export default function TransactionData(props: { const explorerUrl = getTxUrl( props.transactionData.chainName, props.config.skaleNetwork, - props.transactionData.tx.transactionHash + props.transactionData.transactionHash ) return (
diff --git a/src/components/TransactionsHistory.tsx b/src/components/TransactionsHistory.tsx index 9fd8b18..6ff33b6 100644 --- a/src/components/TransactionsHistory.tsx +++ b/src/components/TransactionsHistory.tsx @@ -37,7 +37,7 @@ import { useCollapseStore } from '../store/Store' import { cls, styles, cmn } from '../core/css' import { interfaces, SkPaper } from '../Metaport' -import { getChainAlias } from '../core/helper' +import { getChainAlias } from '../core/metadata' export default function TransactionsHistory() { const transactionsHistory = useMetaportStore((state) => state.transactionsHistory) diff --git a/src/components/WrappedTokens.tsx b/src/components/WrappedTokens.tsx index 2b3b710..9668805 100644 --- a/src/components/WrappedTokens.tsx +++ b/src/components/WrappedTokens.tsx @@ -37,10 +37,9 @@ import SkPaper from './SkPaper' import { TokenBalance } from './TokenList' import TokenIcon from './TokenIcon' -import { getTokenName } from '../core/metadata' +import { getTokenName, getChainAlias } from '../core/metadata' import { BALANCE_UPDATE_INTERVAL_MS } from '../core/constants' -import { getChainAlias } from '../core/helper' import { cls, cmn, styles } from '../core/css' import { useCollapseStore } from '../store/Store' diff --git a/src/core/helper.ts b/src/core/helper.ts index 9f66ce9..ac29597 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -28,18 +28,6 @@ import { MAINNET_CHAIN_NAME } from './constants' import { TransferRequestStatus } from './dataclasses' import { SkaleNetwork } from './interfaces' -import mainnetMeta from '../meta/mainnet/chains.json' -import stagingMeta from '../meta/staging/chains.json' -import legacyMeta from '../meta/legacy/chains.json' -import regressionMeta from '../meta/regression/chains.json' - -export const CHAINS_META = { - mainnet: mainnetMeta, - staging: stagingMeta, - legacy: legacyMeta, - regression: regressionMeta -} - export function eqArrays(arr1, arr2) { return JSON.stringify(arr1) === JSON.stringify(arr2) } @@ -63,33 +51,6 @@ export function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } -export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { - if (chainName === MAINNET_CHAIN_NAME) { - if (skaleNetwork != MAINNET_CHAIN_NAME) { - const network = skaleNetwork === 'staging' ? 'Goerli' : skaleNetwork - return `Ethereum (${network})` - } - return 'Ethereum' - } - if (CHAINS_META[skaleNetwork] && CHAINS_META[skaleNetwork][chainName]) { - if ( - app && - CHAINS_META[skaleNetwork][chainName].apps && - CHAINS_META[skaleNetwork][chainName].apps[app] - ) { - return CHAINS_META[skaleNetwork][chainName].apps[app].alias - } - return CHAINS_META[skaleNetwork][chainName].alias - } - return chainName -} - -export function getChainAppsMeta(chainName: string, skaleNetwork: SkaleNetwork) { - if (CHAINS_META[skaleNetwork][chainName] && CHAINS_META[skaleNetwork][chainName].apps) { - return CHAINS_META[skaleNetwork][chainName].apps - } -} - export function getRandom(list: Array) { return list[Math.floor(Math.random() * list.length)] } diff --git a/src/core/interfaces/TransactionHistory.ts b/src/core/interfaces/TransactionHistory.ts index 109014a..f5399ca 100644 --- a/src/core/interfaces/TransactionHistory.ts +++ b/src/core/interfaces/TransactionHistory.ts @@ -23,13 +23,8 @@ import { AddressType } from '.' -interface TxData { - gasUsed: number - transactionHash: string -} - export interface TransactionHistory { - tx: TxData + transactionHash: string timestamp: number chainName: string txName: string diff --git a/src/core/metadata.ts b/src/core/metadata.ts index c98de9d..393d6c9 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -24,7 +24,11 @@ import { TokenData } from './dataclasses' import { SkaleNetwork } from './interfaces' import { MAINNET_CHAIN_NAME } from './constants' -import { CHAINS_META } from './helper' + +import mainnetMeta from '../meta/mainnet/chains.json' +import stagingMeta from '../meta/staging/chains.json' +import legacyMeta from '../meta/legacy/chains.json' +import regressionMeta from '../meta/regression/chains.json' import * as MAINNET_CHAIN_ICONS from '../meta/mainnet/icons' import * as STAGING_CHAIN_ICONS from '../meta/staging/icons' @@ -40,6 +44,43 @@ const CHAIN_ICONS = { regression: REGRESSION_CHAIN_ICONS } +export const CHAINS_META = { + mainnet: mainnetMeta, + staging: stagingMeta, + legacy: legacyMeta, + regression: regressionMeta +} + + +export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { + if (chainName === MAINNET_CHAIN_NAME) { + if (skaleNetwork != MAINNET_CHAIN_NAME) { + const network = skaleNetwork === 'staging' ? 'Goerli' : skaleNetwork + return `Ethereum (${network})` + } + return 'Ethereum' + } + if (CHAINS_META[skaleNetwork] && CHAINS_META[skaleNetwork][chainName]) { + if ( + app && + CHAINS_META[skaleNetwork][chainName].apps && + CHAINS_META[skaleNetwork][chainName].apps[app] + ) { + return CHAINS_META[skaleNetwork][chainName].apps[app].alias + } + return CHAINS_META[skaleNetwork][chainName].alias + } + return chainName +} + + +export function getChainAppsMeta(chainName: string, skaleNetwork: SkaleNetwork) { + if (CHAINS_META[skaleNetwork][chainName] && CHAINS_META[skaleNetwork][chainName].apps) { + return CHAINS_META[skaleNetwork][chainName].apps + } +} + + export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: string) { if (!name) return let filename = name.toLowerCase() @@ -53,6 +94,7 @@ export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: st } } + export function chainBg(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { if (CHAINS_META[skaleNetwork][chainName]) { if (app && CHAINS_META[skaleNetwork][chainName]['apps'][app]) { @@ -69,6 +111,7 @@ export function chainBg(skaleNetwork: SkaleNetwork, chainName: string, app?: str return 'linear-gradient(273.67deg, rgb(47 50 80), rgb(39 43 68))' } + export function tokenIcon(tokenSymbol: string) { if (!tokenSymbol) return const key = tokenSymbol.toLowerCase() @@ -79,6 +122,7 @@ export function tokenIcon(tokenSymbol: string) { } } + export function getTokenName(token: TokenData): string { return token.meta.name ?? token.meta.symbol } diff --git a/src/core/wagmi_network.ts b/src/core/wagmi_network.ts index 2c89ef8..f817485 100644 --- a/src/core/wagmi_network.ts +++ b/src/core/wagmi_network.ts @@ -25,7 +25,7 @@ import { Chain } from 'wagmi' import { getSChainEndpoint } from './network' import { getExplorerUrl } from './explorer' -import { getChainAlias } from './helper' +import { getChainAlias } from './metadata' import { getChainId } from './chain_id' import { SkaleNetwork } from './interfaces' diff --git a/src/index.ts b/src/index.ts index db94f55..69e38f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,7 +30,7 @@ import WrappedTokens from './components/WrappedTokens' import HistoryButton from './components/HistoryButton' import TransactionsHistory from './components/TransactionsHistory' -import { CHAINS_META, getChainAlias } from './core/helper' +import { CHAINS_META, getChainAlias } from './core/metadata' import { cls, styles, cmn } from './core/css' import MetaportCore from './core/metaport' import { chainBg } from './core/metadata' From 08042cbdbc5593c89c0c06268f16f7736e171609 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 15 Sep 2023 17:10:28 +0100 Subject: [PATCH 053/110] Restructure imports --- package.json | 3 +- src/Metaport.tsx | 49 +------------- src/components/AmountInput/AmountInput.tsx | 2 - src/components/CommunityPool.tsx | 2 +- src/components/DestTokenBalance.tsx | 2 +- src/components/ErrorMessage.tsx | 1 - src/components/Metaport/Metaport.stories.tsx | 1 - src/components/SFuelWarning.tsx | 2 +- src/components/SkConnect.tsx | 1 - src/components/SkPaper.tsx | 2 +- src/components/SkeletonLoader.tsx | 1 - src/components/Stepper/SkStepper.tsx | 1 - src/components/SwitchDirection.tsx | 2 +- .../{TokenList => }/TokenBalance.tsx | 5 +- src/components/TokenIcon.tsx | 1 - src/components/{TokenList => }/TokenList.tsx | 16 ++--- src/components/TokenList/TokenList.scss | 15 ----- src/components/TokenList/index.ts | 4 -- .../TokenListSection.tsx | 13 ++-- .../TokenListSection/TokenListSection.scss | 17 ----- src/components/TokenListSection/index.ts | 1 - src/components/TransactionsHistory.tsx | 4 +- src/components/WidgetBody.tsx | 5 +- src/components/WidgetUI/WidgetUI.tsx | 9 +-- src/components/WrappedTokens.tsx | 2 +- src/core/actions/action.ts | 2 +- src/core/community_pool.ts | 2 +- src/core/events.ts | 64 +------------------ src/core/faucet.ts | 4 +- src/core/fee_calculator.ts | 3 - src/core/helper.ts | 2 - src/core/metadata.ts | 6 -- src/core/metaport.ts | 3 +- src/core/miner.ts | 2 +- src/icons/sberbank.svg | 1 - src/index.ts | 8 ++- tsconfig.json | 1 + 37 files changed, 44 insertions(+), 215 deletions(-) rename src/components/{TokenList => }/TokenBalance.tsx (90%) rename src/components/{TokenList => }/TokenList.tsx (90%) delete mode 100644 src/components/TokenList/TokenList.scss delete mode 100644 src/components/TokenList/index.ts rename src/components/{TokenListSection => }/TokenListSection.tsx (85%) delete mode 100644 src/components/TokenListSection/TokenListSection.scss delete mode 100644 src/components/TokenListSection/index.ts delete mode 100644 src/icons/sberbank.svg diff --git a/package.json b/package.json index 0012981..126d9ec 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,7 @@ "lint": "eslint --ext .js,.jsx,.ts,.tsx --fix", "prettier": "prettier --write \"src/**/*.{ts,tsx,js,mdx}\"", "test": "vitest", - "test:cov": "vitest run --coverage", - "dprepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies\"" + "test:cov": "vitest run --coverage" }, "devDependencies": { "@storybook/addon-essentials": "7.4.0", diff --git a/src/Metaport.tsx b/src/Metaport.tsx index 97751fd..9686f9d 100644 --- a/src/Metaport.tsx +++ b/src/Metaport.tsx @@ -24,32 +24,7 @@ // @ts-ignore import React from 'react' // import { createRoot } from 'react-dom/client'; - -import { internalEvents } from './core/events' - -import * as interfaces from './core/interfaces/index' -export * as dataclasses from './core/dataclasses/index' -export * as interfaces from './core/interfaces/index' - -import ChainIcon from './components/ChainIcon' -export { ChainIcon } - -import WidgetUI from './components/WidgetUI' -export { WidgetUI } - -import Metaport from './components/Metaport' -export { Metaport } - -import MetaportProvider from './components/MetaportProvider' -export { MetaportProvider } - -import SkPaper from './components/SkPaper' -export { SkPaper } - -import SkConnect from './components/SkConnect' -export { SkConnect } - -// export * as sfuel from './core/sfuel'; +import * as interfaces from './core/interfaces' export class InjectedMetaport { constructor(config: interfaces.MetaportConfig) { @@ -64,26 +39,4 @@ export class InjectedMetaport { console.log('div with id="metaport" does not exist') } } - - transfer(params: interfaces.TransferParams): void { - internalEvents.transfer(params) - } - // wrap(params) { internalEvents.wrap(params) } - // unwrap(params) { internalEvents.unwrap(params) } - // swap(params) { internalEvents.swap(params) } - - // updateParams(params) { internalEvents.updateParams(params) } - // requestBalance(params) { internalEvents.requestBalance(params) } - setTheme(theme: any) { - internalEvents.setTheme(theme) - } - close() { - internalEvents.close() - } - open() { - internalEvents.open() - } - reset() { - internalEvents.reset() - } } diff --git a/src/components/AmountInput/AmountInput.tsx b/src/components/AmountInput/AmountInput.tsx index 440cb33..0ec8b89 100644 --- a/src/components/AmountInput/AmountInput.tsx +++ b/src/components/AmountInput/AmountInput.tsx @@ -12,8 +12,6 @@ import { useCollapseStore } from '../../store/Store' export default function AmountInput() { const { address } = useAccount() - - const token = useMetaportStore((state) => state.token) const transferInProgress = useMetaportStore((state) => state.transferInProgress) const setAmount = useMetaportStore((state) => state.setAmount) const amount = useMetaportStore((state) => state.amount) diff --git a/src/components/CommunityPool.tsx b/src/components/CommunityPool.tsx index be5b89a..c2233c1 100644 --- a/src/components/CommunityPool.tsx +++ b/src/components/CommunityPool.tsx @@ -34,7 +34,7 @@ import TextField from '@mui/material/TextField' import localStyles from './AmountInput/AmountInput.module.scss' import SkPaper from './SkPaper' -import { TokenBalance } from './TokenList' +import TokenBalance from './TokenBalance' import Button from '@mui/material/Button' diff --git a/src/components/DestTokenBalance.tsx b/src/components/DestTokenBalance.tsx index b7b9818..1b7f44c 100644 --- a/src/components/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { useAccount } from 'wagmi' -import { TokenBalance } from './TokenList' +import TokenBalance from './TokenBalance' import { useMetaportStore } from '../store/MetaportStore' import { BALANCE_UPDATE_INTERVAL_MS } from '../core/constants' diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx index 0fd6333..af3c9b4 100644 --- a/src/components/ErrorMessage.tsx +++ b/src/components/ErrorMessage.tsx @@ -1,4 +1,3 @@ -import React from 'react' import Button from '@mui/material/Button' import { cls, cmn, styles } from '../core/css' diff --git a/src/components/Metaport/Metaport.stories.tsx b/src/components/Metaport/Metaport.stories.tsx index b339ee6..8b2a84e 100644 --- a/src/components/Metaport/Metaport.stories.tsx +++ b/src/components/Metaport/Metaport.stories.tsx @@ -7,7 +7,6 @@ METAPORT_CONFIG.mainnetEndpoint = import.meta.env.VITE_MAINNET_ENDPOINT const meta: Meta = { title: 'Functional/Metaport', component: Metaport - // decorators: [storyDecorator], } export default meta diff --git a/src/components/SFuelWarning.tsx b/src/components/SFuelWarning.tsx index fe5e320..0d3e1d1 100644 --- a/src/components/SFuelWarning.tsx +++ b/src/components/SFuelWarning.tsx @@ -21,7 +21,7 @@ * @copyright SKALE Labs 2023-Present */ -import React, { useEffect } from 'react' +import { useEffect } from 'react' import debug from 'debug' import { useAccount } from 'wagmi' diff --git a/src/components/SkConnect.tsx b/src/components/SkConnect.tsx index 0e1dc37..89b93e7 100644 --- a/src/components/SkConnect.tsx +++ b/src/components/SkConnect.tsx @@ -20,7 +20,6 @@ * @copyright SKALE Labs 2023-Present */ -import React from 'react' import { ConnectButton } from '@rainbow-me/rainbowkit' import Jazzicon, { jsNumberForAddress } from 'react-jazzicon' diff --git a/src/components/SkPaper.tsx b/src/components/SkPaper.tsx index 0e88319..ea74bc1 100644 --- a/src/components/SkPaper.tsx +++ b/src/components/SkPaper.tsx @@ -21,7 +21,7 @@ * @copyright SKALE Labs 2023-Present */ -import React, { ReactElement } from 'react' +import { ReactElement } from 'react' import { cls, cmn, styles } from '../core/css' import { useUIStore } from '../store/Store' diff --git a/src/components/SkeletonLoader.tsx b/src/components/SkeletonLoader.tsx index bb91a3b..add147c 100644 --- a/src/components/SkeletonLoader.tsx +++ b/src/components/SkeletonLoader.tsx @@ -1,4 +1,3 @@ -import React from 'react' import Skeleton from '@mui/material/Skeleton' export default function SkeletonLoader(props) { diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index ecef961..fd64d3c 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -11,7 +11,6 @@ import LoadingButton from '@mui/lab/LoadingButton' import Collapse from '@mui/material/Collapse' import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded' -import TollIcon from '@mui/icons-material/Toll' import { getRandom } from '../../core/helper' import { getChainAlias } from '../../core/metadata' diff --git a/src/components/SwitchDirection.tsx b/src/components/SwitchDirection.tsx index a5500ee..41ec161 100644 --- a/src/components/SwitchDirection.tsx +++ b/src/components/SwitchDirection.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react' +import { useRef } from 'react' import IconButton from '@mui/material/IconButton' import ArrowDownwardRoundedIcon from '@mui/icons-material/ArrowDownwardRounded' diff --git a/src/components/TokenList/TokenBalance.tsx b/src/components/TokenBalance.tsx similarity index 90% rename from src/components/TokenList/TokenBalance.tsx rename to src/components/TokenBalance.tsx index 8d62ae0..29f83e4 100644 --- a/src/components/TokenList/TokenBalance.tsx +++ b/src/components/TokenBalance.tsx @@ -1,7 +1,6 @@ -import React from 'react' import { formatUnits } from 'ethers' -import { cls, cmn } from '../../core/css' -import { DEFAULT_ERC20_DECIMALS } from '../../core/constants' +import { cls, cmn } from '../core/css' +import { DEFAULT_ERC20_DECIMALS } from '../core/constants' function formatBalance(balance: bigint, decimals?: string): string { const tokenDecimals = decimals ?? DEFAULT_ERC20_DECIMALS diff --git a/src/components/TokenIcon.tsx b/src/components/TokenIcon.tsx index 72b2e3e..0c4813d 100644 --- a/src/components/TokenIcon.tsx +++ b/src/components/TokenIcon.tsx @@ -21,7 +21,6 @@ * @copyright SKALE Labs 2023-Present */ -import React from 'react' import TollRoundedIcon from '@mui/icons-material/TollRounded' import { tokenIcon } from '../core/metadata' diff --git a/src/components/TokenList/TokenList.tsx b/src/components/TokenList.tsx similarity index 90% rename from src/components/TokenList/TokenList.tsx rename to src/components/TokenList.tsx index ff9fb34..5ba2a13 100644 --- a/src/components/TokenList/TokenList.tsx +++ b/src/components/TokenList.tsx @@ -9,17 +9,17 @@ import AccordionSummary from '@mui/material/AccordionSummary' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { getAvailableTokensTotal, getDefaultToken } from '../../core/tokens/helper' +import { getAvailableTokensTotal, getDefaultToken } from '../core/tokens/helper' -import { cls, cmn, styles } from '../../core/css' +import { cls, cmn, styles } from '../core/css' -import TokenListSection from '../TokenListSection' -import TokenIcon from '../TokenIcon' +import TokenListSection from './TokenListSection' +import TokenIcon from './TokenIcon' -import { useCollapseStore } from '../../store/Store' -import { useMetaportStore } from '../../store/MetaportStore' -import { TokenType } from '../../core/dataclasses' -import { BALANCE_UPDATE_INTERVAL_MS } from '../../core/constants' +import { useCollapseStore } from '../store/Store' +import { useMetaportStore } from '../store/MetaportStore' +import { TokenType } from '../core/dataclasses' +import { BALANCE_UPDATE_INTERVAL_MS } from '../core/constants' export default function TokenList() { const token = useMetaportStore((state) => state.token) diff --git a/src/components/TokenList/TokenList.scss b/src/components/TokenList/TokenList.scss deleted file mode 100644 index a93d6e6..0000000 --- a/src/components/TokenList/TokenList.scss +++ /dev/null @@ -1,15 +0,0 @@ - - -.iconToken { - width: 22px; - height: 22px; -} - -.default-iconToken { - color: black; - background-color: #dbe106; - padding: 4px; - border-radius: 50%; - width: 22px; - height: 22px; -} \ No newline at end of file diff --git a/src/components/TokenList/index.ts b/src/components/TokenList/index.ts deleted file mode 100644 index a555ff1..0000000 --- a/src/components/TokenList/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default } from './TokenList' - -import TokenBalance from './TokenBalance' -export { TokenBalance } diff --git a/src/components/TokenListSection/TokenListSection.tsx b/src/components/TokenListSection.tsx similarity index 85% rename from src/components/TokenListSection/TokenListSection.tsx rename to src/components/TokenListSection.tsx index 78aa0ee..29cea40 100644 --- a/src/components/TokenListSection/TokenListSection.tsx +++ b/src/components/TokenListSection.tsx @@ -1,14 +1,13 @@ -import React from 'react' import Button from '@mui/material/Button' -import { TokenData, TokenType } from '../../core/dataclasses' -import { TokenBalancesMap, TokenDataMap } from '../../core/interfaces' -import { cls, cmn } from '../../core/css' +import { TokenData, TokenType } from '../core/dataclasses' +import { TokenBalancesMap, TokenDataMap } from '../core/interfaces' +import { cls, cmn } from '../core/css' -import TokenBalance from '../TokenList/TokenBalance' -import TokenIcon from '../TokenIcon' +import TokenBalance from './TokenBalance' +import TokenIcon from './TokenIcon' -import { getTokenName } from '../../core/metadata' +import { getTokenName } from '../core/metadata' export default function TokenListSection(props: { setExpanded: (expanded: string | false) => void diff --git a/src/components/TokenListSection/TokenListSection.scss b/src/components/TokenListSection/TokenListSection.scss deleted file mode 100644 index 0377d29..0000000 --- a/src/components/TokenListSection/TokenListSection.scss +++ /dev/null @@ -1,17 +0,0 @@ - - -.iconToken { - width: 21px; - height: 21px; - margin-left: 2px; - margin-right: 2px; -} - -.default-iconToken { - color: black; - background-color: #dbe106; - padding: 4px; - border-radius: 50%; - width: 22px; - height: 22px; -} \ No newline at end of file diff --git a/src/components/TokenListSection/index.ts b/src/components/TokenListSection/index.ts deleted file mode 100644 index 76e5a12..0000000 --- a/src/components/TokenListSection/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './TokenListSection' diff --git a/src/components/TransactionsHistory.tsx b/src/components/TransactionsHistory.tsx index 6ff33b6..579e418 100644 --- a/src/components/TransactionsHistory.tsx +++ b/src/components/TransactionsHistory.tsx @@ -31,14 +31,16 @@ import CloseRoundedIcon from '@mui/icons-material/CloseRounded' import TokenIcon from './TokenIcon' import ChainIcon from './ChainIcon' import TransactionData from './TransactionData' +import SkPaper from './SkPaper' import { useMetaportStore } from '../store/MetaportStore' import { useCollapseStore } from '../store/Store' import { cls, styles, cmn } from '../core/css' -import { interfaces, SkPaper } from '../Metaport' import { getChainAlias } from '../core/metadata' +import * as interfaces from '../core/interfaces' + export default function TransactionsHistory() { const transactionsHistory = useMetaportStore((state) => state.transactionsHistory) const transfersHistory = useMetaportStore((state) => state.transfersHistory) diff --git a/src/components/WidgetBody.tsx b/src/components/WidgetBody.tsx index 949bca2..d396138 100644 --- a/src/components/WidgetBody.tsx +++ b/src/components/WidgetBody.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import { useEffect } from 'react' import { useAccount } from 'wagmi' import { useCollapseStore } from '../store/Store' @@ -12,7 +12,7 @@ import SkStepper from './Stepper' import SkPaper from './SkPaper' import AmountErrorMessage from './AmountErrorMessage' import SwitchDirection from './SwitchDirection' -import { TokenBalance } from './TokenList' +import TokenBalance from './TokenBalance' import DestTokenBalance from './DestTokenBalance' import ErrorMessage from './ErrorMessage' import CommunityPool from './CommunityPool' @@ -59,7 +59,6 @@ export function WidgetBody(props) { const tokenBalances = useMetaportStore((state) => state.tokenBalances) const errorMessage = useMetaportStore((state) => state.errorMessage) - const loading = useMetaportStore((state) => state.loading) const transferInProgress = useMetaportStore((state) => state.transferInProgress) diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index c241a6a..38a094a 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -20,10 +20,7 @@ * @copyright SKALE Labs 2023-Present */ -import React, { useEffect } from 'react' -import { StyledEngineProvider } from '@mui/material/styles' - -import { useAccount } from 'wagmi' +import React from 'react' import Collapse from '@mui/material/Collapse' import Fab from '@mui/material/Fab' @@ -39,7 +36,6 @@ import WidgetBody from '../WidgetBody' import { cls, cmn, styles } from '../../core/css' -import SkConnect from '../SkConnect' import ErrorMessage from '../ErrorMessage' import { MetaportConfig } from '../../core/interfaces' @@ -47,9 +43,6 @@ export function WidgetUI(props: { config: MetaportConfig }) { const metaportTheme = useUIStore((state) => state.theme) const isOpen = useUIStore((state) => state.open) const setOpen = useUIStore((state) => state.setOpen) - - const { address } = useAccount() - const errorMessage = useMetaportStore((state) => state.errorMessage) const handleClick = (_: React.MouseEvent) => { diff --git a/src/components/WrappedTokens.tsx b/src/components/WrappedTokens.tsx index 9668805..fb26b7a 100644 --- a/src/components/WrappedTokens.tsx +++ b/src/components/WrappedTokens.tsx @@ -34,7 +34,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import ErrorIcon from '@mui/icons-material/Error' import SkPaper from './SkPaper' -import { TokenBalance } from './TokenList' +import TokenBalance from './TokenBalance' import TokenIcon from './TokenIcon' import { getTokenName, getChainAlias } from '../core/metadata' diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index b6fc0e9..4a72f92 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -29,6 +29,7 @@ import { Contract, Provider } from 'ethers' import { MainnetChain, SChain } from '@skalenetwork/ima-js' import { TokenData, CustomAbiTokenType } from '../dataclasses' +import * as interfaces from '../interfaces' import MetaportCore, { createTokenData } from '../metaport' import { externalEvents } from '../events' import { toWei } from '../convertation' @@ -39,7 +40,6 @@ import { IMA_ABIS } from '../contracts' import { isMainnetChainId, getMainnetAbi } from '../network' import { walletClientToSigner } from '../ethers' -import { interfaces } from '../../Metaport' debug.enable('*') const log = debug('metaport:actions') diff --git a/src/core/community_pool.ts b/src/core/community_pool.ts index 7666340..3c04413 100644 --- a/src/core/community_pool.ts +++ b/src/core/community_pool.ts @@ -41,7 +41,7 @@ import { BALANCE_UPDATE_INTERVAL_MS } from './constants' import { delay } from './helper' -import { CHAIN_IDS, isMainnetChainId, getMainnetAbi } from './network' +import { CHAIN_IDS, getMainnetAbi } from './network' import MetaportCore from './metaport' import * as dataclasses from '../core/dataclasses' diff --git a/src/core/events.ts b/src/core/events.ts index a5023c4..1002fe9 100644 --- a/src/core/events.ts +++ b/src/core/events.ts @@ -85,66 +85,4 @@ export namespace externalEvents { } } -export namespace internalEvents { - export function updateParams(params) { - dispatchEvent('_metaport_updateParams', { - tokens: params.tokens, - chains: params.chains - }) - } - - export function transfer(params: interfaces.TransferParams): void { - dispatchEvent('_metaport_transfer', { - params: params - }) - } - - export function wrap(params) { - dispatchEvent('_metaport_wrap', { - amount: params.amount, - chain: params.chain, - tokens: params.tokens - }) - } - - export function unwrap(params) { - dispatchEvent('_metaport_unwrap', { - amount: params.amount, - chain: params.chain, - tokens: params.tokens - }) - } - - export function swap(params) { - dispatchEvent('_metaport_swap', { - amount: params.amount, - chain: params.chain, - tokens: params.tokens // todo! - }) - } - - export function close() { - dispatchEvent('_metaport_close') - } - - export function open() { - dispatchEvent('_metaport_open') - } - - export function reset() { - dispatchEvent('_metaport_reset') - } - - export function requestBalance(params) { - dispatchEvent('_metaport_requestBalance', { - schainName: params.schainName, - tokenSymbol: params.tokenSymbol - }) - } - - export function setTheme(theme) { - dispatchEvent('_metaport_setTheme', { - theme: theme - }) - } -} +export namespace internalEvents {} diff --git a/src/core/faucet.ts b/src/core/faucet.ts index 93ba4f7..f51fe27 100644 --- a/src/core/faucet.ts +++ b/src/core/faucet.ts @@ -21,11 +21,11 @@ * @copyright SKALE Labs 2023-Present */ -import { Provider, Wallet, JsonRpcProvider, AbiCoder, TransactionResponse } from 'ethers' +import { Wallet, JsonRpcProvider, AbiCoder, TransactionResponse } from 'ethers' import SkalePowMiner from './miner' import { ZERO_ADDRESS, ZERO_FUNCSIG, FAUCET_DATA } from './constants' -import { AddressType, SkaleNetwork } from './interfaces' +import { AddressType } from './interfaces' import MetaportCore from './metaport' function getAddress(chainName: string, skaleNetwork: string) { diff --git a/src/core/fee_calculator.ts b/src/core/fee_calculator.ts index ab9d6c4..6eeeaeb 100644 --- a/src/core/fee_calculator.ts +++ b/src/core/fee_calculator.ts @@ -21,13 +21,10 @@ * @copyright SKALE Labs 2022-Present */ -import debug from 'debug' import { fromWei } from './convertation' import { CoinGeckoClient } from 'coingecko-api-v3' import { DEFAULT_ERC20_DECIMALS } from './constants' -debug.enable('*') -const log = debug('metaport:components:fee_calculator') export async function getTransactionFee(): Promise { // todo: get actual gas limit for transfer diff --git a/src/core/helper.ts b/src/core/helper.ts index ac29597..eedd40b 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -24,9 +24,7 @@ import { getAddress } from 'ethers' import { MAINNET_CHAIN_NAME } from './constants' -// import utils from 'web3-utils'; import { TransferRequestStatus } from './dataclasses' -import { SkaleNetwork } from './interfaces' export function eqArrays(arr1, arr2) { return JSON.stringify(arr1) === JSON.stringify(arr2) diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 393d6c9..89b5329 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -51,7 +51,6 @@ export const CHAINS_META = { regression: regressionMeta } - export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { if (chainName === MAINNET_CHAIN_NAME) { if (skaleNetwork != MAINNET_CHAIN_NAME) { @@ -73,14 +72,12 @@ export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app return chainName } - export function getChainAppsMeta(chainName: string, skaleNetwork: SkaleNetwork) { if (CHAINS_META[skaleNetwork][chainName] && CHAINS_META[skaleNetwork][chainName].apps) { return CHAINS_META[skaleNetwork][chainName].apps } } - export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: string) { if (!name) return let filename = name.toLowerCase() @@ -94,7 +91,6 @@ export function chainIconPath(skaleNetwork: SkaleNetwork, name: string, app?: st } } - export function chainBg(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { if (CHAINS_META[skaleNetwork][chainName]) { if (app && CHAINS_META[skaleNetwork][chainName]['apps'][app]) { @@ -111,7 +107,6 @@ export function chainBg(skaleNetwork: SkaleNetwork, chainName: string, app?: str return 'linear-gradient(273.67deg, rgb(47 50 80), rgb(39 43 68))' } - export function tokenIcon(tokenSymbol: string) { if (!tokenSymbol) return const key = tokenSymbol.toLowerCase() @@ -122,7 +117,6 @@ export function tokenIcon(tokenSymbol: string) { } } - export function getTokenName(token: TokenData): string { return token.meta.name ?? token.meta.symbol } diff --git a/src/core/metaport.ts b/src/core/metaport.ts index 209720c..158bc57 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -40,7 +40,6 @@ import { ERC_ABIS } from './contracts' import debug from 'debug' import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import { interfaces } from '../Metaport' import { MAINNET_CHAIN_NAME } from './constants' const log = debug('ima:test:MainnetChain') @@ -118,7 +117,7 @@ export function createWrappedTokensMap( return wrappedTokens } -const findFirstWrapperAddress = (token: interfaces.Token): `0x${string}` | null => +const findFirstWrapperAddress = (token: Token): `0x${string}` | null => Object.values(token.chains).find((chain) => 'wrapper' in chain)?.wrapper || null export const findFirstWrapperChainName = (token: TokenData): string | null => { diff --git a/src/core/miner.ts b/src/core/miner.ts index abb4fcd..7a8ba21 100644 --- a/src/core/miner.ts +++ b/src/core/miner.ts @@ -21,7 +21,7 @@ * @copyright SKALE Labs 2023-Present */ -import { isHexString, getNumber, randomBytes, keccak256, hexlify, toBeHex, toBigInt } from 'ethers' +import { isHexString, getNumber, randomBytes, keccak256, toBeHex, toBigInt } from 'ethers' import { MAX_NUMBER } from './constants' interface Params { diff --git a/src/icons/sberbank.svg b/src/icons/sberbank.svg deleted file mode 100644 index 007e4c1..0000000 --- a/src/icons/sberbank.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 69e38f2..bdc027e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ -export { interfaces, dataclasses } from './Metaport' +import * as interfaces from './core/interfaces' +import * as dataclasses from './core/dataclasses' export { useMetaportStore } from './store/MetaportStore' export { type MetaportState } from './store/MetaportState' @@ -15,7 +16,8 @@ import ChainIcon from './components/ChainIcon' import TokenIcon from './components/TokenIcon' import ChainsList from './components/ChainsList' -import TokenList, { TokenBalance } from './components/TokenList' +import TokenList from './components/TokenList' +import TokenBalance from './components/TokenBalance' import AmountInput from './components/AmountInput' import SwitchDirection from './components/SwitchDirection' import SkStepper from './components/Stepper' @@ -69,6 +71,8 @@ export { cls, styles, cmn, + interfaces, + dataclasses, getMetaportTheme, useWagmiAccount, PROXY_ENDPOINTS, diff --git a/tsconfig.json b/tsconfig.json index ad5b433..2238d89 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, + "noUnusedLocals": true, "jsx": "react-jsx", "typeRoots": ["./src/vite-env.d.ts", "./src/types", "./node_modules/@types"], }, From 2c478696ff8d38df4c00fe29f07112382dc5f80b Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 20 Sep 2023 17:05:48 +0100 Subject: [PATCH 054/110] Update submodules --- .gitmodules | 1 + helper-scripts | 2 +- skale-network | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 3aea469..5343ec4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,4 @@ [submodule "skale-network"] path = skale-network url = https://github.com/skalenetwork/skale-network.git + branch = add-additional-metadata diff --git a/helper-scripts b/helper-scripts index e9de03b..45d533e 160000 --- a/helper-scripts +++ b/helper-scripts @@ -1 +1 @@ -Subproject commit e9de03b8ec07223e0d28313353ab1aa8c0219586 +Subproject commit 45d533ea5d3895ce79ebc275fa1cbeab11fe5036 diff --git a/skale-network b/skale-network index 5b00cae..d49a336 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 5b00cae6736322f302c3a75331b19ee02b0a3596 +Subproject commit d49a33606bebe32e7803ba732aacba70a4ee4a21 From e51339a12df1ede4e6aa266f12b51f1f32f32a37 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 21 Sep 2023 19:40:26 +0100 Subject: [PATCH 055/110] Minor changes --- .github/workflows/test.yml | 3 --- skale-network | 2 +- src/components/ChainsList.tsx | 3 --- src/components/TokenList.tsx | 4 ---- src/components/WidgetBody.tsx | 4 ++-- src/core/interfaces/Config.ts | 2 +- src/index.ts | 3 +++ src/styles/cmn.module.scss | 15 ++++++++++++++- 8 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index afac8d6..377cd5e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,9 +17,6 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - name: Update submodules - run: | - git submodule update --remote - name: Prepare metadata run: | bash prepare_meta.sh diff --git a/skale-network b/skale-network index d49a336..8c27b43 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit d49a33606bebe32e7803ba732aacba70a4ee4a21 +Subproject commit 8c27b4390bc22beaabe4107bea9b5e1f8e0f79de diff --git a/src/components/ChainsList.tsx b/src/components/ChainsList.tsx index 7756bb8..d41734a 100644 --- a/src/components/ChainsList.tsx +++ b/src/components/ChainsList.tsx @@ -138,9 +138,6 @@ export default function ChainsList(props: { className={cls(cmn.chainsList, cmn.mbott10, cmn.mri10)} style={{ marginLeft: '8px' }} > - {/*
- -
*/} {schainNames.map((name) => (
diff --git a/src/components/WidgetBody.tsx b/src/components/WidgetBody.tsx index d396138..76aed1e 100644 --- a/src/components/WidgetBody.tsx +++ b/src/components/WidgetBody.tsx @@ -151,7 +151,7 @@ export function WidgetBody(props) {
-

From

+

From {appName1 ? 'app' : ''}

{token ? (
-

To

+

To {appName2 ? 'app' : ''}

Date: Fri, 22 Sep 2023 19:29:35 +0100 Subject: [PATCH 056/110] Update WS endpoint func, general cleanup --- src/components/AmountErrorMessage.tsx | 2 +- .../{AmountInput => }/AmountInput.tsx | 11 ++-- .../AmountInput/AmountInput.module.scss | 51 ------------------- src/components/AmountInput/index.ts | 1 - src/components/ChainApps.tsx | 10 ++-- src/components/ChainIcon.tsx | 5 +- src/components/ChainsList.tsx | 2 +- src/components/CommunityPool.tsx | 10 ++-- src/components/ErrorMessage.tsx | 4 +- src/components/MetaportProvider.tsx | 4 +- src/components/SFuelWarning.tsx | 2 - src/components/SkConnect.tsx | 2 - src/components/Stepper/SkStepper.module.scss | 3 -- src/components/Stepper/SkStepper.tsx | 2 - src/components/SwitchDirection.tsx | 40 ++++++++------- src/components/TransferETF.tsx | 3 -- src/components/WidgetBody.tsx | 8 ++- src/core/chain_id.ts | 2 - src/core/community_pool.ts | 3 -- src/core/constants.ts | 14 ++--- src/core/dataclasses/EthTokenData.ts | 45 ---------------- src/core/fee_calculator.ts | 1 - src/core/interfaces/TokenDataMap.ts | 6 +-- src/core/metaport.ts | 2 +- src/core/wagmi_network.ts | 7 ++- src/metadata/metaportConfigStaging.ts | 38 +------------- src/styles/styles.module.scss | 33 ++++++++++++ 27 files changed, 96 insertions(+), 215 deletions(-) rename src/components/{AmountInput => }/AmountInput.tsx (79%) delete mode 100644 src/components/AmountInput/AmountInput.module.scss delete mode 100644 src/components/AmountInput/index.ts delete mode 100644 src/core/dataclasses/EthTokenData.ts diff --git a/src/components/AmountErrorMessage.tsx b/src/components/AmountErrorMessage.tsx index f7a4627..1231aca 100644 --- a/src/components/AmountErrorMessage.tsx +++ b/src/components/AmountErrorMessage.tsx @@ -7,7 +7,7 @@ import { useMetaportStore } from '../store/MetaportStore' export default function AmountErrorMessage() { const amountErrorMessage = useMetaportStore((state) => state.amountErrorMessage) return ( - +

+

{expandedTokens ? null : (
@@ -200,7 +147,7 @@ export default function ChainsList(props: { size={size} />
- +
))}
diff --git a/src/components/CommunityPool.tsx b/src/components/CommunityPool.tsx index 86186c3..214014a 100644 --- a/src/components/CommunityPool.tsx +++ b/src/components/CommunityPool.tsx @@ -28,7 +28,6 @@ import { useAccount, useWalletClient, useSwitchNetwork } from 'wagmi' import Accordion from '@mui/material/Accordion' import AccordionSummary from '@mui/material/AccordionSummary' import AccordionDetails from '@mui/material/AccordionDetails' -import Grid from '@mui/material/Grid' import TextField from '@mui/material/TextField' import SkPaper from './SkPaper' @@ -217,70 +216,66 @@ export default function CommunityPool() { />
- - - -
-
- -
-

- ETH -

-
-
-
- + +
+
+
-
- -
- - +

+ ETH +

+
+
+
+ +
+
+ +
diff --git a/src/components/History.tsx b/src/components/History.tsx new file mode 100644 index 0000000..b11aab2 --- /dev/null +++ b/src/components/History.tsx @@ -0,0 +1,186 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file History.ts + * @copyright SKALE Labs 2023-Present + */ + +import ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded' + +import TokenIcon from './TokenIcon' +import TransactionData from './TransactionData' +import SkPaper from './SkPaper' +import Chain from './Chain' + +import { useMetaportStore } from '../store/MetaportStore' +import { cls, cmn, styles } from '../core/css' + +import * as interfaces from '../core/interfaces' + +export default function History(props: { size?: interfaces.SimplifiedSize }) { + const transactionsHistory = useMetaportStore((state) => state.transactionsHistory) + const transfersHistory = useMetaportStore((state) => state.transfersHistory) + + const mpc = useMetaportStore((state) => state.mpc) + + const size = props.size ?? 'sm' + const network = mpc.config.skaleNetwork + + if (transactionsHistory.length === 0 && transfersHistory.length === 0) return + return ( +
+ {transactionsHistory.length !== 0 ? ( + +

+ Current transfer +

+ + {transactionsHistory.map((transactionData: interfaces.TransactionHistory) => ( + + ))} + +
+ ) : null} +
+ {transfersHistory.map((transfer: interfaces.TransferHistory, key: number) => ( + +
+
+ + + +
+
+ +
+
+ +
+

+ {transfer.amount} {transfer.tokenKeyname} +

+

+ •{' '} + {transfer.address.substring(0, 6) + + '...' + + transfer.address.substring(transfer.address.length - 4)} +

+
+
+ + + {transfer.transactions.map((transactionData: interfaces.TransactionHistory) => ( + + ))} + +
+ ))} +
+
+ ) +} diff --git a/src/components/HistorySection.tsx b/src/components/HistorySection.tsx new file mode 100644 index 0000000..118ea98 --- /dev/null +++ b/src/components/HistorySection.tsx @@ -0,0 +1,77 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file HistorySection.ts + * @copyright SKALE Labs 2023-Present + */ + +import { Collapse } from '@mui/material' +import Button from '@mui/material/Button' + +import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded' +import CloseRoundedIcon from '@mui/icons-material/CloseRounded' + +import History from './History' + +import { useMetaportStore } from '../store/MetaportStore' +import { useCollapseStore } from '../store/Store' +import { cls, styles, cmn } from '../core/css' + +export default function TransactionsHistory() { + const transactionsHistory = useMetaportStore((state) => state.transactionsHistory) + const transfersHistory = useMetaportStore((state) => state.transfersHistory) + + const clearTransactionsHistory = useMetaportStore((state) => state.clearTransactionsHistory) + const expandedTH = useCollapseStore((state) => state.expandedTH) + const setExpandedTH = useCollapseStore((state) => state.setExpandedTH) + + function clearTransferHistory() { + clearTransactionsHistory() + setExpandedTH(false) + } + + if (transactionsHistory.length === 0 && transfersHistory.length === 0) return + return ( + +
+ + +
+ +
+ ) +} diff --git a/src/components/MetaportProvider.tsx b/src/components/MetaportProvider.tsx index d82327a..0e43de3 100644 --- a/src/components/MetaportProvider.tsx +++ b/src/components/MetaportProvider.tsx @@ -73,15 +73,20 @@ export default function MetaportProvider(props: { ] ) + const wallets = [ + enkryptWallet({ chains }), + injectedWallet({ chains }), + coinbaseWallet({ chains, appName: 'SKALE Metaport' }) + ] + + if (props.config.projectId) { + wallets.push(metaMaskWallet({ chains, projectId: props.config.projectId })) + } + const connectors = connectorsForWallets([ { groupName: 'Supported Wallets', - wallets: [ - metaMaskWallet({ chains, projectId: '' }), - enkryptWallet({ chains }), - injectedWallet({ chains }), - coinbaseWallet({ chains, appName: 'SKALE Metaport' }) - ] + wallets: wallets } ]) diff --git a/src/components/SFuelWarning.tsx b/src/components/SFuelWarning.tsx index a2af73f..c7eb653 100644 --- a/src/components/SFuelWarning.tsx +++ b/src/components/SFuelWarning.tsx @@ -30,6 +30,7 @@ import Button from '@mui/material/Button' import LoadingButton from '@mui/lab/LoadingButton' import { Collapse } from '@mui/material' import LinearProgress from '@mui/material/LinearProgress' +import ArrowOutwardRoundedIcon from '@mui/icons-material/ArrowOutwardRounded' import { BALANCE_UPDATE_INTERVAL_MS, MAINNET_CHAIN_NAME, SFUEL_TEXT } from '../core/constants' import { Station } from '../core/sfuel' @@ -205,6 +206,7 @@ export default function SFuelWarning(props: {}) { {mining ? ( } loadingPosition="start" size="small" variant="contained" diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 1dca463..0c3f916 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -11,6 +11,7 @@ import LoadingButton from '@mui/lab/LoadingButton' import Collapse from '@mui/material/Collapse' import SettingsBackupRestoreRoundedIcon from '@mui/icons-material/SettingsBackupRestoreRounded' +import ArrowOutwardRoundedIcon from '@mui/icons-material/ArrowOutwardRounded' import { getRandom } from '../../core/helper' import { getChainAlias } from '../../core/metadata' @@ -63,8 +64,8 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { {stepsMetadata.map((step, i) => ( -
-
+
+

{step.headline}

} loadingPosition="start" variant="contained" color="primary" diff --git a/src/components/TransactionsHistory.tsx b/src/components/TransactionsHistory.tsx deleted file mode 100644 index 579e418..0000000 --- a/src/components/TransactionsHistory.tsx +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @license - * SKALE Metaport - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -/** - * @file TransactionsHistory.ts - * @copyright SKALE Labs 2023-Present - */ - -import { Collapse } from '@mui/material' -import Button from '@mui/material/Button' - -import ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded' -import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded' -import CloseRoundedIcon from '@mui/icons-material/CloseRounded' - -import TokenIcon from './TokenIcon' -import ChainIcon from './ChainIcon' -import TransactionData from './TransactionData' -import SkPaper from './SkPaper' - -import { useMetaportStore } from '../store/MetaportStore' -import { useCollapseStore } from '../store/Store' -import { cls, styles, cmn } from '../core/css' - -import { getChainAlias } from '../core/metadata' - -import * as interfaces from '../core/interfaces' - -export default function TransactionsHistory() { - const transactionsHistory = useMetaportStore((state) => state.transactionsHistory) - const transfersHistory = useMetaportStore((state) => state.transfersHistory) - - const clearTransactionsHistory = useMetaportStore((state) => state.clearTransactionsHistory) - const mpc = useMetaportStore((state) => state.mpc) - const expandedTH = useCollapseStore((state) => state.expandedTH) - const setExpandedTH = useCollapseStore((state) => state.setExpandedTH) - - function clearTransferHistory() { - clearTransactionsHistory() - setExpandedTH(false) - } - - if (transactionsHistory.length === 0 && transfersHistory.length === 0) return - return ( - -
- - -
- {transactionsHistory.length !== 0 ? ( - -

- Current transfer -

- - {transactionsHistory.map((transactionData: any) => ( - - ))} - -
- ) : null} -
- {transfersHistory.map((transferHistory: interfaces.TransferHistory, key: number) => ( - -
- -

- {getChainAlias(mpc.config.skaleNetwork, transferHistory.chainName1)} -

- - -

- {getChainAlias(mpc.config.skaleNetwork, transferHistory.chainName2)} -

-
-
-
- -
-

- {transferHistory.amount} {transferHistory.tokenKeyname} -

-

- •{' '} - {transferHistory.address.substring(0, 6) + - '...' + - transferHistory.address.substring(transferHistory.address.length - 4)} -

-
- - {transferHistory.transactions.map((transactionData: any) => ( - - ))} - -
- ))} -
-
- ) -} diff --git a/src/components/TransferETA.tsx b/src/components/TransferETA.tsx index 71d4885..4adb897 100644 --- a/src/components/TransferETA.tsx +++ b/src/components/TransferETA.tsx @@ -36,7 +36,7 @@ export default function TransferETA(props: { token: TokenData; toChain: string } return (
-
+

ETA

diff --git a/src/components/TransferETF.tsx b/src/components/TransferETF.tsx index f19a309..df1a97b 100644 --- a/src/components/TransferETF.tsx +++ b/src/components/TransferETF.tsx @@ -35,7 +35,7 @@ export default function TransferETF(props: { fromChain: string }) { return (
-
+

Estimated Transaction Fee

diff --git a/src/components/WidgetBody.tsx b/src/components/WidgetBody.tsx index dedaf37..34b2f10 100644 --- a/src/components/WidgetBody.tsx +++ b/src/components/WidgetBody.tsx @@ -19,7 +19,7 @@ import CommunityPool from './CommunityPool' import SFuelWarning from './SFuelWarning' import SkConnect from './SkConnect' import WrappedTokens from './WrappedTokens' -import TransactionsHistory from './TransactionsHistory' +import TransactionsHistory from './HistorySection' import HistoryButton from './HistoryButton' import { cls, cmn } from '../core/css' diff --git a/src/components/WrappedTokens.tsx b/src/components/WrappedTokens.tsx index fb26b7a..8bc8fe4 100644 --- a/src/components/WrappedTokens.tsx +++ b/src/components/WrappedTokens.tsx @@ -32,6 +32,7 @@ import LoadingButton from '@mui/lab/LoadingButton' import Button from '@mui/material/Button' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import ErrorIcon from '@mui/icons-material/Error' +import ArrowOutwardRoundedIcon from '@mui/icons-material/ArrowOutwardRounded' import SkPaper from './SkPaper' import TokenBalance from './TokenBalance' @@ -178,6 +179,7 @@ export default function WrappedTokens() { {loading ? ( } loadingPosition="start" variant="contained" color="primary" diff --git a/src/core/constants.ts b/src/core/constants.ts index 563a53d..9641de5 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -66,9 +66,9 @@ export const BASE_EXPLORER_URLS: { [skaleNetwork: string]: string } = { export const MAINNET_WS_ENDPOINTS: { [skaleNetwork: string]: string } = { mainnet: 'https://eth.llamarpc.com', - staging: 'wss://goerli-light.eth.linkpool.io/ws', - legacy: 'wss://goerli-light.eth.linkpool.io/ws', - regression: 'wss://goerli-light.eth.linkpool.io/ws' + staging: 'wss://ethereum-goerli.publicnode.com', + legacy: 'wss://ethereum-goerli.publicnode.com ', + regression: 'wss://ethereum-goerli.publicnode.com ' } // ETA constants diff --git a/src/core/css.ts b/src/core/css.ts index b4c3c07..25f6b53 100644 --- a/src/core/css.ts +++ b/src/core/css.ts @@ -21,6 +21,7 @@ * @copyright SKALE Labs 2023-Present */ +import { Size } from './interfaces' import styles from '../styles/styles.module.scss' import cmn from '../styles/cmn.module.scss' @@ -33,3 +34,15 @@ export function cls(...args: any): string { }) return filteredArgs.join(' ') } + +const sizes: Size[] = ['xs', 'sm', 'md', 'lg'] + +export function inc(currentSize: Size): Size { + const currentIndex = sizes.indexOf(currentSize) + return currentIndex < sizes.length - 1 ? sizes[currentIndex + 1] : currentSize +} + +export function dec(currentSize: Size): Size { + const currentIndex = sizes.indexOf(currentSize) + return currentIndex > 0 ? sizes[currentIndex - 1] : currentSize +} diff --git a/src/core/interfaces/Config.ts b/src/core/interfaces/Config.ts index 2e6a7d8..9f15d77 100644 --- a/src/core/interfaces/Config.ts +++ b/src/core/interfaces/Config.ts @@ -33,6 +33,7 @@ export interface MetaportConfig { skaleNetwork: SkaleNetwork mainnetEndpoint?: string + projectId?: string chains: string[] tokens: TokenMetadataMap diff --git a/src/core/interfaces/index.ts b/src/core/interfaces/index.ts index b64abb8..df24428 100644 --- a/src/core/interfaces/index.ts +++ b/src/core/interfaces/index.ts @@ -35,3 +35,6 @@ export * from './ActionStateUpdate' export * from './ActionState' export type AddressType = `0x${string}` + +export type Size = 'xs' | 'sm' | 'md' | 'lg' +export type SimplifiedSize = 'sm' | 'md' diff --git a/src/index.ts b/src/index.ts index 1b5ab17..86749dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,8 +29,7 @@ import ErrorMessage from './components/ErrorMessage' import CommunityPool from './components/CommunityPool' import SFuelWarning from './components/SFuelWarning' import WrappedTokens from './components/WrappedTokens' -import HistoryButton from './components/HistoryButton' -import TransactionsHistory from './components/TransactionsHistory' +import History from './components/History' import { CHAINS_META, getChainAlias } from './core/metadata' import { cls, styles, cmn } from './core/css' @@ -42,6 +41,7 @@ import { toWei, fromWei } from './core/convertation' import { getWidgetTheme as getMetaportTheme } from './core/themes' import { useAccount as useWagmiAccount } from 'wagmi' +import { ConnectButton as RainbowConnectButton } from '@rainbow-me/rainbowkit' import { PROXY_ENDPOINTS } from './core/network' @@ -67,8 +67,7 @@ export { CommunityPool, SFuelWarning, WrappedTokens, - TransactionsHistory, - HistoryButton, + History, cls, styles, cmn, @@ -82,5 +81,6 @@ export { BASE_EXPLORER_URLS, CHAINS_META, chainBg, - getChainAlias + getChainAlias, + RainbowConnectButton } diff --git a/src/styles/cmn.module.scss b/src/styles/cmn.module.scss index ff6b1ba..50a7023 100644 --- a/src/styles/cmn.module.scss +++ b/src/styles/cmn.module.scss @@ -22,7 +22,7 @@ flex-grow: 1; } -.flexRow { +.flexw { flex-direction: row; flex-wrap: wrap; } @@ -157,6 +157,10 @@ padding-top: 15px !important; } +.ptop25 { + padding-top: 25px !important; +} + .nop { padding: 0 !important; } From 3186820e68a72311355e09effdf7658a604a1abd Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 3 Oct 2023 13:12:59 +0100 Subject: [PATCH 059/110] Update skale-network, fix sfuel issue, update legacy addresses --- skale-network | 2 +- src/components/ChainIcon.tsx | 4 ++-- src/components/SFuelWarning.tsx | 11 +++++++---- src/metadata/addresses/legacy.json | 16 ++++++++-------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/skale-network b/skale-network index 8c27b43..20616e0 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 8c27b4390bc22beaabe4107bea9b5e1f8e0f79de +Subproject commit 20616e0652a7a6f53b3f7ea90d86fee2e6086662 diff --git a/src/components/ChainIcon.tsx b/src/components/ChainIcon.tsx index f8a3c4b..2c63542 100644 --- a/src/components/ChainIcon.tsx +++ b/src/components/ChainIcon.tsx @@ -2,7 +2,7 @@ import OfflineBoltRoundedIcon from '@mui/icons-material/OfflineBoltRounded' import { SkaleNetwork } from '../core/interfaces' import { chainIconPath } from '../core/metadata' -import { cls, styles } from '../core/css' +import { cls, styles, cmn } from '../core/css' export default function ChainIcon(props: { skaleNetwork: SkaleNetwork @@ -17,5 +17,5 @@ export default function ChainIcon(props: { if (iconPath !== undefined) { return } - return + return } diff --git a/src/components/SFuelWarning.tsx b/src/components/SFuelWarning.tsx index c7eb653..b5a5dbc 100644 --- a/src/components/SFuelWarning.tsx +++ b/src/components/SFuelWarning.tsx @@ -180,12 +180,15 @@ export default function SFuelWarning(props: {}) { } const mainnetTransfer = chainName1 === MAINNET_CHAIN_NAME || chainName2 === MAINNET_CHAIN_NAME + const noSFuelDest = sFuelStatus === 'warning' && chainName1 === MAINNET_CHAIN_NAME + const noSFuelSource = sFuelStatus === 'error' && chainName2 === MAINNET_CHAIN_NAME + const sFuelBtn = noSFuelDest || noSFuelSource || !mainnetTransfer function getSFuelText() { - if (mainnetTransfer) { - return SFUEL_TEXT['gas'][sFuelStatus] + if (sFuelBtn) { + return SFUEL_TEXT['sfuel'][sFuelStatus] } - return SFUEL_TEXT['sfuel'][sFuelStatus] + return SFUEL_TEXT['gas'][sFuelStatus] } if (loading && chainName2) @@ -201,7 +204,7 @@ export default function SFuelWarning(props: {}) {

⛽ {getSFuelText()}

- {!mainnetTransfer ? ( + {sFuelBtn ? (
{mining ? ( Date: Tue, 3 Oct 2023 13:14:01 +0100 Subject: [PATCH 060/110] Use legacy config --- src/metadata/metaportConfigStaging.ts | 279 +++++--------------------- 1 file changed, 55 insertions(+), 224 deletions(-) diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 7d08762..b669a36 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -1,17 +1,14 @@ import * as interfaces from '../core/interfaces' export const METAPORT_CONFIG: interfaces.MetaportConfig = { - skaleNetwork: 'staging', + skaleNetwork: 'legacy', openOnLoad: true, openButton: true, debug: false, chains: [ 'mainnet', - 'staging-legal-crazy-castor', // Europa - 'staging-utter-unripe-menkar', // Calypso - 'staging-faint-slimy-achird', // Nebula - 'staging-perfect-parallel-gacrux', // Test Chain 1 - 'staging-severe-violet-wezen' // Test Chain 2 + 'skale-innocent-nasty', // europa + 'international-villainous-zaurak' // calypso ], tokens: { eth: { @@ -26,47 +23,6 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { decimals: '6', symbol: 'USDC', name: 'USD Coin' - }, - usdt: { - decimals: '6', - symbol: 'USDT', - name: 'Tether USD' - }, - wbtc: { - decimals: '18', - symbol: 'WBTC', - name: 'WBTC' - }, - _SPACE_1: { - name: 'SKALE Space', - symbol: 'SPACE', - iconUrl: - 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png' - }, - _SKALIENS_1: { - name: 'SKALIENS Collection', - symbol: 'SKALIENS', - iconUrl: - 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png' - }, - ruby: { - name: 'Ruby Token', - iconUrl: 'https://ruby.exchange/images/tokens/ruby-square.png', - symbol: 'RUBY' - }, - dai: { - name: 'DAI Stablecoin', - symbol: 'DAI' - }, - usdp: { - name: 'Pax Dollar', - symbol: 'USDP', - iconUrl: 'https://ruby.exchange/images/tokens/usdp-square.png' - }, - hmt: { - name: 'Human Token', - symbol: 'HMT', - iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png' } }, connections: { @@ -74,152 +30,78 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { eth: { eth: { chains: { - 'staging-legal-crazy-castor': {}, - 'staging-utter-unripe-menkar': { - hub: 'staging-legal-crazy-castor' + 'skale-innocent-nasty': {}, + 'international-villainous-zaurak': { + hub: 'skale-innocent-nasty' } } } }, erc20: { skl: { - address: '0x493D4442013717189C9963a2e275Ad33bfAFcE11', - chains: { - 'staging-legal-crazy-castor': {}, - 'staging-utter-unripe-menkar': { - hub: 'staging-legal-crazy-castor' - }, - 'staging-faint-slimy-achird': { - hub: 'staging-legal-crazy-castor' - } - } - }, - ruby: { - address: '0xd66641E25E9D36A995682572eaD74E24C11Bb422', - chains: { - 'staging-legal-crazy-castor': {} - } - }, - dai: { - address: '0x83B38f79cFFB47CF74f7eC8a5F8D7DD69349fBf7', - chains: { - 'staging-legal-crazy-castor': {} - } - }, - usdp: { - address: '0x66259E472f8d09083ecB51D42F9F872A61001426', - chains: { - 'staging-legal-crazy-castor': {} - } - }, - usdt: { - address: '0xD1E44e3afd6d3F155e7704c67705E3bAC2e491b6', - chains: { - 'staging-legal-crazy-castor': {} - } - }, - usdc: { - address: '0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9', + address: '0x17A7Cf31a11554e75246973663262dA56F84F89b', chains: { - 'staging-legal-crazy-castor': {}, - 'staging-utter-unripe-menkar': { - hub: 'staging-legal-crazy-castor' + 'skale-innocent-nasty': {}, + 'international-villainous-zaurak': { + hub: 'skale-innocent-nasty' } } }, - wbtc: { - address: '0xd80BC0126A38c9F7b915e1B2B9f78280639cadb3', - chains: { - 'staging-legal-crazy-castor': {} - } - }, - hmt: { - address: '0x4058d058ff62ED347dB8a69c43Ae9C67268B50b0', - chains: {} - } - }, - erc721meta: { - _SPACE_1: { - address: '0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6', - chains: {} - } - }, - erc1155: { - _SKALIENS_1: { - address: '0x6cb73D413970ae9379560aA45c769b417Fbf33D6', - chains: {} - } + // usdc: { + // address: '0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9', + // chains: { + // 'skale-innocent-nasty': {}, + // 'international-villainous-zaurak': { + // hub: 'skale-innocent-nasty' + // } + // } + // } } }, - 'staging-utter-unripe-menkar': { + 'international-villainous-zaurak': { // Calypso connections eth: { eth: { - address: '0xECabAE592Eb56D96115FcF4c7F772ADB7BF573d0', + address: '0x9C0e8bC2B2D403299214c80081F93fAB5e10b593', chains: { - 'staging-legal-crazy-castor': { + 'skale-innocent-nasty': { clone: true }, mainnet: { clone: true, - hub: 'staging-legal-crazy-castor' + hub: 'skale-innocent-nasty' } } } }, erc20: { skl: { - address: '0x7E1B8750C21AebC3bb2a0bDf40be104C609a9852', + address: '0xFbbDF9aC97093b1E88aB79F7D0c296d9cc5eD0d0', chains: { - 'staging-legal-crazy-castor': { - clone: true - }, - 'staging-faint-slimy-achird': { - hub: 'staging-legal-crazy-castor', + 'skale-innocent-nasty': { clone: true }, mainnet: { - hub: 'staging-legal-crazy-castor', + hub: 'skale-innocent-nasty', clone: true } } }, - usdc: { - address: '0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6', - chains: { - 'staging-legal-crazy-castor': { - clone: true - }, - mainnet: { - hub: 'staging-legal-crazy-castor', - clone: true - } - } - } + // usdc: { + // address: '0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6', + // chains: { + // 'skale-innocent-nasty': { + // clone: true + // }, + // mainnet: { + // hub: 'skale-innocent-nasty', + // clone: true + // } + // } + // } } }, - 'staging-faint-slimy-achird': { - // Nebula connections - erc20: { - skl: { - address: '0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d', - chains: { - 'staging-legal-crazy-castor': { - clone: true - }, - mainnet: { - hub: 'staging-legal-crazy-castor', - clone: true - }, - 'staging-utter-unripe-menkar': { - hub: 'staging-legal-crazy-castor', - clone: true - } - } - } - } - }, - 'staging-legal-crazy-castor': { + 'skale-innocent-nasty': { // Europa connections eth: { eth: { @@ -228,87 +110,36 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { mainnet: { clone: true }, - 'staging-utter-unripe-menkar': { - wrapper: '0xa270484784f043e159f74C03B691F80B6F6e3c24' + 'international-villainous-zaurak': { + wrapper: '0x321e1aa81B4c6CC3B8EFe3D9c0AD67E6eC949c2c' } } } }, erc20: { skl: { - address: '0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6', + address: '0xa101902B3119f4830292bb79ebAB56967229207B', chains: { mainnet: { clone: true }, - 'staging-utter-unripe-menkar': { - wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6' - }, - 'staging-faint-slimy-achird': { - wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6' + 'international-villainous-zaurak': { + wrapper: '0x51A1eD016633Afb00C25Eb404745C61D8c16BBd4' } } }, - ruby: { - address: '0xf06De9214B1Db39fFE9db2AebFA74E52f1e46e39', - chains: { - mainnet: { - clone: true - } - } - }, - dai: { - address: '0x3595E2f313780cb2f23e197B8e297066fd410d30', - chains: { - mainnet: { - clone: true - } - } - }, - usdp: { - address: '0xe0E2cb3A5d6f94a5bc2D00FAa3e64460A9D241E1', - chains: { - mainnet: { - clone: true - } - } - }, - usdt: { - address: '0xa388F9783d8E5B0502548061c3b06bf4300Fc0E1', - chains: { - mainnet: { - clone: true - } - } - }, - usdc: { - address: '0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33', - chains: { - mainnet: { - clone: true - }, - 'staging-utter-unripe-menkar': { - wrapper: '0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0' - } - } - }, - wbtc: { - address: '0xf5E880E1066DDc90471B9BAE6f183D5344fd289F', - chains: { - mainnet: { - clone: true - } - } - } + // usdc: { + // address: '0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33', + // chains: { + // mainnet: { + // clone: true + // }, + // 'international-villainous-zaurak': { + // wrapper: '0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0' + // } + // } + // } } - }, - 'staging-severe-violet-wezen': { - erc20: {} - }, - 'staging-perfect-parallel-gacrux': { - erc20: {}, - erc721: {}, - erc1155: {} } }, theme: { From 48f4e0bd73620d35f4eecc686ecfd94a790692df Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 3 Oct 2023 13:34:28 +0100 Subject: [PATCH 061/110] Update metadata --- skale-network | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale-network b/skale-network index 20616e0..d9d60d1 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 20616e0652a7a6f53b3f7ea90d86fee2e6086662 +Subproject commit d9d60d12826ac8e0b34107856b0f9073c09cf663 From 69a395608ecb6255661a6b975ac6db2ebb4626ae Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 5 Oct 2023 12:40:31 +0100 Subject: [PATCH 062/110] Update ima-js version, update history component --- bun.lockb | Bin 0 -> 613415 bytes package.json | 2 +- src/components/History.tsx | 2 +- src/components/HistorySection.tsx | 4 +- src/components/Stepper/SkStepper.tsx | 15 ++++++- src/index.ts | 2 + src/metadata/faucet.json | 4 ++ src/metadata/metaportConfigStaging.ts | 60 +++++++++++++++++++++++++- 8 files changed, 83 insertions(+), 6 deletions(-) create mode 100755 bun.lockb diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..024c68a5dd1724d8a5df90ad6e284059e9a59a13 GIT binary patch literal 613415 zcmbqc2{@Ho_dhf!LntXzrjnA1kOo4AP%0A2(80kmJI5SS8YC6bASx*ll}eN$L<35r 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%0A?xMxf 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

C^|63M5*VG@SU*V>fPRJ#1gA6n@>ah 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 z+>acE&o%#jj+QKEC)NSh0oH+kH3xo;VPXH+=P6_x|J%P;|3CRf{$>t4u@0~funw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>eunw>e zunw>eunw>eunw>eunw>eunw>eunznmao}Vg_pd!2cJ_BUe0o@@hl*cNSh!nYph{q{ zM}W6qpqGkmsF&AFarG(UVSdqG!QM*Zn&NJOem+6L-oqJ2_5b`m5$OvT2L~t0-(i2x z^I(1u7z)TV2n$J@!C)8>Fb$>}R_7Rq%|$g5Yb0reiJNLeP#5^6aBvKPMq-ipLR90S z8i_AVHC||>AEZAMsK!U*k!?y-jh|{GFfT{7QP4==c!4t2#?f}9Zz@z1pc)_M)u}d~ zYW$ejpqe1nMq%EI?wkrCx zs#KFi-I}(WLNzI9i>RhXHEC!LR8xmWwj~2{sHO#t^iLLSq}o)fk@F{=YP!(Kaghgo z^u(V=H3ev#R#%hf!@h)s&z~BOW>bXHZQUby>QNnN%a^qa4*{QB4(^I@R>4 zHU-*i^o4ZC02=W}4P2)EGp6mxxm-bZ9Mc@Ck#pIftbl1QG_qI|jG~$;ZKnlo4Aso2 zrVY)M#xtkdRA}>|kyBwlG~%fau%zuQX**qLR#da1+B9f3R9iqbJ!qLE2qtT)O-Fq) zZD&KZ8PL|#cD7WT3C)~pc2t`M&46n5(C{CJK9~>4!5FGJK%0ej$|z&0=7{tj5CQ{86+HoOQxC!v>Q}gPuKN?7D%tv6smba3#Qrzs(C|OMzvI``9O=LS{l`Sp{=0WMymNi zi-ShSk927GkHa4%(RLXSNS_112C8j?MlRbxkV>_kwB1r@o2ZsWwIFEIsJ0s#IUd2l zkZOCV76NTH)%H?t88k^~WZcT3S}5vy=l~gya-os_g@MzgQ<(PAcHyX(KqF(+eyT;F zz8xAFqYlt@m!n=lf?&$0?IKZMMzuq<-3n+Pq!Fegv|SYH1-J$cQ68o3qERn~MyBIb zTZ#G^s-2)(475Oc4W6R$VxgUEdkmm zx^4;85}|#8HW%d?s;x%-8*NugwKdR2;9!}cJPVE3u@skR06u{2%-)wV+8hxcS$ zXrx*O>UW`$F`$WRnW(o>tr;4zV;g9MMy3|pZaeDD(A-hBQf&w7x2SfTYCE9~g+|WH zJ2c)d)XBX9nc8T(EY!(3Po{RNWuu->w|kFjyP-{?+I_0+fu=^a2hhkd+6!h-?J+d6 zA35Lz_2mg&Hy2tcG&0U~QY{a4PTKA%U3VX}LVE2#qwV%X6Goj(-L%~S)Fn_SW7Kn$ zI6XNI0&K$}^>8I-!LF=R%xk)8n6@xlxWQ^jZS_$g)wA~=8oq^U& zwZYKHzLkQD(8yTEP1~JCT>$4nJjx+dJBNBdJ-$Psp*tMs!8WS#(smc1ZKc{sXk;5@ z;07w|Q1U|~`+E`8KqF(t7`pBy)N83WmTH%wIUybyE5=c+9CdfP4FRfEKwAJU4dr;M zRids3jf@q7RJ(#Y_Xu1gD21SrzEpv6Xh+5XQQEE=^{pv3T_Xt#bfq8OXlF5W>FHw*9rEqY}quMLXzr+|p#v@az zy+-{t)y$~&1{yg=Wc)Cv+FR5)aqg1wV?H!;y}bj2sAfgm^+MyK+5)P*hsI4cYiQ&= z_yEYbLB(%LL=9#6Ew0P-vId>B-f$~ZTB7Z8>o}($(3q9P=AASoLnbM=(_!=ZwBO= z@SyE}QjMJJo>U_P{7-s(y{N{C`Ju!NOy1D&AIBhQJX8y$?FK`8L)TqOH7?APbDvB> zRO5ygOWOrgZ3yP$s1`!Cp_q@S+A^vQ!@M-rLaD}sd2$|+DGVCv)^KR#JR(yBZAb1y z$@xO`J>rj=A11+56{l4BYJ zjU2bp(BkOvT}9iCfp!yha{S_{HWr#P^)!KMO2mxUr0z|`oT{?PBxOq9RvmgF2D_jfT4iob9ulBzzaqKKEMw~fzeDfIIL2p1=!u10UcE{D40Q0D)jB2m-+%1S|ugAPk&G{0rbBxCBUExg1o0 zN^k{Kfod=U7w<^Wg^T$a=mt8V0&yzAuUs>h4+4V$$upC@@;D#>zM;(wFcTO8BVY{X z0+MSs1LnX2keo8fAuj;dfaH#C0m&C{1KYt4uoLV8S>P_1gf`+p0!#*yKnkqIGLl2y z2uR*|KCl2*U;!X`V?&@1W&jhMD)WFTFazdbKCl3mU>48^2EYi+20|FLL;=Yolf3aR zkOg*wy@2G64}gOp9kk)IpX812gGb;o=l~=~+y$Nik{j*;&%q1u5*)?#>4D?n1AGC= z6_Z0|Ug&!oD3s z%xS1=08K!0$KgO5$JHFA6<7dB{&+0NhUc$wUiSi$KkfsC;4~-(C4l6R$D&W6D8oQF za0Bk(Gd^qjKot5<^1meC8wW_PmE>0Q(T3!%NUmxZ>RDhr$N*cwN$4bhM)GAOKRg|% zfim>>BJc)2z!$7SAC_bPBEbrvjCPV>H0oo(4eX0J=EcB7a0u%Np%exaz()APg>72nHb_1>4yGQb7_(28kd6tOPM27Q}%SAOesa zG|4$X1dqUD&;dx!xf47EUEnGp`Q>Y%4%CC|faI6YV}GV$AF2@VDyRb_#~cJmUYX>P z!vV=1lU#8Ohy{sYHCO}I0+Jt22J1iyAh}_Z3*HDw{A3OjL!6Wb(bbu$I z6Fdc7;2G!!J>WTb0bYVv;5B#ybP=26lJ$TAAUR|sK=Q^D!6YCKB*0`K1*Cy6-~;;+ z;{Z4a^1&f+7#sngz-N#HNG|y}$N-0NOpk(NfaHl!fz#js$OZgpGX{J@{WEwB!a)Rx z1S>!^SP5c4EC>R@AOw&+vprY{9DpM*26KT6j@5LO8o&*84{#FKdl4Wx<`O{i%4fkj za2^~1B(F^J$Ruy9bP>bzf19hMr?7}v(KsF#bW|C7TIb@9cTbeuoL4D$t`~Z=ShjN?JzX*%pebtXA$Jt z!#Y5o^<;xxAOZVF^7{=aFQfnE;1Y;N%qSp*aYGuAd^E{FAI7pHpaJpgF@GK00MS^! z62yQwunNS3M6epH0m&c*Yyhbs4QvFPz-F)wYzI5QPOuAPfo!lF>;Zd04mb)z!A4vI zX2Py*!L0TrMMrT{gd4m5x!&;r_E zD$oJCU>eW^)4>cd6U+kozyKHmBQP6~+&Fn|J{OpPdB7By0dqi}cUu5UU5cL0rITZ2lxU%;12>oAXo~5KnPd{LO~eV z4&Qcwoq*)+MZp9h1|)$rPyi}`0m;X=fqURSs0Ld> zCfE+#fCaDy#y}a!0y!`N?7+Fw3*Lj1KnTQPbE`lCNCd0F8ju8%!8+iD>&Y89lM?ej zz!&%de-Hoy!BRkS=p<)O^5HiD$$__kauAI1Bm|Vfqa`TafIA?$?w=qL?EO(ZVj$>O?v2+o2$a1Y0x>;z~cUgEgtf?a^*8c2SHlgyfsZ zGm9#40P7wE`QQ*BIck!V-UoJpEU+7_0RbQwECXR60z?5PU<>Sl6)*?$fdweXIA)BJ zXQoDFsWTVMz5!90+Kef%R zNhAkDaxNr4MDjr-|3mUUB$q>SHzZd>axEkmQ-i)-1-0NB2nQs;`U>aC18^H$fhSkc z|2sIwcfmby0LS$p$OjFWZv@TQw+8sL6@GDni>RN4w&90!ZGGsif=NIeNC1+@dP%$k-QY5a z1S`Np_;?32V}EXe7C>@=x4|7y3@_eczdnFu)MEh20p5Wh&!IJdMnLj?ia-I#03jd@ zM8E_f2Kd1!(2M?%93RQ)$pIr^3+w>N>5-gq2j~PZKshJ{BtL!*WP{yc57-NGKrXn8 z{~;0GVE-9GRYJOkaJ1GIp0 za2zOMn_B4GG7tu|uuVafZ_%$4z#A+;8}dHWVnE(uiigK*0C^{A8`ugo&_*6efSH&l z?;(-*jw!Ly1Ye++f^v`t$U7_K9hE}tb1!%S!hjK& z4dwt7Fb_Duk0pS-gHi~N0zP;njO~&4L&gE}t_R5v>tNp(V0ksxBR+is#HVlIJGcv? z-~)N5LJ?fT`F_gXfB1L%Jv;*tF)474HbUtrt(pc~u;ZGgPf za0?6t;aEQdr6P#K_F_N~2nNdld0r9@B+yP0kZ1P~z(Y_4YCs0q2HXL87C#5bgPrhi z7kCSZw*&L!*?c_wS_^_e4g9zY$TRWlfIQ#61eU^wi`X_l7?1u5fYE@&{tj&<{3Op6 zwxhlYq=OrXT?4LyTCfr^oS?e`H}DecEYSmdVlHM8iDnPfm>LIJd3LXX90NzN7fS@@#kvB3JdY9tGpS&i6qpS5Al1CQ5`ia#!$o^sI;=pD(U>65YQx4(`Sw`+3$vx#5K%Rq< z^@IWG16fXD3IX!mY~UFhd8RfRkmqbjUH|$W2WdaxBR``~G_q_AAnTK7aRaePooHmc zqJZ>U1d#S*yX1c5EI0#-!BIfY_XR)<46HZMeqdV`ST+%m(ge%^lfZPK2c`jWpamua zRiFZtff7&zGC&$g0SO=pNLvLU4`hKHPy^(7pE?+L)<@PQ%eBE&Ffgx!x-KvV`d}_F z0)}8F&sSw0&KEHj`wnP=9?dZhgvFwmCF59s=+lX-Js2Ic`%Fdq<&%#*q$AooP% z*bf5a{0;-5zz=uolV z`3RKD0XffBpo|3hU_aOm@<0~Y1xSn?AQmt^iotv|SPA~CS399?2gH+1unNS1ctHBI z24sM(U_Dp|l0gzk1Z%-+kU-~2`wbukYyxDz(?BY%r=#2m$bN4ITLAfNCfg$WNG8%J zvL8ev3>>?G_2+;_WdI1>ykd?|*GaJRE2{(5?X5fY(b z@B%ys55Zk<2iyXUz#H5E*Fim~1Lwg~a1GRgtKb5t0oC9NxCF|;MQ|BZf(lR$h*kxN zb{UX$Nn5fmDM{=!T9R{&?AuL1mR|-9pcym)(vIv;3uvW~`3ImKko{-__rX0d3y|fH z0nr|TZqNmufDX_Jo`Pqf2fP9=K_3tV0)XUI$o)6@UPC-2_g_N+@shlc^ciKu|Hs~U zKvj`+-Ci&U6cjP%96++5V$L~d(F0d5~{zfcpa6CtzD~&w##QHeB<*YI3~toA+eAwgJ!m1tpI4;62Pw ziK~m>b%5GH8Gv%t2e`-N2Gj;z0q!$o2T%Xd8E^u)Kf$)F1=PWHU4ZErHv(Y-5DyFm z;sEX^4F;GGeYO}N5};2;dFY=h^=BU3OKJnO0Gb2MfTloWfcG_#!`2At+tXL(JGvyX+TuC{V7|)z%(n~B8R!Ib1Udk`m-qEV*cIpjbO*Wt`w<_E&>!dn^agqXTA&}` z4)g{31Ac%9;0@>iPrwWC0epc00Qs!QGZ0|_5CjYa!T|E2fTD{CTxZmUqA%8!_e3LP z8&kK69{D>8zo{$UL){F)bxwpi3F?zQK^(aNIu(P}(tL8)j}h-p@8u+L*i| zCpq>5mZOxxxKa46iUWI@Qa8~0legMWy)ytq!qbqaq2dGMS2 zuiS^t37kb7*RT5#o&in*`+#I%2OwDr>Hm{5{buYo#BB$*0$YI1z*=Ap@CUFGSOF{t zmI6t@Vqg(47nlP~1!e*>fGGg&=VXM;e;Pva(}7vQY=GQCfN=|ec|amCA6Nn`16Bd7 zKe0N)ZmchCxGEmN)fTFuyxZVTo252+(0%-vCOx;mehkyeB?>z{d z0FDF4KtZhIyO@*mjipryS2VV;emN9su`&O8|Alf9Eq?^Y=OYz6e|Z z{sis;w}6|#b>JFs6}SvApDRE*a04KB8@L191$ZBM-uDQgEX?x<@E!ODdOLE0k45qz)Ro-@EmvsJO!Qr?7!3npBu1qn*xd+a^N~Uzr z;9d&%`?>eed%6G5Jr?e-lmU1T!99^m@-_Fps9&aIe$1D9_l##du2&`7{Q4AboeB1+JR|&48xBbi}noxE0s}48-r6n0)wbmuG{x-_J7u zoRCf;+zf02_83mbOM+k^}w;BL+Azc2YLX?d7XZ^b_eJ;_eMAs zAWV?U;aH0;ap%2FdJZ-@Om+@ z2-pCyFRVuR2e1lQ39JArNJn@LxCC4TwgGI93xF1U8ba#w2yhr+JEZ{00MqY8 z$bPp2;dX%Om2|1N-V5vjb^}byG`xS8e9hm?k0H}3_we^Y#P0|80SACI#2u1D#%Gj+ zWrcu0ijZXuz%}c^eApk@4^9Hd0m^s+I0u{s&H$%?(*WmN-lvq!>&pQ1Qu-9-zk=T! z$BZXW-SL`b@wcK!_Cd;U10nfa0ONT56u1p2_ddb(W8e|+0Jsa>0qz6$fQJC@VSX9& z$#|{g{j2Z|@?iPQ$Jl)zWS)Mn!c*2S0Dpg$`HUR(&$j*!d;`7$EQf8O)Q|Tlyiz`8 zQ1WG*k|#r^=eWxp&+(W!j^!~fV%yImkQMVieNA>Z;uvyF z@qV^9%VwLge6}fh>X~iI@yq*^wxd1GSdJ2(F-|Fmc0gOe0eN;t*a_$;hg}d-=JP-gTyuWt1~Bi8 zW3sy(r_?WF$h5sO@Vzr!GaufYd8{epA!9oZKsrBw>q}pRJ^az+8xK%c`aDWm3HW^mzb7G_2ryqIKSgGyoeoge`3PD6jODYQ^8nt@dd&q` z2d3fu3eVrOArtGf5Z5b!<-jtaE6QDpa0#IJXp3>3B;yZ+D}hzOYG4DvedzUaco1P4 zupQV2Yykp+tw6?ord0gi4eSDT0Lj2kK-sHhdirWC0 z62GaxLb#^>sJl&oC4N(fd|xgfkQazRJoV>|via^^8-zI!Qf~`!%|3FEStCyAW7H+v znQJ9e#OFtt1tIO)D5Rrp$d2FR5N1P|8^{IZltVLwtmgvo7PvMCIL`AUboRD z>VkcW`rx^5*0D6gasc&K24QdHTNYtygq#nVmS?l71J!^U0Oyx2fHIcYPD-D)0nhfQ zzBdDudkJtBacpyE@D6}2!1IBQ@^3qY8*twg++&YxWj)E?T=&;QoEuOV;5$k#KrMjl zT|WP+jccZHMX01<$T;3J0U_sY-p6$Hfu2A&zyAddTXXA$2BziZ-m zLxio#p)So3_D5V(giQcGui_Zt-XZnY5^<}+H%I7<-yAzFa9tH)D}-$TwtvQW>Wy~1 zE4VHI?KscucL3ed*FHkkN^w^*f)6gW*86;3LZa!Sx`7l$F=fay&W8 zJOm-@)d2FbuDoWQC_Cl$N8Xf|GCBdwN731MT+^P6LpT-~1&jdhArIcevPJ^r#sJKl z{hjjip3#WooXY)5-ow0=dpRC<<973__k;_#50Lge?Cs z^1TT8rsFyd*PMr@;d&}C8K57+IcW;6GtNQec>jKY_iV=RO~4UcGtZ5jDUbv#0Tu&`fQ7&U zAQ6}W>_IxpGauJ1YbL^Zz+7Msz%ph5Yn;$ zJg=2~;%?|K8Shof$QYOLntd@DU>mW&W#o3@H)Y5e$Fiw^jp0m{Cs3&HU--Gi1KZBjY_v{my{D0-Oez_W>XcNCi0FS%*u&W#ByU5OL=a zvfgJAvTn2w%!ld60kjRYF}#;?)R$s|nAb(%PavZWV%!CQX}pnlM%zO!5>W18T16I? z%`!5}&fh1I5BJY*;QBhieY13g1rc`vc)~c~KJXa02c+U2-j@gb9sIrpOvG>Q<=w{h zPK0+6_C!cq{0P@f&wL&Lb#O0r!g?@{zAF8T7r1^7JOeE7JLB4y{1Bw$Ue;gWUjv8| zXEzwnJx6js5&r?GjoRlw~=+*@3K1eEdh9oK2#c>ZTKt~nl;0UT#50ImTt z+B4eFnTY=c&}Mx@_!am9d=?_i_H_wF9oJDXXOeR%=SK@%Q)gUT(I@6Ov_w3j z#QT+e&$0j@ej`2{`OtnTYds~Nel*Kx*(Gs5b;vR(BjXhxo%it_S{p!vcx!}8I{Mz_ z5my6o5x7?TZMIou#FYW~{E;%!S7h5Utc3WA0N=@|0PwvGzW=FQSI0Hq<*f=-0eBxl z-`f@;eV~iTm+$l10UU2m2&r#JfaS3)hJ44Dd@Y3Z$(4FF!f$2mrL+g%^<~}o-Y@mS zcYs}xrU>Gwo4WXY1o3=FxHfPY*RBZJ9&8H)`nG_49G8r1j%(_bZO-({eTpoK9ON4! zKc#%7T&B5)d>Y7QvOFaZ=1cyxTo(I6SKQYWAg8RalstKFV{jSeZGzuQew4W_e)C=B zjsV|jZU=M$ngK7N3k3i5{+76@xyt=55U=FH@-kktY?i5(wlz3oGPFTF{u6R#&QqyV zXWYR1l)AOYHOrzrYUOnRhyRS`s}`@6!MxSvC1lyVx`6!z^ zz<1mG0GZ`tol+KrnC*`eGT(OIiQ%89tx!EB2Ti-=AZCQ3wYC5kMF~Ux;}q_b@La5*$Mp zq@fLAdd4Zf6S)}tPD4Gm1Kit-#c%3}_KkLmc9Z*?v}25;edIlv?IZJ1(kgb0di`(v zM8?t$Lpr5h{wH>oWqDXDT+EZ)OW`FIR|onXFBSBB7Q6Bl=)&b;_3hy(~iM!&ZYEu$II7a5z_Y@hj0Qw zF5~>h{5iL71h)ZL56l3j1Ji)1z!YFIK$$3q2KO^f=J`&UyP0Q3d8sGXGjkm?-jkVA z>aN^FU1h8*^=hm<-p4+y`fM>rS1xraqxmFd}r zOgkUfOm_v>dw`7Xx&WNgu1vE^PRBEX%jI-RSxfPoDy-1wbG~Arb-`D z^h-bPf6~6n{p?f!OSTInzNsp%lUqZr8#sNi4jy1nh4`$o9n zFEly-{asAE|NIM0p*pQ+IPRVD=*rEaQx48BH?ecEvA3~BPeLr-0*D`6q|O(=(Otat zOldI1!Q9bp-0)%g&67DzYS}n4i8wu963nEnjc-?(_|`|ybO(zZqHV!u2+Z_p5fXNnH)&Jb4k(2akW`QvWGo#7u>JQp&YNBUW zfGGrKRPK7Sr}c@Mpl7&aUkps1Gp$pWruKT6)5KP)sCZ9=vW+>k{#t5>??I3N)nSGB zMg-nKh@V=s*ThQO++1^-*xRD2u99DYc`v?Lp^6s6%6Enigx>6J#R`(LjoH28e33!t zYRC*KQ~>Q)2F%fur=J$<;BJ%C#L324lw%LZ5=`z{PGe%Nz1rq9p#Y93y&0H-V0_M8 zs9o#SqXU%M!O_MJUKtol@WFaxe20fO2B93vX6LBUg$4Kpp;42GzJzUos>6Bipt zXVyoDG_2_&kLS;|&CcPi5Lc<9;+#0vpz#TFr~2$ZdarR#6Ng$7wM3RJ`-3+HyJoSB zgbr-%U6|W8F!_eeS-l zbfa3W;B>Z+g99m^#4QYFL)8&OYFkZAWKEF@hX4kIc?GkVtQq!b`_xN5PLRMJ*+GH< zVZlKlH1igCS#%pQtN|oIJQ=R(*m57HRg6Bf?)@Gx4rp%V77`p5?im)Q>7zY;{CS(4 z!-RHh?9o0Up}O#JKV9f`ciT>zt0zx{4y5Xc)9rW*CEj}daI>;?_LoL(PBt!-I`jC+ zY2q#qAn5r&#*dx})1r9%>-4D6IE$&iQDkhKoseA-NX;>4nmRVJa{3YU9c5&d1w#oI zRgAAueWcEVeFuqSx|O*B9jGD0NY>|H1az@IJ*xOS(>EIcy+1v3tY%e6M%&NC_{8 zvZf}Ra4%)LAm`fAuFa?WE@c9c2K~dv-c_6;V|~0Hu1qSoVEbS>H%IC~$L^;2F>gth zl($y<1cSllq8X(13G@SV?n#RZPb*IxCKxdqV)N{iK!WKd1UZrRJ0t0oSkYHtxMw*xRr)M)q z8jNAYgrXegW_o#G{OpGI)g`G(@wq;G)#Fv;@?W}?J;c-`1u@ih8_a|j2=`8S*Ua)& z^kGwzYRHNI`hpR6oDLa2zU+WidIn=BJUT?Das3dUH&68RsVIlT#NJS9bCaQ%p*SC$ zMo8<@qxq;Y6P69=C$wW<3+j0XhL*y5!|Jjf>yD`}F*GmyVnPKlFAmOLIw;%hP(7pd z@Dmbr>A5CgTJ@|eO<^fq#EfC6YxEGIY-{93d$6G3?Y#B2Y=6yGaHKg9C*U)VEb7_` z6>>Ca1?t*PSjEgegbeJmjf0aP%3&X!ocnq$v%sjDEXP$?a(=0s{d(QrelLbJU9?=^ z5BQyHGS2%`OQh%cGMo&I6=a(WhHV;gyw9hStDhP@Nk`$qKU%I+qtxxb=k<3v6ph+zT7jXieZ$uO{E~l-pE*Z~m@zV! zWBzwf{GHrlg1sO+b{C!RJ!{@%=srsm@x2+8tS)utxnAGn4cjM<4FnzVi(~9nmupw6 z5qGlQ99S{fLo$Y~4My}tmE6$WD!G9%Or!J=V{Iyph2l^&+jQmF7rIK?D$T|CvE?u^ zYOBN?%&$e$9;EKy9@5al_Nyso42TU)X4ae1rj7>IQa=`AksGb@olEzQR;uk61PL54 zrDM{X0;U9*oaY9Ox%58QVlcAa_+2LWm+^J0G;G%U-r1L6&_8G?_!T+&Z(F+BtUA7W zJggY{wdkY|z?1=#FFvo?Hj9XQrtnZ;t}z(+6&p&>GI?H`m8Y9j)-y%HlmauoUfZ@F zxuYH;H?$60u9lqU;PwK?_XfuvWEy9ogWu70X1(c0iMkE>o!*-jW}+zSbN@&)=gzTx zb$Y2wg5mu0E=!KBf#+Mh=o$4i0e;~eKAJIc3!AsIJ-m!*gv+N13=a3B&(%BqqwnDD z*D=>q)6~zvP|sj5cp?FNvxMDicA)|-l9RBo$&dg|6yI`0f+?`DqKBYY6$8Uja{orhBYrPRx(f#V7rUQeIGYu@vnusr z>YCDW8sydh3@RI+*FCY!!y4T(Fl}V!dw|KtkaA7nA3y>jTMwDJoM%+R>N`mm`rM-J zT+jwMo$yw>99UCp|lQ0jQ}dltx{bEO)1TTCQ@*VB}Rn z3z_Lq6b}w_4At;GN}jRXt+~#vIJ!F&i#*^~gnQDd$!}X402! zb~>_Bw@J@9f?+w8-~6$2;q|rU^|`4^;24gTs|VVr*`gb(lM3(tgfv*Qv4^PC$Jxf= zDayfr@eTJ!G-&PRG34)Z995)78WpL*{6tpNXp%?aT6Ns*?=m;^Q8-EJvZo#B65m8y*j*}y zWpZFLr62EZR<2U}L&+^fIbxg|mEgaWV>CBm4 zv%^t8D*D1#r}$LNjrQ{D-Efs7yOstk7X7;M14b3+A7#Iaeu%Wzyn=Enfm!h4^xffa z=4}-<6`M~#z|a@#Q6{ncpyco`G6VmuB)0$Pzjewp(eGNN=~rdFLD#NexHst6pvmRH z{1(0ROc$96oHJ|O$fUC}r5eM^>6xowILmypE7$Z~zTgsII2xgy z*I+0?LcyBjQjYdv8ZwS}vaDK8UL-(+$NyNhtV+z}P|(=63ot{lL(^g%x+JHgxz!X`e#$gFrCUb)KE)hdTvNsf9G$$ifnA z7Z~=p8^abl+;$#(84PN}K6)Aq?M`BrxalQ&Z?_lai2im(E+;N>jYG?}pRsNfHAR|- zU^uorCWIbtwW{|;FvyF!sn^HQQlP(yeTv^2%TV9YB2_xh|XD~+tH*V_5sD^N9o zV@{~eh>cfr+ln-7xq3s)tnP*GXfLgdoG_{kb5q(!y(g;o_&>@?ZF<7%bJ?j)@6Ck{ zXvI)d>`scU`j%&2oSU+-`E6AbsJnK3_;brQvPLn2?}4Em zJvX5HJm{YIm|NYUZUQ_j+SO;5D?4xh5mp|O}%i|cm-n6D&zS=Nf8&Lb_dky;T?j_Axc3g?8j|IOVUtOqQc$me!r-h5$x`1_- z7~5!{@sOY(BseqI-_3X2m(j8W7$v_`pXC*$S@Xi;`cE6)`PN62BkYd4Y%k?BWz%cA zS9UM^2x-vUXm?bsvJ31X%!Co!3rntEj!JImy(+mu2k$VI(F+(>C&!gt&WkXgV*FsM z?^orx;ou!@@01VA7w;;WWw_7*EVjKSL1rw99-3X`tji@)Q(-D}L0Ikj1^JAaIJ3*` zkyDeglZC-Z9fXHELPG4Zd*8V-ciV`@@X3*aBkLG)D}ZvARl7MpqGi=qLN<}xTpX*! zv=wg=8dv?}{VbCti-hWEwu0d*bam*7^-Wo%3$KgWNcRq0tL6 z6ZWCPoTavtGNgGRGn)>bI#(qoa-Ez8vV8!<_8E}ac0?`vyYVuECju(EMmhPaN`Zqw3DFl>b>J~#IS+-it56Focj1H&1Rjm?@yBMm*2?>RPoitAm?S~xX3pztu9IL@)6QB%{`i@?afa)@6Lyix2t7qTzhxq0(hrg#FyXKKG1 zKV7Oz8ogS#>rDSEgHOp4AWeTTY=twESH8P&Hs!e}NA!4qFzmhivpio`sMy!-U}R4y z77Ts9O-svm-r-pEx~WN|EoL+1HXRImmBY&}-r1Y@b8eLP0#<_ID7k!j*@5nB^A*)g zupNvQm=TNaRLY+k(?d=J#hnDheo!#R`%qQ;%Iv-J2)+V_bK{riKY~82*~h*kKW}^i zCN~)C?%mCL&#av)7|}ousiuw6kEfb@}us$!+NK zaaRg$8Ww`p48nuXHm2{#uVmkmb&$8FBtd19WzAx?)syCW;n}IrB}I{jdOPtVTaSj7 zQrUNCEzv`&fT4C)ty~=Zva-3-tI!JSx$Qz4dc?cWKlE4?wpE&$L~ciACduJayG6@C zbIh^(q8!7yB+g0lh=YSXv73mVzp>HWIZ;|X`#>7h70)C>wILzc#yOaH^>pRfNbCS2 zBk}JJE?6J4uO(~reN?iFNFyXrpJf7q{rxy^A2dB5m3Zyld`pbED3RN1awZlrk+M!YV|ZK`xKB^WuF2^M^wfFdajCZ z>4KAk06l!4gN=J`IBbR;O~rRn_iEN44Hk&;DekN6YMq;ixkO2${sc;W4$k}pihDIM z`S|xwbRZfIHC538GE~t4G^*}7{El{hM+cGcJlPW$4@rNq&ib8lzPU-et^Hk_h8I}ki5Xqq?$s2j zC;11pn@*mwympDVLbh6TQ7VI>UlrQ3p4F8~SF*}d4luz3=tD5uZoo1_OijHp*dttNy{M zux@i0)^j}Z2}08hj3t=6zA5p0E)QMJvqciqOP1}`m|@j6R9=6Yqfu;NXneuY-zj*a z=m}>#Q!jmP{yI-jtv}v*A7oYkNUzriAOW@lD7EqZQ70T~9LQ?oU*V_(@4y9bxjuz( ztWao_P=RJG2_`9`}@>c_HdJ=XSlQ#PQ}1(`vQZ&F`c2M5m` z@pxIOG-bC4esV7`xsY3Z_cxJW8ZT0IBap^BIMf<@&c2#*PezVUIXg?#N9;xW1c!(N zR^RJ)Jhsz!Hdo@54Xxmb+}LuRJ@Z|vp6I?qW}uy&VDf;;kvR2a`GkkmDAS-Ey9UzA z_4x+3C6?Q|DyufsCthae&plpjVBk)rzo8uLsZ!ONv2HQNQztFPY$kF;nu$om`jjm3 zedyHHkGOi5WmAy=X`&iRvgNt@Zf1||c?OC!LZj*u*r6P*`2Ry{Q6H67K;KcUwLB+}N+1MmL(bdtXyCk((&zlFSr&{KKa8X z%RL2aw?l5SRaTJz~CSZz(~$ng<3x7W;U(DJurB&fG+-eUB5m! zBqZ_jNj|~lJO>G)wE+QsI0pY__Qp%aV#{(bP2Ob-45Oy;-qF^B7q?xW+(N8N*gTpR zt)<+WhIT2vqDLJ(MZor}`1eF+x_3&{{CM>RYZ*u&PPO>ybh=tjj+zl&BYt!$-9&01 zv7cC^tuz*_ew1sPy5UdSw^}xM?ZMf|$x@|F4Nr+Gn*{UAx#gzTal*P2GYEM=Zf$TjCl@1YLDR1FkjU9ed2^F9X;(` zksD?#YSbgliOVg`qjnK{`}tLc-jx1k=xxZ(hNhb=^^@lt?x()H&=YCo-a9}WE#CaN ze{z&_!l~0dx4|BQa@3#Y_{%jt^ss_sj>op^kVf_(jr0(V1U?cl+6p+0YOhg0T^kYM z$AP6O{<2NWvlUC>+y>6fiR0Wxd{~~$hSZt;(X4-KC$kR=hGQEY&Zy-REvL?WYA0E- zysOWit(`T|7qW?(+G^&vm-^cuy*iGyc(8^uAjTD5%`tcf%GBf!+)Eo}Uo>FtV-MjWq0UUydc*ef&L}PS5C~ zbz;z6lU@tLi`i2Ha`1op^C*`{_@sj`m^pUI!d;x+or8fIZq74+XJFLuvO2XGg>j_O*FlAQD;1~VLEI0>*b4= z&@OzX!)jMpW%M2Oxn5;#qdqDl7|g5AlCGBx&OXWhSy>yQ1JOr4aJm)`NyBEZo_{nf z@?B2Bh(3C}i?pj7ANlM;_g2o|r01H*Oyk{Es%aa|guO!|-rW~zgmp2ry~@08XhY&; zBQl|zc+WHb=y3lhmmIck=wXBR7`Q>D~(e z86F$DK|9biRce@<@+O*kZtC?>*V~&OQp=61e{aXcQFX40_7S$%Py*$BnatV|r_aQj zG``{CA!2ZkE>o_1&#)OHH!%n6!f^rulzH#6{v&Jk#UL6e2dQ3qs z^jQpjWiA`puQQK&zDXmDzb3PdxBT12XPy`I@5>DBBlW}_v*UKweEM@RrFIokPX|*3 zlB`?q(tPIWTGrAo0c*MjOffK>w@2-KSGi0>rjaBtT-Q@O)V1N+A|=fs;5jl3N< zw13e}@Sb=+5ysz0Ph>v;!6&i*F-oL8aJh z`L>5X4bMp*mZyas@RbKJg>Xl@mq&|S`y23SEKh)=LWb=FM)cmPNMnvPwf5%NvAab3 zBS=Fp4NbQM3~jI5sP;!U&1oa8#6{=%0Lj_v`csw48*heVL1CUEH?DHVZg4bj;#hUI zC`U+OR5mdd49oe1TuLGr^>Y5_x#?}y@8tH@U24-(bFbfy+FgjgC2B*xeUq6kcGEXq zx^{G~+zRL-nd|dEk-(@G6XmGaRJ|Ykjs*Xw-ebu;wlV5ujqdF)`8$1EcQW%{Ss)4; z<*Sd-^>Hv9^&x$hUv!-sP)2SAjD@>k*n79yHtbY$aWeJ>nHlYYr>{1^AK{sM!))7F z^|^vH^rA6J@Q$bTAYX0x&h(DmFMZzL01Uk}iP435Vh6Cr)i+t=oY#$13-^UOV9K`De9F0v6czT|y{FcvYd4ve{ z!48wPcYxMs`gPx3Wbf>60bf>kT7)$LYx-BYEwP^3W!p_nrXhyD>nemi z`?WYZyK7X^O8AM;HIL-l9RS0YdwjyP!R1#|xN?=5(_rXHM#c`=w>YW}_xZ3Wq4B*U zGu37+T%PydH$1&m(%c0@i?K^*dA@A^*FV7U4P|GvoZ+snyy7?7ro!^&GnG%m$Zy0W zH_VKDC0^JNYrn9`h5Blfcg@2YCdCi=2??wq!HC8EFS(V=L0_2?z>CiO203Ox96F-$ zH5{jfa4Mt#a>@KYI!B2#HyK(nB@If?H2)}!(U!w>&lQU_m-uMvoqJm4s(2=jL4rOK z4TgOuZDV|+QeB#I{{YcsM$0nRbeoX1!sm3J69U2BI~Pn1q?xjJS!l|KBPoIrBkPcy zTS#;FxNnQ5V0K0tF=zb=hO=3e%Z-=Cwie0_21_Z9#>Zd^gK59bWLT`5^C~br#|6ev z2hw?e8(U-TL(j^X2dShnYIlUYaSDzY$M%eQ4VG*;X2o?DMV~U{_OC37r+nt8J`XM*r`LmCP1qVdx zG*#@DpDF%0;-e%1H+=UY4cm0-fXImGAH6waU?k8UoB>1G5;p42RC8^gEYgUlB3Hqb z2J)K!_fv;1)6*X(TmwA`rM+mfdMc*8pkhr_jaGT$sG*r z0TaEGC1|zn=k1$)Zu2RetQ}owD4)MX-FWhF%ypZYb3__3wzCCEmb}imr0e!&KkoxW zzZVje1H&;FlU~~Vq^EVXR1R%?;b5s1J_OAgZkc-ptun?H>$4mTM{w%E%IVLCwxWF# zJB@Y@M*IUVW-zjhyx%Bm$4GxepQ4R7GSkY(c#X`ovQJ^;%}4pkgW;QxG_Y`YG@A!P z52(+>4l!BUcAd*7P@+Eew(9jU{6dD(4+e)yv-Fy>J(9kR{8>S0RJ8UOFzg}EKY85f zS1_j|7`g9Q!G5qGJj+|AXrAE}xyL2f=OY-Vu`2kmO_eb%*m9Dto!r7DFQDR~6`d;= zACOgHoPJTC9!SGc-_UZ6U$G6{um%)968eGh(fI4xtZjreJ0G|+VOHQ|hwR8ro`2LW z((lY>hV!;3N~TuS=PY&WZ?vZ3YZv(uVR}yGD*c_u?4c~58 zkqx^{hCYRuHNmKt(+#qjp~juYURqzOM=Q?gvgd7lc82M*Gg?#esR#90^8>~MHLI*o z^}B}u?w4PT$|n3?!*b{+V&h#!*T424GuOx5MDgDY+ecvx(^R7=#y@qbCt}x>v*x|WatJ@Y$0`M>LV|DW=r)%B+C5gYBJ|79hiULQjn zLeCpR$jCTV`hmLC>T4$B_JDoVuvaPNsP{y53GU#Lh!uKO(UCnGm(1qQ^_;xg{heO* z5xLQOD(ainc~8{WDk3-N$K@9*y??i+sEf)vYjlj{ZD>2DC*Hjl$I~-hw80NC)SI&U zFw9M1&LcPaJAbqMf@}elqu7%fIdDB+P&;Px<;9R@Jh1 z+YStSmGb?!{|)PH@qAltG&*R0X9q)FJL)&nQSZIK)7t71>_olT<9iJMe)aZo#~_R>p22{Je*g@} zT+!BMx`YaG@5E>n`|;0U517XA>7~{)I24N(&DD>Sy>>0Wj~JiGMdZeHR@pt4 zkJ>-1&lQ;bWl~QqHX!lu<(}+6BA({22Sz*^a^#LC*1Pyx_|TCD-MT&Gz9nVD%2oa3 z$SssZZ{5toWJFluVOx;~Czxm{9)qFxG;4e7r8#RIOXTmsg^(Mi?-vx|((jxQs zAoXASvqWwMQBH|U#WoD_cX};y6T7XI!BBz*Ig_fd`&6$zzo;jDWzjUw_Vk)etM=S7;~iQlyrQ@bpMt(MWvME2ZlYQ zv`a#}i$NpG=$UXZ`N3FJC|$5i;%l(y=F5a0r~GxOshI0w58xwlgGHC;rQ?)y9n!Xek=Ko1 zkQ?hWp~#&5U9Xlp1x8+LW{Hz@5EHuPSfi`U4}-zJ0=usn7)rgn@2Q375|^0k(-Z~6 z+$PU$?)#?rYUPvxdQ~|vg}{tzmNs~_Ljb*7S%O+%SRebsYb(Xr#q!-gd0uD^hI8;T zuYN<4+5~r&8T8&BV9J1*b*E9sGcAhU1;eobCTLKYHpCBSSW4`ETA`rhOE7#s=ZI5b z+R!i^Y)QraYnGQO8xa78`|jEv(TWfu8ag^`_v5-1U8zl~i6g^WjZ`r-w zn}gxge=rGhnt+dev-u5+=Bk?e6ewpB7-}cB?em~nIX-jOkSj2hGY<^wGq85&d9_Qe z-76T8o00do<-M9UNW(rF+c)cmsIbA@bC7q?cgf82rORv=wEebJNFd2pS!%fq@0Rmp z4p5KblVVstvHNulxzWPT)?KJ{YSQ`)E$68XisX^l?@oWDji{EbD&`W!QW(6CE^3Na zP_Iu&L{K!p=2o?y$Jko=vh@}9K~KQBw|GhFV;k!2^6FAoj8k!7TJr@AHTv1C+_pju z7Q?zcMGX6ly3`?I0UG}Mb6HQ%cSWAJKsjO?$3bIM0%2j#4wK6HcDa>l(=MmSAPsl2 zAlr2??AHsOJ=W%E8koKYS-RERXLeKgZQ)laPkbF(84@NJ%@o?`vw8 zbRGI0XBI^LjLkpjgyNs!%%m{yksEtr|Jq-(&tDLZ9YAzX@$YxaQSYPLu;?I9f4}h4 z>vZ-@ty9LKJ~Sby&zVtDzivOM+9IcO8-B>zalyCGz|i*AG?_lz{e+ftz0jz=CeSZX zXYJ*oIdEv%U*8WehkpPEU+67qwA2cgQE|m9EWVv6zE<{MMwYs@{O2Rf_7^sU^Jy4f zD(>7ZV&a55XYhPZ=^?-41$-VW^(ym)Bc~r9cHWG+xr&Lq zWKUJLu)-HJc%nwdokMQyA*C0X-gYdo^$~KzSg3`S-mLM`e&S~fo&OKVQ+!S<7RXM& z@R8KL0QK29c7oKV7u?>jvp0QvL#hwwpMc;9d?gt2MLk_w_<&UfGVpgWAk?+92f1-H zw%UBPPPTX3=ZQ3;hYXu2^~5awioU&&f32IS4`vg5OB4*}g_;9ebR9M(-zCAot>Bo` zg;?tb1|0o3`*{W1t(;G3aZOK5Z3j$b4CX7SFdX z?elb{FapX+^FtF8(JjjO`Nh0 z3&8<=^T`Q2sn!bztPQJQq>K?JWr>YC{i0}*W(wLWbYdk2&Th6DL7%>UjX8w9R z7<}BGpKlKh4hX=9JT)Bx(p%(T-f$`y%=J9UGf9{x`IP5{)NDuB>zQzzapSr5zlQhvxw5=TLvi*?*xqLtJM7oWnu%wn%HZq% z@DqLU$pSBY1~qM5-{SRa?lLtQ4{7O(&qc@?(4pPqbv-K8;L1quI~&1pWL3YoH1wSR z=02t-Npc!(P;{U++|&0{#OD!RpH0IkiQ`j6XvZT|>q*Bd&GJh2T?ZbO7L0frJ8-Hb z+rm-18?XAF56{iwu!qAmv0x}kll&W3hEF_;xdgfvO!#OMWf;-nns?}`q?Ne{@JucaUG}HlluaLlK zeZ)I9>N*J01_kpJdcXFiE0o)R33@|r!uG0dS|PbC~2`$&&-t56#4SSuG4X6ou2sv4A&S7i`(w{^ND9KJ(B{a zAQ;<5^ZM<`p5Ii@oC9M8#;kCYCkxj{9?>(8z*vIWuX)+L(i77Ldgd#b0$}`->#lOl z9{5ntn9Y{bq$hYcvzv8opPnfTh7u(G5jWsv&Wh*tj3XFIu%?+`-_ZJ%6ZK3hFonUK zTVh`FV3jBB^^6t_Wvf=8ii64cdrS08gq&MI|D>OnrY1MkGvnnn9#-FSg$75-*P{SUkK%qB3DdQ8@}zWpp}N9mb^a&FV>JTE_B;=D0>=8Bx#z6VoR6)fER zqn>#xr#bo|zI5!euG96*54oJ27WJy{G?{Ty&sfZnWOHtFa{H2LTVwQ$(fu9q?m^K5U^MDaQua6;c353BLd^ugT^J$DS27_lzNy-2dxTRTl#|2Q}ldk*OF z!s|3TX9-3{0$Z#o{e1j_P}b0%^|PFs{dx*=LkfBu`4>yFWv@3@H!WM0{M@GygG8fV zQ^OszYN8hS3o1#H4JrP|&|B*tZPTC}*(PS5=dhlG*Uf);&-Fk4N{P8is+>#anMuFr zka?y>ZVqK)W6QWZ{{NZQ@ZAdiO#AQJ(XC6RIk?^aEnP0{@t807D$EPUTAN;tou=h7 z>0IZ@D#h=-Xf<&)(%`)|t{1dmIO-2vZt{HCqrvpAIm_5%Zx9SE#mHEXecm$yd9D)= zfB3$Px@YGf9Dq?17JerD!MH}5jyK|9QiD|_2sh5C8|u&=a#uw>Af1m zuih&?-cU9cV}~7|6OfvFDEp7)_J92))`sgwiBW&IPraNH%cU7THGjCr;U_zADme}_ zJ%_Nm1QAF>dmVRradhr3d2otKNi$Z?tzO*yUJ-Y5WTs8+eFNq@E^G_!aHfUc z)_`GsmRGvvYtm|C4?VLB3_ZIa@6%jMSGVGxot)+n82SgLP4BvQeEE|5Z`ccGZl}S} ze;d$kb)krzQ?#9jPvA|R;4sAz)%PI8#hbO@AiUw-Z(wO1)X|s zU*t6Vhqk*Dd~3Hm($JD)l&JeV>bf@U2clD<6%6~1!aQ9qNnOv~bkBuXIdAE;qb{3K zpHH-Q=2ecl$w%a07+Tr3M%r!r!@{z|+M3TCIWviu7&V8$@Q$^8_cWiEm>WB^aiH+> zu*Q?4J`8`IU`mT3Rs3~$g91HXoC*sF_QF?7;rri>bX(xs?gALBozbnF3{UMb4HFrT z(>T6iiRLyuQ3werfI%NcIkaL(a~)Ebg4DGherU95K!c(D5+VNX7+UTNm=a*t=k;7x z=XyH!0^r(;HHOt%$x91&8@gr5)jQbL<-tW1qp1Og@4o!8HZoU#k2=_=Ky`!!bHH$% zdgq*8yH4bRM$-9s2Utt2u*7z>Y5tcNyY0V;af+JS@~>p6>un`+%Y)q9^QJ_dy73sY zp{Anc`mU4a`sv+PnzUGMvPaGh^-H@Xj*X_QTCAMe;a+7DKXDEXb=-0H$ykNgU($1IqN81f7F`94Ra8DK5 zG5lr^803e)`-n8OmZ6DdOEr2t0#?}_yX2^>!TYCpYnHYjOyWjK0+&|DU)s+v7A49N zD_8z4Id8uJUF!p@F1p-~yeSxAdyVcmi~g25H}T%C__J)kGaA+VwR&#a0gRRR8tuy-0W@-EsJvueljgcGIKyPv%jRwOpH=_Q9 z;;qge<#QR?i=GaKGwszem44LgJth|z?l{wqhWd%GC|5R%KisGKxKW}N#4}2jCjyY| z`&McG$+n^M)sUxIOlT=Y=he7xlUjT0=Tg_E#4p^5+{AfHSB)PS3rH~dq|4Tk?_XZl zGiqlvois5>!!@A!o9vIQ?k^dRG+d+C!X6};0$`3^nfZ2Q$tl>uP$Wo_(~K?K)Hmy# zqi6NZPMPWYC1w1E&RtvRnbTmXol`@aT29S18J?Yz+g&-0MbYvpSDre-r%;$ra+>xt zqenFA=>#uLVe)R5=7j=HY>KUl-il{N3R40M=hKCed#wG=XOrH25mRkdFr3jl4}bIc z$fOq=#a9l*o@y;Hw3Zk0PbgHjXia}I!rf7On+WY=NofNcoGvr2l&>=2h&-G1P z){0gT<#d!~tM;Q+#o(m{_*4=7nmrNE(8b!!tlzkTUgg)#5NY5OgNf34;tqwOGXcJrtjF`x5d6CKH!VoI+dyvWs#-NA7D7< zkXvvlHGs8n?*=82PVYgoFkMhTwC+$p!81_v*Kar^b2C8@a_o z0``MRMQeQ7cs=_Z!HD{dlV$7L{bWbo^EmWgj4a^|8qNz~#2Jj4NMnWE=KJ)>S*+RQ z5h6FyK8Ew07+H`yTo=esy=oe5UO%nE>*EEHhROl62D#DtHwz5-+SDa&r9L;6ug%(X z{SzRxQ+DmbfzOjQS&>G5Qe0+-l-s3t%~n2}dVyh^DH8vp+rS{KbSN*B2o~bIQ z*;k;>`+jYvod6^JDqD>`7>=y--7ns~^=1nDsBHO$f#KNx*!xne7AfI{^o&|Ruroeq ziKp1=yOU`ebtAmw`bqV46_Bz)-C>{yOxhn1J__TKChZ2@4QzqUObdT$WXJ6Hm(w(umDo%(^+cgElC;1p9!O4-{siE*nEXe zFT$rlW!ObE>Jq4b!?@vY$--v4+}b2O$thm1(OF<0v}uUZ0z{cVSJ zmgV`+9C!mpZn+10q%^1R?+uwV={;8;@_szJwAhdjJ9sGl#LQQ|aybzDHPUco+0|cG zF*d=1PrA65=89JE2?`F?X*5qe9X@tNw_*So`D@MitE4_UG|9Ua1rklCc~1c&9mv(F z_mKH}rB)d4HDlABYsVznL{D5JGpRM^U#ru#i16$n0s4;NlLts3{5PW#h}PbX+;XEn z&jJt5@A0T3$0@agUS;@%Lm3O|E3gJBlJ!58x9x>q+g?=_b0d$xX}q;z;R6vy-fi`# zZM%tmg$~5iicnod7(ddKze3Y4^=H<=(-+a>A%UR|XegMQNvgD?IXXwxg>9E^rhQ{i zL@Trf!#TK4=@#xW(^|C_jPQ}v-#{{ImBrZzBQqf83o+{P^u;>F-)DcL7f+Ae+sS9h za%&s4eAukP-v!(!y_@tVc3p$ij;8eAw;?zT%XJ5^|Xno&kito z!NfGEe%HaeOCFhltvUdv5SZE<+Vp+YwqdxQsdhk;El2%;al6W-c*(LMjVlN@ZG$wlA*=H|u)ngXaSYO6y~3yR zM$U1`HiRcS&>=P6{@F@(wNk*wLP6Pe<>{6pi{p!6dGG);kq~Uf1vW(V7 z#LyE7Sa@T8gjKRmXh-zXgD8i#x5?C!d#lx*GfUJ*wA?8$?1_7hUC`a}v8@jVwvsk6 zMEaDQX4QrF6TjHBT`bawxjxV@2!9DNEPU>b8QVLKdPv`e`#WfD!*A)x4vJ>pF{y`) z__=N2*CK_mmO&b!QI&58LINY-+T$})o1(Y zB|Do2k6EE_Im2?|C>+Z13)6;%;`>nrZw^WQ)6=zpK8+i4vqU*3>NKk}d+_r1VB`uJ ze(NZXGX{E<;omEWqlE=y=s|)J_CO^!jBWn_T|_9_w7c8z34N1WA4g4TLm+jBladaS zp2buT@AG!FtWk724|W3lJ5;UhsWVrrl96q%k#`kk#_g1pn`U^*s-w+9u`7jgL=R~W zhCO6i*JZ2cp6G?0Q^mq|2E#G8X8e*-!_7X@BFSy41w*|JTyaAaS-B{^0MwJ#KLQLb zg=vi0_HpIRv5TX~rv8^I2YC+i^9uLX9Pd#+Pf^co?c{QxQ7@ghHXB<>7(4x@W)34Em0`4%DR%IwRHeNz3JTlDp$00e@=;rG(Rvb+5gq z-$VkmMtsS4P~zqjGaRvFh!7CAs_Rb9jdkB$sSn(YcTX`I)^>l)1$8|XLKoz zb89fPA=9hQzL0#w4XdfT^1UiO#9pK6iaXe*EiI-u3BLWUBz^K~VytD}vqL%9XQWkD z_Y?agH(KSsm#+mUHZhS_$fC7_a7vb|L-(ncbLZ{p;(**Ze(<@YP;5rv6Ox+5{W|A5 z+ZWTuqffEJaYac{c29hF^~Bey)EmE<015n1js?mwtC(81$+?=+yuju$*ssD9Txf@O zG!+byan76A&GfhgW9Re+40q;+?CBg|XW!@> z3ge{7`=_+){%nnI`NH#ycn(rdQw>a8r1|UEHqRzD2aoERL15~ExtlzA*L#l@mU`w2 z7&|b@)n=A_wR6U1Q_TF*Wfr>v7Dh93}#0mEElr_JkB?${-1ABnF!YZib(yT=dtYeeyL!v{{2 z%K>u)OeHYgUrZg7rSmF!x1y#_nin!NAg7z((x=(D%8{9Z7o`*JFFSOf^w=VRyHb#i zgWCZNb={*^LbshJ+n(vm=>mp2ST}ZldRpg!*Y#;ef9+jOjT>`>!&$Mk8ofuU?q zt9078IQs1*eVTMI)WKQZyT#LX7(5wr(!2vh9h9)D>T&b-$*~G!=Tz~s)IQ#hy>qylG?8qG zNYfsS4bqgopX>2MN6)|eo0Db;81@~P9Fvl#JC&0x5}!ahC4r%>N=pbCf9Te2?pMhz zcU&%KckhPN_Dxy(RblMx-hmO{jW`=s?O=h#t$#C41+PdNZF)Dze!`8s^xx#%>VcsI z&30}0=<;^oDTT3f@&rS@4IPu`PD0i}PZY*UGa3wAVP&^t8=tKl{94bflNsNa^Aj2- zSl3b*JG(!@uzenV{?@3Nw_7iTanhJx)o852%rY5b`EbIazx8HkR}&1Mn|b6go2#20 zj{ROqYCEU4U?}y>RmB!3mnm0WVVpFfU?_FVQyn|@d27*0VeIT?fhhx~eAL~B7OAu8 zQ^-2l4#pA8$BuoA#HHuFt1xzUPr=xe>Ckt<(=G}0P~}uNiJs`7|P~VA}(On=I)L4ObVEC zVD{ZBwRnB&!58(+Z7?tk@m@`f^*(g1gpb15*%eHe{tnc{6Zv*-$#&!Ko@i%R7Ywb- zx|_*aHT^VP6$lCJoV>wMqu(^vt3N+)&iC3B*}$-0$BeJr(|b@(7lpC2+bNgR{5=Gp3#9}?@gKdXLP?Vi+R3R(I^;Nq>^2?@7Ue?*dT?mv)u%Sy1rej`nXm*JjW@F zljfG3Tl4u7JGK9MW3-;hdQ)msXW#PkJ|8~cM$a?{QwX_@iP%%ov_+0UQxhA+6h(~M zGb20KKDeVK?l4{c*>SjSPHW_b>4F?z*&H}@^WOA3=T)or!d^xz|J>*U*moQX+>&-Mo(&j0DpzgiMUVikKo>>b+bD-Lw6Hy6mhF9C=;nHT znucIlpXPT1npWGlLz>Y=Zbp4r!L&yj`iYI&cWvKx;`9_LH(GypnJM-u@Wmgk%gL=x z*^IXj86#g#lGlxfdjZP&$43{$H)b^bi?y$n*ewsNe;`UWgG_vejHcDp4)t(%E3$?? zks3vts<+SY=z7a-sfT<_%ztOp&f^mm9oXAm08<>29Jl&XD0lw9Wc$XNz5>IZSUY)q zm#>Aq;EO5aC+{6;Z0BF|V{OE|3i%YqS)<+x>Kgrp-teSL8xW?kN!e-B$iB*Xl!GT- z?7hZwqvxQ`sP_ZoX{ZBrT^p|{dzJCr)ESl&eos1aGr!P|r^QC@@kdRu6TlHX9t`Pa(@&f*yg^JTmP1xdCHCLdJT-c;#Ygi z$5C??X*jm;{5Z9~ilf;aq=BNDn_3!MHQ8J>8g*U&&X`m0y+*&WCcJ?252St_95=n` z)s@vS=Kg!{RsRxH=5qmxO~murFli4Z^B0<&RM%OqzgRP^ekkP@Jm*aQxG`;JLu#Jt z#O9!pzda!DN~v24BY%|uosGRJ{E<|S!!*yBeG3*3F*Wf)3@uVTLQ90t>S(pc?AzrM z{Q&cigVPN8jvYk~T|ayF$9*ugeAro70>%nyI=ou4ve(hrJ|c}^&_lHWS})zVfCJy# z_KxZYhCLCzO8vcp%sMbP$@^GZO{te)|60L3dGmCPCrUWmB(@ zaW8<|t?Id{?GiZuLT~ClQN0|adK0_HMn@LTc;R$L82@rom?o;)?c%X%2ZNwD_C%vC z*YSm92a3Fu)@Eb&x`kZ#o7)U&WN*-@1n417Mzn)8Lbg4~trYsh9#@ki=XF;<>vOvX zhI^{{UOczSWziuI7=E=5t^H55qm)zWm9&!;`7OZi=eppT$c;})QBzwmoSj$fKm5Gc zprq$yxRGS5X$yw_PC(Q6Yn$y`Wd%b&5u>CJ818YMK4M?*NO~j5gA`xo^Z`Q;vSgoO zo$hKP_alw$)rX2v7UunmW}1!<~gb4hdto&G~s&VVpEtFeSjW$hGmsu(ahB6vkDf-bdBf zGQVT5)oN;Q`#bef)Ag^sPU9nsYYer%o6YWKH=BecdjTS-C?YBd zo+zS-q9`f|a)~G^h&Ku%m$#q@D)Imo^!rtHPiB%#&xHK>-h1DH?`>wLrmL%~tE;Q4 zt80!CTgnuuMCJW%fQ?wp@XICVVJ~aVVzsP*y+l zdCq19rlKvi*;&nNQ>uA~FqJYYAHEgU>(kSC4ft83nXQ}6d#;JDcht10rB@}RSsg~y zN#otI1vkE0cK7;CwEM#PEvtj79*4!6RyF1<_Kdo!XiJ4VT^;HuDph+FdgtGzEs-`z z;ag{jifUv_-DncAW;1@w%TTnIsvZS*Y~}^idQvIFQEqD5)DSfV>Xo=U+QhUTjuKVZ zV_JrN>!$IR*fYv>G(IDZ{nr__Yvi?rVADk3-WB-(h2wuc8@9q<1a>Y?b( zm-g+v^Ny2;_2$e798cj12Z7M#SUY3+3$9D5;{?)pdFDZ#u|V;)LF@>qA*N^4c!xuM z-b}?&XNLw&(>NY>Xu+WCZs=R~8Tdw2gMt=-P&`G4Cb>&SZJ99`5b5_Xb(L_O>Wrgh zKXv?gkBCfQzkkphaz&8l?dE-Zs=D=WND&=`4PgvUV51B|oG2jO0K0mwL$&;$?xLt=# zl4bgitM^3S$giK#Att#UW&fOZW*YDEv>du2T;DhBB9tKr#zG0cq3jG4INDS;s@0;& z&NYD0nN>)9GnAn{-qv^Tykf@U$7bpEsLOc6fxKdmJF;L;hhE-#^`DbvAUo=@t&RyE z-6D;nxer}&?=P9(oh{wkav`_1T6 zxoS&=wzq(d_QtnluY7f5(V2AG>wkdlSScXEHlMR-pdL5xF2ry!h?UN9Ug4q-a#B#af&rX7oLKADmfo$VajxA#@Tg=t6?~lcwF|e(1tV zK!|G4MvyT+Z{DN(x~xna{8L*l!!ac$=jTYrBbv@65=PG3CiO^7t}dg7sIi$^HOy<1 zJTj!O(LW}6WF&XgkobD^3^OJ(6V(Hbn#@0vJ0^3M>;;pViTDP2Gsz%~)=)hzx7$N` zBR*Sma<5%~ESt%v@vX6$ycidLphu!nU%ovHQKW z<>xbm&q()@?I|9Xal>n$R>!`k9Hx!}9GNXbnZMT5z2og01CNRJpc=Ih+;TO_5ODyF4g-vTjVf;~D1GdAXfY@;mYsW1hWhlR-L2lgvgWBTb3GKs z4- z8sD_`I{zt zx3BwpU&j~vv`74o#_MudsES&e{?)ep&)%InnaeP*J?D!uc|)I>_|gyeb|I?C4l>K3 z55A#H$CTtv-E(Tbr7{-sJ>f_wQ02JCKmEhEs=n={bM*L*l2pb*;z@PtIO4V0d1p>W z;!uwTwT!A|$K!Dpcthbx`n^?mOdYWo7O|%J)Y>KS#3&6P(tN2%@0tt5$l5XWo$0ks zJ=v+t=n1_;b^A4(~!AP-=R`RDkKAGMBuYJ!LxG=6mjVT0&7HQ&^_H{_~t<^}SrDmxAzKjGEZ zw~*%JKywStm&j4ld?X=izNOM0bv=KA2Q*{o- z)%dYZ{{b7`XX}<3w?ab7hh#Osd)$BxfJm>0;-osv0L4jl%+BusTRqeh9Q@JsyM}~r z*6Se)-&x|R+8BQQ?@iZqqqt~k5t~AIcAnKqcq4KfeEHCYuh$?=AbJ( z*W>cJ2PVU&z-pKK=LbN@-g$lP{9)@CZQTh-M@}x8@a@lPX$SBw*@s8*TL-`QukW#8 z`Me98(#R5XePT8_KKcT_gt6l>UgEcNan_Lkji@e8VO{ZI z@Rqw;70=hd>udU*Rkq_#x=^%q;M{KYw>R4eO|D@(1CW-Wx?SIUW;VM#2bK_8id?VR z*CIeB^_TEK;wvw&CZ@KGeA)6y~%+DE-v1}gcN04^i(>Q-`^XdKl?m08y zyVcs$6A(J7aq4}&udQ?2mcBaVdI{Js7`W5@LPT~>F#>-YhD~1Bo1Y7%q;aNF z1s`!|hxyqtB&Af01&T{+&{OnJ*1pBdU#M1iC)y*u5$Zgr>Xw@4=pYeM_cokZo>0W$ zxb%hd+FWvJP8BGSb7SH@6VV1S#R->x&py))7mJfs7mwUu{^zBSKhM(3y^Tzf_?Q85 zue`H=Pp!ijV-?C{0dlYo93>q;F)e3m>z@4|VhCg&{whGo-pRRQOuP1v|AMtM%?q?J zq4PSV8#5luZhYaij_@B)OR0a<6tuWhjP1+b>fUfbXuuFz53nr*)g+@QKRW&Wvv2)@ zq?e3KST!9`h9GCxU;WtQpMS!qVn6}pdMzOJ0ol}S&GtdByhW!~s0<*V19Ao+PoDnJ z^0K>E=1K@gi5gpGt~f3C^NZb|sJ~=zTS*(RnL^kdQ5X>3qN5jI{qC}&2PuDuEd|HS z9q>R=2JKOB6lF~E=7DB1D%2T-)#K;Q-f4W6bmE}R!PeWerlX~l4-(u-oaaVD-bpKe zjxu%73wI5=f1;|Q>&1Dm&b z|D45J{_4rJ!Hz~&Xr(P?vP&v0Vv~J9=^s_=5R-jpnwLPrseQ!i!};YI`?qgSyA8CF z1qr`cLqHmU>hVK{q|I@(YR(WU;^>aUSv3Kfc5vRD70pk;+Dy|fij0&fW3sO)scwgQ zXaui1y1en9n@4>EY;t5x4vE#&&xiANjH*|oBOtPjTJx#7)3dLbPtTqB#IV|w$L34@Bsr$BwZt55Mo`v77c0L`cX0lp)$0*2piuec>Oq2_pDLUU!qMb-w%eyY^=&LlBhd3%97sY_M?>u)#Y@bA14ibd;T6^>-k9 zPE&kuiu5%#=n8ZJtxu2o(q0Ol)oUxt&^ct3se8F7bNJ7(MQ>g|b6&NyzksC?-Zjbp zCUZY`Mj5i6@`tW{b9U`H@W-EpCnV`R>x!9a@TWhYGbWHelxbs*4#gWJ2bnd0+jD-O z`qitY^~Mv@5GG&5l=~PaRLksmCbY@by9qW-&D5tR*k~-E=S;B4mR?p{jME#^9^KaG z(Bs%EU@rm6+;@fG!O(YduKB6|A2es752&86z#8i9pYv}RyrbKiUVwB31iV()qwrGz zn~5(#`VWpNHi9%CC@|3>lA|W0UiN}XbcW=0qF)S}k2A~1Yx4B8uLg(_l{lAZs~q+B zs;6EhR<1(_KiB2U8z)aD?_Hi$l9^ zsMJ>T34@S)RwB+!runk>VdX#@AnR$J>cPGOf}1>dX2FUWiYrVY(MqPn%hH{ zLsx05DfL|MxLT|i-u`@M@AXrgVf~|(N##2N4S|g)_&j~-(*urHu#OjQkHkLJw7=R4 zP=A%6E$X4UaqArizN>R+MrrS68&v6xj#n`Te~?vY~PEywZ8YQ$3vT zq8_=DNc{Sa@W^Ck29WHP%3S!*?F|>4xT6}%(DzTF57cwLI={bqrDrm>WfuQyz#Y=p z2X4Rlodf$Ti96&z1_i0C#MRb@YK_s+Bs;5SZHxkKG&>)e{LFn@J{(P}U769SB1~L! zI8LtK@1|wZd1E_&*P$J0O_>$1qiQkG*@9nC0t;~0z zP>pKO6wi_hP6NtVAZ6z(K!^vGyXVeq@%Qi94Buj`+oSmYDt|d+LyK$r{Z`|eH&6!ph`5ue<1uvjpCT>*Hj>$1 z0axR=_gY~=Ui@qNz2{Boi1plfY7b%!6YJ6T>t~@1>4TM--+Z0kv&O}8mq*Dr037Ol zTG~5|{cibLgN3A<*az5rA&GwsZ;sb?9rw0fNy(!4Z>yjjZV}3LYpuwBec6-G&Prsq2!9Mx1rpl<#PFS!ljT0HOKRdAa-c$o99dX1=j@ zc?J;DCg;#Dd(tlH{S z>z!B8p1Nr1{G#+#2d7^!3RKfd0{x>{|3D7Zc9q3HdM+xPLg$Pq0kne(tmAjT3KqWQhw9DF}$SbQWNE?_1x;+jLG5jUS9s z8mA`lmUvE(2T>1ciq$o*9&uK?4BEY<2z~HPUC%s}AzgL({kL!bAZ^pzvL1{Q(`@wY zhdnhz_(#u8d#cZvB|Y||3@^0c0hBGBa^tzz9@^aTbcV2CCq}jKjDemr&4bSEF|CK! zKZ-BufCnvZ6noWcZhLP1Kf6coVji#;r#&Fl(yDQrf2^^n{gnpV@Gbf=FuzZJuCch2W4hkenj}Q}`Aoi9C%u!2_Gr|%k@)>N8A~u5^{`Fe%=_`zlSg9w(9k9d zXub+zyzUBK={vng-#MUw<{$Wo71`)vi%pp>_MOL_G-6iwsq_3yHFIK}P>P^|c}Q zzdRCUXuSYxP534oq>gAG=oX{#-jSi%hx)p@0-K!8EWSHdknbi6*4+5xtgqVC`kULs zEAddJFoo{Fpx%j7I&^yskj^42njDReoO@2+G3ooJdzfmDN;dI9%Kq{AN0)g+o=}Sy z${)C~;}L&)+E7qSQfm@ZB3qi4FW$JY;JNUOb4M0)dzd!$7dlQu8PZ6XjJmjQ^#!vi zx0d9A32pR^M2Luq-%^$tBq{hdE4S8#oBL9Pz8rIVAOz&8eLj%+if^BLHXw4AG1(cy zD50Hi6WU}M_4?=d(x#s46){EpI7Xxo;+Y|Mew|R zJt0;2nh~jG7a%lRKK*U?w_{%aki08op@25iRU6B-C+ze3BM!&%YbT6-F@64FvXwD0 z;@^wFM%fs{s|GJvSgkhM@p8TW0U&fX>FdecA}pmb@)qK*M-2g#5L3oFzfDi>2W?p`6lZjty z`{(;a=uICDX6~?rbVYlpe`Y;=&gv^?JaYgLtO`hz_XeaMAiEY;O&{{j=tGQ+ zEmewHz=Yv=x>4`qhN~uL0fN9hnio9b(PbWlyKm0^*Y496xAg!*)*!H{GkF#-5?L(c zz7h-@>3UE=J(uNM{QHKFipZ)V8AUxNI}cK>O}+w1Wk3O~3gUA>U^2r_ToLM_Iry!i z9S+r;x)f1iOf_Uw{Z&Kt31!8XH-Sy@eND8qIPByp^`0;8s{O~@=gD^~_gF@Qw$`BS zx+a%>(&D$pB%@>_fd}}~tDnY1c8%6o_Pb)(6hNeR@&S~gKK1|6_3)L&*C5sq*my70 zWKAnqyHBDFHm)W#NPl_dn0x&q)&t|-;dmJk8YL~u`@a;b-+v9))0J#klf5a~KPzRK z!C!pcu5OFV$yXySuI?oQ+v0a`zOQHD^RRBw9*jqlYfq5QR(x^x2d!_r>}QJ3Ks%|P zz9>Uuds*(|A71WQ^)FclkQ*hoE)O_owpv@4xI;Vu)oSh(qf9!mRWAQm=MyjNw*X~m zZiIqS)0SAzcv+9SOyW1KhawFq4h8w0iKB^Uru>wty!t;rP{793S;F}1F0Untiq?ELO{EWUjB4$|ij6SMNcz)LZ*zx(Y zmv+)_Ioaf0ak_n}@HxbNojeoY#YUNykQD!!zrVg|%Q;v{ASnRL!E`_<_G|jKerbz7 zx$zDi@+=_d0P<|}9qTtO>U~g$n9PkN@#t;!9#~>aQ<7dORcflADHkWzt8Kr1+(UO} zW2L9HR3TN+6y|<0GRD+;N8Qqh%*{Q^bJ1Db|F~|okSfAvvNFoV3JiQ}RZYZ3Jl=Z3 z1??tz^3v0eq8)cqzbN}hEmi6=YVN4^Br-eosuCWv24_5WLBnbv^x*dJchs%{gw{-Z zFBx`yr-AipeMaG9B2+-+S%0F>Tv5 z>wH@NO`peZ$oA#jt5IN7Smi7F+ ztLG_a_Zu@`mVx$vPeR%?3eFflsERB|w1(!!(`C;&7v6GOtBc-jL6A<|sn`Sz_{u$w zwwd!jYP!1&cH$O^#YA@GmuJ4m8#JRo`NlU@Z9cuH6R^=5gLf<9HO1d}m&UXr+7VQn zMmzGTuNos}v%A)hul4x{JOASLAi_A)k(p}}*U5H>I_C5?V59zNcg^z0uWB=f_HxJ; zgWqL3APoUI`{q99{r+>y`V67vkK-9Y>HzXmt*xb%la_u82=xLW>N9uh2-a$2#g0wr z?*)Hu{${FXJz+*y$F`=zH#OA?zM(xut}tu>hvUxfW17Bx&zyTCcR-uEr5BA8`*p&5SDoMHk4Gc~z1<5j!Nlvp!?*2z>dZA?O9*;F-BLfW z(O5V=_ko|9jvb`=VKIUy077=}-u&-=JUDsm6^xDL;9fvz)Q=qS)u11*ZcAsnJ8KSLr_1WKy%4#M?Be!Q2AY?sd{j;?BfD@;zkW_;^>S$p#N7cQ3H{_buGR>PE zy?Od`yA11vROI>{$(_VG!%($!DDw^u??&(z{k)u9t3?*kA=zze(`=i$)K&uREatJ8Bv> zwHH(!0sgTv)?G2`AqS^}0vh#|&USmoo!?})qyUf>fRMb6ojb65SnFzx#JeBx%r~gWf|~b7$9UBpV4MltDVyx5vNty!&C+c zN&KfjRc!gT$-o!&dhP&(qyUFVzN8=KS?Zmo~ zWpifr(MBU|igkccdwxB&QE}16Kee3{^xSUr5Aopo=ZEia<-3L=Woi5%yO(;LHXASW zwWI5S@+bS=HC9ppY-;H}7iEZV4eqII_4gMa?9|KL0|?2^Pp*Y0c8@&0NkY&I4+288 z)%xb5pDv#Lx=JloroGJ^J;r*VudOtNX+1WUx7vm^wJF#>_+*k; zChUjH<0PwOysAO@R%( zexpQKTyu-d%c}k!)*SN!j&ahg`4J#w-QN0==cDtx4qPs0Kv*bW0MY`GBWpVU@t)`E zds$f(*Ae0*tBb4N|}zl(xIzvUlOge7e1q2)c6yfUvZ z;`N8yyx;xGuS*t^6-~Qv&=gnQB~~91*?kobB$X$pmx8uqoRcA>n-=Dv0I zMJM{6V6DTylV$Fq<-L!@miFi_f;~)I%JnFAm(dTtjJbrjLRz2#~-boWrF!}u<@oMknwV;6fE}p6NF1qi%#d)BBY=4vq08$T- zTCWUj-t=K#86a4lQlF~Fwz^N%9%r?$Q|-~b1+JJ;8kA)s$NEsbnYH ztmmZPa+<8C`_?xPJ(~Um={IOz+Tc!D?Hl;UtGLsu(Qke=$}sQJCRAe6%g_bepz#6S#i!4=s6_P zVVzhv4KYc8tbhN*pPF1XvOTgJ%QA^lg`HK_uuq^&eUv%r`~CF>pHROXWuPZ0z;_8C zgZ9Px}=$)5v`b5b~7VGxMMO#*MnyhcdKZ4+%-!mx1K+KU2-e+J7{5 zY>4WZy*9=+behm&(_dY?)_YdwWZs)%NMf82cC8p|5Ua$UcoR_FzaLjlB8E3(D)Yd!Jq$5z4?~Xc&Z?RM(|tguK+VyA*Dk4=>SNsT6s&%0@3hg|Q3g7m?CVAM3!Qi*&+%u^e;UnX3iu4v zT0lt0&#GCm{M~}LKLUhoK49Ad2+aaZ$KLc~i}j~|%n+VS_5ecr3+~|ft6Y;Gr9C6s zjYT~tJRsHp+g4vb_q%(FhI1Lt0JM9Yh$(qs>E&~4bbgDX17RmpAvF)ek$fkm{K#)P z{N>>7yY@0R_AA#zJruF_*o%h`Jb2@_HGs&OsRbZp*`2=s@u|a{g^1fgtRZu!+>5N7 z5kzf$((#VW-gS%9)0}vhdQ9<+cIk%(J?cb+OQn0 z^&YTMODpe+yjJh$KkwG-QS=WeFzr*O&E$&~)N?5F7APaW^<NmfsQh1CX|$x=qW5om-3> zOnZkC(&`a0DxaR7{>CkiaTJq)-WdhJ&b``m{-d z)^#SHJ?MQsqn_#4qB$GZ=d*-9*f5~Rqr1<(g=B|v!l37>!AeU9w#h4>-grjECl>%h zr!@fzVN(VI@0hl0%g)Br-kOP)N(k&qUKNZz{2z4apnfjDKru2Hi=>qm9$IK5g`XT$ zt9Me=K4SF_$2#nZQ~yj%$P9NJ_tDWm>c|oGu5Ul^n#RwkPd{<(RoM?3A|Mny(vUZB zr_>d6IOYz0wfC;ABbUkE2Co4Ldobu$-MIVpJF4wGMTb0rdPw5uY$#0MyP`UsOeG0H znFSK^_lSFk?Ah4oa~+~yiJQs+_x3TD$yV+=1EQ8H1rH!q3LXHW_Nl66XWQc<{@}W& zmrUz=()~-p9cTj@jf(gx%%^H?xqXiCSM`1Ll;SIzy!a$x6H}o2d`UbuI$wfm1~d0m zEUQ7tgf<_&_i^>|d+!(7&e5d~*HaF*p4NWe>*tBP#-4+ENQZz2t5Bv9%KY*DXH9Bt zy|h@*nDYS2ktELS^jp8?J%9uJCjZ{&GAXT z+e~$POwLiF0Bt9kcqfTF=$O7w3O(w+;?gO-mu$i;L;g2{DDuC7qy9iXf8T86^!g7! zvg%sOdIC=4D37@x2~L>~j_XhlakOCP+B+95dM-=$59)am5E}KqQ&*O~c-hAr0Flyb znzNK+CrvZqvQ6$5u42bGQ_p|qv!3Y`Mb%lH=v7gA;Xg&9$cfqFj z>#c4`*dT9^>n4EI0pzi;D?GgnYT5F>A3># zIraW_V)kjx^Ao?WuTD7=_>5;FZJOnv=9@aFl6w58N6E|23M=5ArGvlzX2a%hAt5wM zz#g^bpq50nrcl$CN~SI~4`w_k^iGTEqwAJWEOY9bVm=`C(CcT;E?ai%=%?n=Sm2>x zk*#?p%G5=fyv21FoYtfjqJ=g6rf!d#*Xk@)>RePGfC8FNbLaTpZG6~A`T*y)sDF-c zUMaOjyj^nimYero@lV?6xu8I}P#nJjLO#OT*G~MQPt%(Z0wO()p@7d<7DViVZ`);qRd`>w98L)2DuE0m!=-7u}|i){`r z-I^ZNJ0U>GXZQJU!&jW}?Ca0!y*)wJ^M0$wxy|1xy-bJ91cYXcb6Pa1cXV;h#(>bg z4Xyk$AY>!8`T9uhqvwA!L5Ca$gjNTI7u9%iTwoHNXOdpS6Q38J;KgkkHJCcf{X8Jj z@=gVtN2aprUjLdaawfe%bBV;Z`2`^%Z9gtL@Jap0?$jaQ146TNWM!S#2fw%DZ=JTk zBxJ<3Kjb{M?maE09xYY#Kn+o6TE%yUd}W1Re@UaN>l(cIQhQJUPYunQCi5xuH8C4` zm`c3)Fjv=18PW9O42sD@=4X=laW9FMKI7Rtz1J6i4rK^?>hA)C){Qr$y|Sv`+WRg7 zguGv9kEvgoHTm+FMW1eLzrFF5mkw@D2lFtE)5Pz-q@sI!EfyN~g`WO4$Ho@ ztm8hN0<~5SpbTm9BddpW@3FJ>TD^=~Ur#|98r!X}TDQB+%KbOt3?ZJ7-Bt{xLy)}D z=XB4lRl5tz0bBT~WN>^CSV_lc%=#&O@^y17Wp86WtIq8h3?Qdbktcs^(V2gboV0NW z%8>Lz5}yV((tI0RJ+dv3f5vh^$bAfmI&*9!;LZ!cGgm&kcGZF3*4-?yQG?WWqS_iv z#i~gx)1y05*kh(iw1hN>xo;f_ii9-?sn@;x3tkaD_b~Q1E&z9?z4laS^}%(k(Nbvz zsBIVZibdT&iS4ns>{1zxsf;;wd&Vshv-A_EK017Bt$v6$(d?a6#!o6^LCsOqy*7?E z2}c4U59E8wi|79GMfT;7qEE46!EmsUc(q)o(sOFrQMX5(oi!B-E?X*eV(n}1X?xki zN63qVF;Am@Bp@_rJ@obLipl*>_+3goJWOh;8(>d@bV%a&f7G&*l7uL~5LE`M$+8<< zF)?`f+$OXxk$ct0SMO|=+}Yjcz{%fKY#fAou!~L>N@6eU#k(|*CTb)NVy=;ZuETw= ziuSl}TYLF4p;7RQk=j8uj(LDk|I9w!_sQh`EAP`Gs{kRd$C$wdCr=+$XQ~d_Bq5z% zu30PlEPsxGz&vp51B7gSe)^RcZ2C$sb2cDU#{2Cr z6VBPU(xI2B7Po(@+vEpM80W||7N~D59&ergug!hw0HNh zFTozAo+Dnr2}m12(*6}X`HsW6+XRIA^gqwHYnG>RL}8O9@18Vv{nbZx+EVE`b^jb+ zZ0b>>miYhXUMOB6*0f`9+V)Xl{dLcYUVu{mFOZP8KpTzvzQewDOnUEj#64*m>G<~P z@x`W=M78XEyi)iRcTH(JJZ)5)2BM`jg4H}oMJxY=dg_9sf3_+5*XS{Qq#h-Gpq5_s zIQ?&y>mIAp9IepOXRjXZ?p^yJBFX+wke%ZziN_aPDw3#{s{dxaqo(>lKWp|~Eqo|V z(+3^CZP8q;e>A!N&uf<_`0y?-)r*g zIe`WbKEo>`-jRNLxQI}h^ws#AZ*2VXr{de~*mnN6&D)+4BDe5cvpT$e_?8;2K^x^X z2ifBQp`F!j`FWR}@@DRt48eL=vG0t=BBv6{kZ-W_YbU?Dvf03O+*0_Dk=YjzTGMpD zec<|+AG+dthH!T$>Nm}Uk+?F_Ac~!D)T5ra)w1)S*9VC_(0rnf26 zs_~@jFKvF}NXjrH_db4KBmBLm*7WpfR@4H~>Du@?zH^pZAE-xtjW@&|%c?{3{;1kI z^aXv?x0H}0%ceiOxpm1b9nu95@@>qW-Js%gvryu7JLiJAw;S4P#`IllG$ zH;)CiT&t;8OYeVPs*Z2ZsV$-7%QyepG{;#q&y~&X_0RfFaz>|l`#;Z7b#EVEUJv|I zMAP*gIC$Wdch8{R3WW7gQZn;6NIFh58%I+~zrA_ni^%4=%Xe}a3Ndpe?%V+zLlWaD zG)Ut9f(A+4h0`F3-zL%^iTiUJB++}UK@#`pG|2O4DaBWTeT$gfg_Q|77+JV}5=7 zgXSnB!;Zd|_2g|k@4%1qUZ8#FOgbW#<@gy8@{bNJ-qrEV9cvZ?LVjhqe&Wxl)lWN& zcgZ@7w<+qUX?9gZNImU4YG+b(NQEaa=yI1h?zsG%nkQyd-dI0Mn~y#dhJTNA+qiqf zX(#-eo>t>+QBQms>>^ zc#+Tc414j%>E74CQ5k7ra^8%-F$xMULmBFYitqAzw+&4{0cE6B6W8XbIRE}jSJQAt$yf@%J1?rnAc>kCUo9Ar_aQBP-Fu=jq@CO-|-3lNg3 zh1*yD+$wwloeYwFY8Fq?kw21{id^qqFEmA$TQ5!j?z5{AKMD$XWfac?K=Ev*-&{oO$? zb2%Uw#S!$S@3#uu?i?pTkq%F?hB0L;fXD|C=fA#U59@crF-nmm#t)Kwo$CPiTeVR;5&G+7O zFrq%|BDDYQ{d!zJ_rPTAr11QMp8Ek1nrUBOJAc^vMO$g6?I`rlQ9$Yda{u}s8K)olfrr=oP zDXiP0-Yt)x8`G6@qYhE`srk;gbuXl{C!T1#Xnp#B+c#kU^Sm~XU7Q1{RO z>#-}uH}z;#>z!1_sao?Tt|aVxP90^v2W=uNO^<^di4ZfI96#?Z^z~hX?w{x@zNsd3 zB7OcE^W1Jg>Z45CPFoIk+BK~OYfGL_O}{tA^H2QuX;B8NEQGQ-jsROauz8#J&sn_X zubzNpL&;+V)O6@^&K*FhggpK6< z@{Il4H>Y8}&9hl&_yqwWt7iO=A!&0Qt!Vutt+ejv3d?TV!Fh95G(X`MK%{+L<33^8 z4ZiW`fBu~2qS$ab1qMqBo%!C7|*;K5E;ezm^KX%zJ;XEW28iRY}rC{qtqKlkH=aF;JL5CyMc`v(yE`oYo1 zI^TT3pp14pB#~;ZjJeH6J(tSZR?C}uoSwQ-?Eidm!m2lCWaf|2CGi|UXit1Z^;VBR zJ@#ocgt+sO-~F$^TdlAM zra>kHat0u^KdrcYU*;bt>ySATQugPd*FDWTuFxUN03ixo!~c4A#6*`%hkPO-rv=KE z*P7a{h7M7WVD*?Yx1Okf)EqS(B`iB?O&&jf^c9#|4%E6TC5?nL51`^Wod{9ey=omF z&jZ~XXCj^Ns2`E0xp6o6%DA#Dq=DSLA$jL$f@+J!-zGGb5-+y?T_W*Nv&10#=VXwN8we zM13v^Y!d6!YCZpW@q(L;lnKKuoeWk;W6 z&UUDBA$D>60@}!OxcAYPJC01~gnY#ClXE1Q+7>g}wUX_rT7+yVz;@fkpEd3O@%p|} z;xTbQ`jYTqbm_lgU)HQ9bW)H`{dL7o+GgR^y{gub^6m3l(kWn!AKC<~3kaPq?Dasw zvgdm{S_%lYrxhS%MUNTRqsx1nNB@7C*+438|6g$&$h z2rD|Hc8$Ci(-;2)2s|ZJk0QGYybE6dYtL5c z)VxkbdaG>__1rU})LXsVw>?BJ&{$B*b%o1U;(?dxudB}A+A>_D5z3J7343h?VNbco z9|CY&(dL_(_fenJ5Ic>?Y4`yZDXqW453LDG5Q)Y z%8b}1Ji!y1&p4y+FWreY(tLoJ?4tu0p;LQ7O}>v#q6lrN$V!8Hj<5BkUZ1M%MDgRd=8T737&bi(8H6Ohqf*6%hxhFK@)0PvjDY*Rb zx|75T;t3+@6=AQpzb^gAym6gBIs=(yXhy2``km$HZd;cCueC(9x8|*Hf9oz9 zw4xF2DU#)yjX$k9YrqC~JpOn%W))W`>~Xx`v&V|R`VL!Dy|I|h)=yb(af$ zX;1XMT3B$`_WQ15MY~s4#lzowM&Pgd;Kd`iRPO4u3HRg)7+boz*H?vEwc_*Z{Q!76 z;I&6x-KgzDeKw|#7ZP^wFmMU~YIZB@0#S-eQf5?=80^Rmm$-Z$zb8@=2$f`%dLpi1 zAQWLqcIF34b14HwRbC)alHtnF5BQx3y@bi=b@{>xAg>?ti`?VhfIkt4@3ogCJ`G2z z04Ow|%JT)>F)uPHh8F~BlzPbUkaGM3kJIPP3%Np7xpDUy0!gGQ=m}>~rq^6?X?!g3 zQj;0x4tawSw3fazpCNvnBI8po)mnNe`OXS&q{yk$5L36)TT0y%`^+DE8w>?Xy~qi*)(=a!av zGrV8|rO}3{K#0WtxYOke7P(-*X7ei=FgN7#`tt%6&Qec?ye#oXczr-TEcbdU+INbT zUvH_)SsZo;APXLMNxH5!Vg4UuP2(l}~j z8-~Dr6qRsYh~$H?8|j?Ne4da6Zp2oSqyeqGa$;Q|SZ`4(z5*1FR_4U21A#8dwMnNL zDD)dAa*87Bk_`@pYygu2f)13zKpUG_*K!Y1PHF9u*SZ!BuW%KXdQk!YvOHvq9&nLO z;V#J-UFHc@35g{^C7l#RU^*n;6Uv3E5pN0EE((r@jL89Xfb*JNGC)J zoL!vdXh=TvSzcK|fhSZJ@%k(Xh#?yIV(vkP{U9`uAytz_b*?$64lj8hbz6`#01%qMO0)cX`8caw9 zeu*)|6DfjCR#vK)jD^z}4#$=8c?wM8P?7~xK$+J{Uzk+rgxROnS?1NNC7nGwhM8Z^Rb-uT_1m>Ho80!(j;eJNP zRr0#h$^@!C)wN*nHyvO0s9f{9$JSYGNa zv51po4=WPVhHT((0y`VNXmuYp2V z%EOc$7*>5_cnfF@ci@t2uRGuigvbX37g5BjDaQAxNyd8?Iu>2=n5H0GT=Y^D5#3-S zAS=&$00jMo@N%KQ={G4MF-*iLXShRs*e18BPoL0HSiecCtn@mOl|k+&Ck}pKVJ(as z@hIT;pj+S{B6zO;j2wRYUAWxigJV&Dz-vUqy)X@RczKx>`wa#0BASK2k~TZ;bG{*z z;9vB*Y>h=<8!AJih=&2JzjY^w0bU4)FM?gjiuY7ugu*FcHt<=nZ*kK@PoD6e;zA3hmN> zS9D<{1UIWEU-+3Va7jl(%Sn)}KEhu$KT*Kd+4YsMj9F?$TZFqzrbYAUuf z8I!Sh*-2p69&=gJ#05Cx1PBea_XNZmOHfo&L@fAfPKT6foecRPDJtSnMu!iuMXwo= z9iQV68TAKn8EWaB%+IcD~@l;__D8r_^*jeJ8{{olWYBErk zK{ZMkS9y#grO~XZ$C!IY$Tn*n!)QM?6!4R!lRUc&h0qGa9am%LVMw3=`V1PHF^Nk? z!_opD4|OF11VXMV{R+FiPOx4UShN_?0SL+fDsjP1E26XZY_$Z|rx~9}i>vLL-VoMhRCDv{dledA?7Ys3YQpJP<-2EL(o!=27)!$S+!8s3USME*|JqC5}TDh$J0KxDYX3{R-6 zhf$pPXzEw9sev*6k?0}YMN+J|X-J4628-T+J`TdQu%Nj^0fZ|Rz<4jRV5i|NAT``^ zC-WS_o7;AH^p}7}zkzsBya#w$QRIqL6v8;Q+CkJ`0v7#7_sb!1nm6A^oPd8ZjJ1u` zC`^2ruqI$YOOr6ZLLGBZI)Wcqk@kMRIQh_~X>IvJ9xLJsqWfG34M9X-2F^I)e_0(6 zFI<(G-2pLQQBYAyeu3du3<1M~Fv@kxF+8Zu>eMN-Ya9(BkIz*_!D4aGBi_>ZJy4o8 z!(}jdy6{wN#bm=<;E&;s1&AA2T81@t0R_WYdj5^C0hRIA*%iC|b{3Lx1%>j)TPJ^g z@z{nm^MJaXJ#o+oT9?I+P;s~@j-^E&S16L_aZxOhas3=;0>(jom}@P=$3$JANnC)# z_PY$GbpVB^jMcxLt_ynoSo~Ne#o%+oE;bgY`0Ec-bSFO!NAh7>P>dvYrbA(9Pd7quofWQ-A2uR(rrp57g5xYh z0I1sq;}BMl4|VajHEV4#P7tX``_c$E#9AJ7A)2=|QiPXL14a5;xw&HAqb@g=qX;{i z5v=C}BUQj)yd?y-!mAUF()tbhN4hI5bhxo6P}+FQ(}CTjxp z^PU4ra{zDc*%={m!U1G)Pm*e@nb;9364E$nOlreXA>A6vM7f7FPyigHDZeb}!%(q| zk7Zv9g4!Z@aAybp)=ou1rKf0S_mM`v-*puAoc-MX*Z-}s}}7v z&;lIx+Bb&TKoN5f3>0~7tyltnH@UUxhu^mw%(S^1q-|w!kyJ|)bT(HQr^g|<_OfS~ z2!O+Im&#lfH(LT>+!MWVtg6UZMt~HBwLzXR+4!*!ioG2dhqOyKPDad~g$Tu=MD1}; zP&@r+-F

Z`3dVy680!9rJ>Nj5gq)ADs&pt%;)}-jYbh{}_iNYEFWT=rz;Q)s82+ zWh)F-U=SBvTr9iwchKpsLKqU{wKCYa72cqg28eqTRZs!0z_!oyx`nAtE#!5-&T0dC zMEKz}jur29eBxc;6Bjx;27EBPlS76VfeC+gG|3e-augVhw>l~VMRx9v_*bcYy zN-PVi5+54KN*=d?Vjwl#8L-7o7&ZrLOx^}&4%S!lO>>XozzRlz`F6LQo5~_ZB3nRv z92*>6W*NvH`y!}_eE?fpn()cl85otnxoK&Fo%-g%-+yWBsb=ih*m|+LvlorBh1EPWdc&^Z(~2>mS%J&@DNjqF17fY%rlV^+z#*iQ1caoT0OL$HY(NY@(Hz5g5iGSM3@k_-=>k{cb0!ORrL|TE%BUg{n4-6WCY$mJstiaH zoFej%kE6Jj>I%)J)xVt4csPA zX~ksAE-NbrXql=Q(}o#nrxjvbYTk6aP9Pjnwo9u>Vv2u^eSFy+!UQc5g+*6u4ckYe#AV}2Z=6Z>_xX=u(3muQTlP1K?c_VT}Ee1G0NpJ zhOe4gBp`5&8>9QNM%p#QGUzDH^5Lv6qQko1)!>niQWY^da zhDs>Qg5FD|R+EFopjC=2mhH3cYIXaVB9@oAd9FNEhlB;3KR748}$|zx$KT?7)4#a4bBX$bwTAXy?EHGj& z<0vKCKNis!v=~N3VI(;7Scdx;Ml5JE+>x8YKIuzrJ-`tA0ApBc77Iy;O>F}lu@88v zv9+uPFXZxFzu~dWW@F;$@n2rux)uvA5uYMpXvsk{@7~1HM|vr$^+6VTFQDZ%%>nol;dB$YKv}*N+L={ zuhHR1_hjcB;gAR= z$FLCG_*X$p{G+5gGS&hBme>dATx~b33@RIeoK8#outf(1?UI^hHH@oT zT^AuYhDKRPk9bYwScqOzh9!rh(Fb3B+90tW>yMz(y=9|mt<-@4bx1z#}%&S6?C?|g65T1=xz&MnN&ut z}aiS|>}xYu9q!U-G8pwj3&z#F}0Tz0<2Xf-Gqy%wTxqeB$p5^Y(mltGX{ zkG-$cER&{PPsMs4lh})iAI{Ms3gV;JV1m8YP8jXb3_yavD2}AX#KUO1d2H<$B@}>m zC`X`@t0+KWD~MJo$%nI!l7r9|WxltdfbKD&7b1W<-|G^4Em%9iKCl{VFpcx!`2#WX z3UjypQKZBwfFSW9y3&4kFG@F1M6bzA5^S+hUhy1|2#ibtd!vxsu!>TA35P=xc2p6*Wk!YQ7wwf38$6_RZjK@ykz&t+*4P(;DE0xl2W@Qw zia(*xkD;`72}pse<(2kL!vCl6{;4blS4?92to?ZFqc)IoBt3P0t8Mo^UqK4Vb0 z3bs5_`61UZyG{9uchaOqCX^~4vh)%QoQ3dI%KQ-VPmgIvj4C0n0gVzhp)vHo7&XOi zY3(y2T37HcS`ZAgM9Og`scnnisWuNtJ7tPM9L}6*!w&G0G5Qvw+5U z3t2t}M9eK<;9ra%dml@bh9o+L5ZiYh58I@h9+eItk6r_XRD@CTWT!{(5!xPsoov~T_aaUy4cE%6~}v(K$y!gwGvc?$I?EgW|sjO8qU42n!FY~%%e zlgB~1tBCr5IL&u>xk zhA@(LW1YZHnR>E1ENZKAaZ*(JK@+icZl@fgNHz9Q1xZP_P8b)}sDzCrwV8}<;Pi@PRT~twwBIU^aMrjm9d#;TZ|9ZDfumNx*6e=k>7N ze8sJU-wDXHOHd{)7!C@?u>r5;kxIi`z+$+AK1{-x5b9j-2H?r*&t03RV@l?uF;z6-4qbz!Pj4K)&C!3U$B1VZC2uM&8|hr$8Ha^lh^wgLpSSRgjO z2E@kOWObunClJKYz#_(KgItkeeD2W7V2-{6G|_8)lDB9?!n7g?k+Vrs?TGD1G52ex zAh4O5h*G2Q7?S0Ascc25kTre{X!)1WNZ)KM6(agb`p>Wl&jAt$P=c`<@mjJn@nxzk z0Ry`j>}*8~nsmm*kSh3Mr&@G7pFd^^TKQ-4=!JBtt>r1u;0{ESj(T*-$X56>N<^_zO{>=_6W@dX*1RXpeY^RKgwZ!H1{uRb)HNhF4L8;ZB^*(9lvnF&}8| z(%BAk?AM)vJobT6sOj_3;Yj;Z%L57GCEyVkSXO1zq(EXV6e^ZEUc;p`T$YD}=Pq#G zk6ekq68dTt5`>f#mbwZlB_qe;2SF8E>iFg`eftex3c%-Zkcrb@m510!e7mp8iHU%B z$`Kn*2^X~we6vLGm#+z`J*AiMc}htwoN92c==Z@Y4J1#$Yq$8YA|)uJ zua5)~fmactr5*ygu}7VctxQh{hX=%kH;_wT$#lxV^^9_>@lqTXgM@#bw5AlZ6@8w8 zlFo(^TR?f25v7h7J(X?`pNOR7l1_SuKIfu`jE4Q#Hx)|^GI8)75RQ3d!7x4`qa}dz zVf3kZryC!9!4TEHxHF_!$Bv5aVpOv$E6|b#sSg)>|!l zRo<53cm~M=UPV?j}2 zJcNmAZ(~pfUXNG*0@a%$BQww zH2zsqf@T~XKw`XQQ<-PKXc8lw3BlkNt1%0qvKyM(~ndlmf^VVvGaDRIHAvs(?>%{#zm_X^9Wo#N&pWt{MUIN=#vZj6a1Nhew8 z7GqI=3CQ#tmIgaDLugINF+LJ@q%fWWfe2h&$O7u&{HYd1`Pa(FAMt&hO`wr7o{X zciO9#I66^0?gSOQsb+&A zsdtVg?V1hBbZa;CUGQ&GQ}KiDCR4$>CfKX&O+Xp#GAXIm zO#CqCAI4uWRa?neaD`SRqKLdoO2J1flPeDpt1Utd)nZ#X5~-3oe)+UaH(;toaH(6&{~Y zMp+3XpJZyP>V%^oqE$`NvvssMO+x<$ci4hAdYvVXp&DMxcJBc zEIR~aTgi+Mk$S{t{>2!e+!z?B6je=Wxp>`YK}XDcKofJ%o{ zRiG&SKsg~AgoRyv=!ir6C{Z5d8FBx-Ax)BqoO|?%JqxUcw}9Mm#|qHy&@Txv7R)Fq zuaXjCjw1GqrefM9YO_CfXlNWWBHC2c3gNd=zHyuahPWq8yR8P1LMePUKq4-Zljy|H znMXr^s{;7K2ZfMZei7R67OFPfF$b9TsCOv|-x&L-fGAqV&rPn7Z=;lX9cF{8c;9@E0b$Sn3Uib2@r_aW4mh?u1uE-mtqo;b}O# zYlq6X(nhNNo(MJ1_z0mk{`hC%GJhZ(^A@<5ofXGF@hqls;t|kW^mmlJ=*#H(=(Vx) zlSe{CM6u9KYG_zKqImf3SP?~82Q<;^V?|=<6QD8N9V;Tezkx@;IaV}6X3?#{61`3h z2}@`c4d11PNDhD~DtVt8G9F=3RD72jB5edmacK`yLnqZl6q~$H4VfGyx=u6b817OOe4mMesioe87C~X&UkyQsI-|=kYu<^ z6_Lg#4OL2fG89Jp40lOVRqFFm;xYPx$s!}x5wl_Ixi2fAuW#ZnirJt0i(*8X=r!9& zyojI^1#YolYITO#_!PNu?kO6gyqAFk6E=$??KLz^yF_=|2ltq-=CG|` zEq!|Hs6w$K#5pr|cX0FF2y^nev6oEoQx@%xd5?Om$p z6`WoPEM>Ih07nKOZ_vtT9``1&>BV9Y;u|<(A1OJMF>M0YnEOP;G2q;Cu1xn+GLdk~ z1(DYk_t2yEHbRt> zR9fRIzOgQMC)h&<*$&daWx(oWL)@D{7xzRTe|atlf*U*hcK+Ac7YSud$)s3hkQyKm z;)6kKpFSu~`cPOG#pDraVo34~6vkT}30D;b*#)T6Fo=2Ch?fc*5R2nX9J+*~V;nF| zQNos<#F+PhJLaByku9h8oXDChkVd4nCX?rs1T8=-F5scm4p&)>rUYGq^19-H9Ke>e zQ|%H*j1B^tnEPYGq^nS%(QlH&Q9_w}C>uDw`7K-S; zk#LK{@*Fn~288(Ky(cT4bCYE6qYfzsQMD8^>9PH9oCU2AD<)3V5rY$=ku4cwm}?^l z+IGj^L)-fcyf`{zJ|)wUOE@M&W#J;W-^I=y4;7ymYARG^fFTMU^6W0bRBd?5$(A?| zIpT7ykL2jp#1%pV@9qXi3f&=K!ww?u-t-`hPQy+aL2meZo^MF%~3Gw zJ*TyTwXLSnN1Y`}h1_EF@dAM}o*~DwrORD^iD+lw!uWvOZ1TrUoYBfPS{$fiqCb`^-jZ1=@sXK$=4xf%ePO z$3EAXDjJt#kRg(ShAiYrdj*KKOV%U2nvq{TMG#3cCk=>E@Q84d+vqigwb^UvD0+|) zy>?QjB-t>F7U)ExuzrI9W}gtCxVQ|3@E0OzANgnCE=n2hc)en`PA=e`+mH)IwM%{H zu#h$;3(9=X0xLN)hIIpt@fMim@kSqcw%UNEl<)i%8F9fPZpVC4Z)_DH*z7U* zM4?UAELS%MSk*vSkM=`3j&B-5os+CTZD)!&p#T~o4G;r+m7tq*R4%&2){QF*RiqoH zD68LquX3ulC@VA<%83i~EppXnac{IoI@WworL~cbV`rhNJRB#Zs17vON{W}n}cY6cxm>qe5O%iuxJ_qv9#PB9v9X)xo-F! zzht*qdv5NY4*2aPH`)y$kk};2P0hvmaAFye#~cn=bFZ;+OKwdN7H`*qo}uKSCrX%U z7Hb{w9;wFJ`kQ1p6&`4oUL}xGzqmu{XMw@W&vY!EvD@SWohGjPMqM0N$CrZ`x*#O% zA7iLf*=5vCK&f?fuq@3Ig5@TrwIN!&g;KcD6*IRSoK+>}IEkbbEMYAfu~{YzW%w)0 zN@B_Y5aYSU>KW6V6qJ{VZ+k1oX`5UO>Mz_gK7u%I9*@gA>P`VI6D^%)tftmjFfCLp ztRZ)&cZ~C`psZ+v^O6_AwmkRP>7-#+5E&-b5!-=NG+F>CVO>6=#tf8Klpmx}KmW7? z*?g{+`1M)#%%f^1VATjCFBXhBLsA1(O6H(8KRRr1#HXl>Y3XRVAEZT;)88r~F7NL# zZxcSHM%x|g4{`!(kfJ@2n8J3wRKaNVBdJzn#?a(b$_T5pauwYUeydt3TPp-fH9(-^S2&5evyWaK9w7p(>tjelGuY{J`dhSd7r6wcrLB{%Ub&vK)8PrO}pdzEbrS<$U-_< zqoeN$v=a7)BLhl2a=?)>{}+8NqF49zYoASUaix?P+6=Y+UnrHImZ#ns`XDC|2Pq0P znlDW&86nn0Lb{wr{YzhEljs6b$lLL0t>H;8O& zIz57cIsF09v96=tl>)0x0ETTn42ODS+}pn9r(ly0EStElkr5joZff*IKNvf0yM#FE11L9?dSitaMG%?OEe8ui)cF3N5Skj5 z-l9{C#<>#DJ3yfDC~cNxTzxSJq2<|Ub>)DNXG5XWPi|09*u|ig*|6F6CvA#5pk9{e zzhwigzcLPO<9dg?1K}k{d%kgXTz&k|f#b6Q&PQ4GfE&pjr9w)V@VvfX??klco&t_D zsY?4$c+KkZwy(;2{DF)j`>zxL(a)iEneK7Zsd5f#tmX-;yZis;5<43kQ5z+de@@T=6frIFzhq9E1VD}HWQ-P{ zDTDztm8>i&!e(>QUIB!KXv3TVP)SHpPf;x&O+Pm>DDGlP!H5QswUt@*2Yp+W8KZo>$MC+b4Mo#X?G-Xh`0!@0RDSc1{jzxoF3{+U|F6u@;T)=WMoj{R6G6Ul&$zB$k#cSayc-*|( zQDF`$bBQ|PMns&O?W{qRc%S4?4_C_uB$^;wxjj!2{jG!)A;g_c^?8WP2qYJip_&gD z&Dby*x5Fqg*j*HgU2Pq=R%3Sy6u!2 za4@1iOB)2#x3qf9O5Wn-*%&!U{C>*t7-Hg3WxIcFQ$&_X_Rp4%zFWEcH<4d?s!e^> zynCN)A7wQY7-<`R+^zQu-J0Oe6dk!3S}bS;L!&eL+xpWz3fbPLgHv=mcrk4rL!c=f zh^S)4%9;m!`LMwNTJ!=OLdp?un<1GBsz5XNQcZ3WWiwK#qi)Xkcbi|_SrGTE?Eg8M zM4CCTp0GlouP`B_`=nOZCGk;{9&am3ib-HUzp#cRl@ba`j$~O(r2MF7^$h$y56NL4 z^RV9{Oo<;SS^tbg?>@8@l+l+RSljr+&n@8fLCm%QFL6IDxf87~a#VsuhvQF|HtM|%*_r&7~!l0qo^K$bvS#c4onAFL|M4PR0 zJcM)@Ql5qba=UEcx>}UcKDPj;_od7w40Oa}cnA-_jp5eZBcfMq`_pci@AAwWWf(WxwZc&j&Oca=Q9s{c^G6dR(I-C5&UoyxXMZV0t-i zvj=>pW)}9lzq{1ZJo1!jUCMNsgVbuwQ~5 zQe$$K^u|M#G1B7cD&OCpk#82~%b>)mH*b$E54N=GyRE^5->si*xxa+qZ0=fd4H#_A z81dLqPBw;EWL*%~=CEQ1ajq07i$oGJE>)aaHn|@5w-?7;&K8Lj6~~e@8ffA-$V87z zNQ%hAzft3Kp!A1ZuNCZ3s+QJnXkG z>K;lq2;Wa1dLKVde{{n^b!Oa&O@e>Cm5}`G)U+7VvmKIsT|UZ5jl2iX1{?DpG&otv z8IHX6Nv$%NbIa}3B%^m80Xb?`%-lNC4HH7-yPkr6c&;X{O?6u<)*vVuqRr`kCmyeFB5npmOq_1PYaraB?6uO6Ki>_<<)d>L51JX?%ScTd%Sp#*if@~VZ4 z^mZPSqgtYIrlI1h2Le93Xhovqv&sAbFJ!|ZNt|?_ zdaCa2@yup{t33cN;aMm3FW3O4!(t1~8kQa{s`OD**!tWO^H5j*kMG~V|KszgKQNQr zoblcv;vsi0R!^*7+|@vFizaKs6U%Z~bmR*zKG+)QC5Q&@ZO?jjx&Ww6f6%2qo%r)Y z4j3b&bjFEDq1&bU`^~{wWNO+3GEH1%gV5?=ic~ScgUD zvF+R$>Wu+x%l7b#AKe|28~7yZs*YQ5ts3tHCSIJTgk2yN!B<7ah()!6w<5xMR*}RR z3m;<(H04mXb>Jx>+ZZ_U9=Ks1SiLQBE&Vz5#`^XF+m}{{7

GtFE`f{*YU8X61q|n8ODHCZpv7^yBF zN~$WN6lw|;Dx1cI2d)eua(_QjK!4R5nd<_{1SDO)C_0+;>T#KlRxe~=$>6SG&DLqi zObE1bW=@7+Y<6)0R>k;oeKn5`c3rG9IF{l)BohTwI&64J2Y!weIcv#)^L!DQT$UNs z>XdevcXC}tr5Vj~wa&<0DJPr6G}bz;b|aE{TnwHTo42%kOj&*-0c$WJa-Fh$lSLpZ z4uUrACMH_~cKN$F*@(?^YZWcL;)k?D%|8DDo-wo`XL4*dhJKN43+BJ5q@UrxQ&$=P zVWn-(|4!|)(U6-oowiz*R9|ww^9s^7`uM5pnhbAiT+v_ULxRcrh9?PctJ5D|OjYN< zzp*AnrlneX52X&Xa8Vl`717#RbozmdA(docW@04a&Ly9ZD9W&jiF?*2-W*`i;fmaE zq{_rG)MP(QxK@kpajiysVhVl@ zBWsJ*X0_?5A+aJT4e|i*TFIrBrRuKhtv*F-o&9`LbF5`D%8%Gjo+@V7K&4TZt6Dk+ zRxB+ht14epEptz@M%7e15wpXas%@0%HKeq8Jc3m#yp)65zObk6t9~x^TdmLAB-TpH zn&Av)eNLlqq~UCvh#;f_3V=nB`6M99eNqHFA{6+W4xmGXhp?v?h*nP#rQ+q$6!dY8?_C+qPQutn6=V z_Qe|v6^lZEi+SJH*~Jd9R_PjEzNU6wH-a6Uj)mf6y`B7T!z7zsVoo_#vHSersVAFv z_A1MHq{i1T+8K4uz4X#bug09`zf&VtuD#0m{Kk4N+TTuAdolOhdbDRbJcUOuf5Ig8 zfZA-fj1%6)>>M>$pVIG3xnT_e<{ zjnzi(b9%5bH?jre5&-ZDawMFZ+v!$9j<1ULcs3?Ysak!L?CXpHkB0W(mUqE%a*SWh zoRGMZjo4%z0Cg2#$UtSW4cR6A$lyBgOYi8m`r4SzAU|^Dou2ESLA+&{Mg#;x6D~ zZ6(JWYP-CiSmTEogpS!aThH^uYz83fl)5Cz2O{MFvp9vQ6qu1&h=IWvR)Lq@Qe36_ zHy<0b-C|U1uPvGS@k-wHMi(F>Y+vh3WG3<>2P_~kO2eMa9-G}fo{#+ZfUEq@h;q3A z=VEFk8!52G28LqtqxN7?{;|hOxf$6%O~OMgPN%6Q1*nHXs+?Rrfvazuclvan?DlaS z%iYTL$t?BP`Adm+J&OSW422n5NG-G@0k@z*xe}gduArAFaEnOE98|jVNJ^h5kNIg! zN|rFT`(l*WFIoKgNS)cY52Gf2O2|0{qO6N)m%uxMnv=!1So2c6Mi~9x2~hyVL;_4i z)PoU_a?sDB2gb{c*2(eW+cG^F0>tvikEn>{dK^Y1U4UHTeMD#FZA&@RnCD@Sj3i3-Q29 z>=1Z>E~N)447^zK+?O)zRYVYX^MZY0)D`G8OY6odP@nnnLs!(Y*fi8on4Z57iHL%6Zh%0E#M#Yt>b-P4Y z*=(3)$58EdMuth3^GJ>H?UT0U1==hFh&j2z;_7{Pfjiv5Rmxt>;_Bl$t0(pK@z|9) z@8#J#AnPxyfL&47Cv5SCyH>gIZY@sScDS$j*}DpbgG3JQS6UdO;F`sK=G?W$sC+@MPD)x-V-zsgH%N$+8X8;X91Ce>CvcY=|`t0PHDJsrVkVO z=&T-=WB1{cv+|-{)SqJ^cg_fQVl6FoF%`0+IAp6?mD6cfa?u`J2+wlfE7% zeqmJv+_QVc6)aru^sv6wtphEUy9&STwUFiQEiV3f5Y#Z!8yh^}CN5JEF zPVWjY6nqOf{ldjy*4rJR*!hORl~fy^;rDB@EK>J?T;nB}a`XNfphG*YP48U`6vxY@l^4vp%B0=j+BpSaNZp@ z2VoN_pmj!!Vz^3EdYGheqD?|7R7=mf`J{?+-=iUyxKpA9WqouO-VdIz0BGVsh}WCiNffUU-Bv}| z^AInJ3yvJ0%Q68pvxlzI^SU4kY}p-wMmuUlxfE|71M(msi6dpBi|omNRR97pWDWq7(bJ?UZ)9 z5mj$fb*5NVi?pS9RK#s*9Yz1j#P%6FZ8xw!HyHV-tk^a#EVF=HtTD(t=E`IP8CT+W zn=)%6&Nr?O)LiAt#%2Pk`<59~%cgP~oMTeEE^7!S-5?6Cf4uvsdjiAX>jDb7cegRU z21VG{WE4TiTI9J2$bc`dYd>0j0Uk; zd)@H1kHy1Do1ESH3{uUDdE5yGopV2RdtQ;rDGs6e(9DgHgO(i&2_Ky~riTr$&P zn;`3BxL`lwg;uosQrd#jQHinR%woU!o5z{%y04!3Y{2nR8OwqOGXiJf3$D50p_zBh zdb^S~qX2A50y5T1j))4FMBQ0u0Is+)tJG7#aIxCoz2RItm*)@aP!Lc)el0NRE3_o4 zCoka-UQwmnifk3S#NX)32($KG?iE*Dt3m{KlML`>HVpy>~~G=)N?BjV`}trE|V z3ITJtw%hmH>8{f+6ULPvVJLmZU5p&5yo8a1M4kzYvI2?(#HlX<5kJ!uGrOH^%kwT> zH0)rW3Rpe>HgQ>1Q!}#5-{gdnYd=ml84qG{@6d0p4AlW{`KH(;O zS-fRpwp+sf2&#kgr6N>*=@U52*+-$5z|zd4R~ytMD|I4SxO8=;EM{Lq)ktC>+ioc= zIt3_W?p-@nX{Q2ZWx9wSEgGt-|GVSkV zedxmw@-8Dt`{=Ayqp1e~O2sD0Es;GXbkcAdTG4Vi7ps zi-iQ<;RR%hbTu_4Kk!Oelz}3p_7@d-{!8w}Q8EX$McPHGCt9B##m@g-U{A%vJ-`ql zNBV)f6#~aNYk7E1=Gen#d;5L4`f)m}mp{Jg$WpKJa-R7?Lu!()%FGilJ$JJ`0QNJnk{>FEVypiGPuo2BF(@)SS}lV%-+xaZsf;bJPrMq{Cs z8w7_&kN|^NNE)xVI+FsHov)-&XAWw6BaDY)3+0DNz_HEkfBwyE{qWnr{^$&(DcK01 zW4R;Zgd?d7C{+Z5CH~33{yOGB9+~uo zB}82p@eHLC(6h4{+o!uPxBJy;@p${oC5uGvuCSE=l|#$hmq&`B&JCzK;<#(FK|<_REL&~nmEJ|0K+8U#LK@P3v%hCa-d{K6{n0Vnc(Kgl%{ zAZtWr|40Pzm3f4yTdA6iS20{c8N>k+mMb4R({E_G68B)`BvN}}D@;_g8~Lhc^@z@5 zUEdQT+l<;4DFGGY30pZE1_vij)zqdA8 zgC{x|eJZts|97rslU;sfhy5iq=@dHydzvx_mBB@udQk@Gi&NBA!nzJeVyHUgp;U6r zBQj%DSKFf$Z4ezSwkCo~S;SAo2n??#FZ83p7dsO&w=t2S9W^mt}d5= zs%0P@hvSiGxU~>3 z-L13`7{$91PkHI{XsU_RQXF>Z9nKP_&qNK{=hLqBG|k1p)9f@lJq{J`lU1jMW{s;# zd#bFFS!D;^2!KHfqbwdVhvg5kqLm+IiaOI}-ND{m%+)cDxb3evE+c?kY{Bc#a$*44 zbt44B7tbu)&6X*9x4G|7^JV!cY)F0>Tc)fOtPY!7c!oH1>Xf!j(yWx7mcv(s&qF(u-=SA4=kJO5gU|X=O z%cAPA)Ag|vroAiPPvumjgK^CS#2RrBp$(e@JtOGALET=m0m(;|M?@|2h)a1ni?Mcq zi;aU${pC=e!X+%@I-bCOh*H`|K|S7%RjN_CMrJJ+WlB)AglE;-UNtG!X&n;LLoDJb zkm*jlRw4+GQ)gk0zag>(b3?aVMI-9IQjFgS)?nLRlZx9Z*znzKcTH z9Gq1zoEZWks!*_mXVIs*fI{geu0uLnmi#~jfRBQ~>&K;@aR-csw%(n$TRpTx&bpNs z*jW}56{fz-q9#_M+aJ;t`K8}Xl#?q6vM1{Eb~DJ-&66G%M}59#F0zuDlA|7i$li1E zi!K%{UGU%}WG8P=DR;U;e?(=pIj-dz$uC)z(zib1R=!k=cH7pZuZ9S!%SY^Uo?bF$ zdR>3~RhjVNU>_{=ah2oWPhWMEJwNQF%S6*CtL;MX&<#GN7g!d_ODgYRCyH<&gZPue z=E+|T-Slb%hMM>uQ1^NF*k(LnYkAb(KvobXt8kLty$F}jo2Q5U`3X}Io_)T1v6t@k zPM7*!fB}iNmM1~Yn-JGRo=`uflb8-0sF@f=HKOuMG#qMGl&ulUnq4^x%I%qv&XtoT zelpKb`_lt{`W`lFgR+nVdRjI=n^CVW$V zKsrACsmKbk#G!*?u4N-)Ra#KCMi5eF=zT6x4^U#wqm)!gQH#}|oqD0L5V*&J?O=#e z>mY@??KG~_YktrI!bjnCv4hN6GXuc0@`15!fQ0EFT#9_$=Q@^2YtZaq9j_Uf7J1jH~&;V8*ucO73wE13jViOgE$Fn+7NXRLP+7h{;SER)^ew~~u|q9+oFiS6>EK*5QE`nROT?QeapSudfi(rND>4)H zCDsf(7OP*~idAIri=sjrr9uYtx!i$PY6M0JYa?{@sa4whYJ8|6v2=Uif$5j_zQ7cYVUh|BT&T7m(#qC>CaL#T4cgOJo3{aSrNOidLJxYyz}_v{Kn&rx^pFUKRqP zkIJforbkEopp==XpIA8`m-8!Yicwn=2m2X|XzEU;CB@<8m<+{7&{+^nWsjm-$U)sx zMy&0k#0emiJsAhF%ak!38=NZF7OWP@E7hmG8cz-T|NJ`|_g*AdJgbDRlnw`q=R>JS|yFYC?GSz!pvUNyI>=V+A%LYkf;F>Z~uE{B?uFh~Xa%xqToT+Bf ztVYbca!xIs8Skt~J_mK6j+z5|Yw6hRt~_JyH2mq5hmeJ2)iJT=KmY!K%1zlO>N;7C z-lO0CDa=#j@UJG04A{h3hnhH>v{4+3ie&+aS#Og;;*z0*{{-=T%b}Z(I+9%nQ;DBD z;nh%@kqopZeht(lTqGt695;VhnP9%rfrvjvJBeTEbZy5(;tpwSo|pUcZUQxENx8Ky^ny%NfT)r3I#j;b>QJYqQ zce0NzchBC*y_zVsX-1uJ6=bHRVCv;{-|*v?mr=DLst_HUs@}G7(RXlj&%_j@-aI z3~*^uDk(?F`@1^`=#vx8&$u-GRms?y9fO?o?jS|SN0R%vM2h_aqvw?T2T=!jm(J?c zGMS&cCev$;^hByq0{i`Tjq9t#)=1!$^cyk3)+ghMpL9TUkSKX%3grA+S#&loxs``m zNeyes$U=~k4W0==lQb7jOMnEZO#@PFQv^ndrUpSfbQTrl+OPFLWnD~5M_YsOAO*-c zaTX@Nm~xsl4+a3;7lj(h@gt~dE=@sAU#^Fm?ckfLG;tQJ%Pa&WFMm|n(MX*vZhmNo z>$k8u@l7jB$E)C8LKlc4g7?p`2TWLC$t8ozmS@qDd7vdIo`aN&mBXVoA=mHM3B@4V zLvaxj2hZZ@&O$1LM7A&>YpYYj+#*_IGzTd=vG6506CdWnY!=r9tx3{$&A`pC#tDp1 zQAJP$rOE&Rs1a>RBzjFt6#@qtC9N)A+)$ViPq`q#0QABapo(2!e+CSsB%lpafMOaV zx*VShYg;D!tE0;Z$hM@mHX55_c`l(g5b~<9-N8q@ep+kb98jX`?U^!tmgSIP6 zUXE3`QGc6v1Hf2F+nP6CLAN$LHb69Ss*j!hT8Xp)tPoOMAw-kFAh=Cs1>+L6SrO|u zShhK>a4ca~i<DJ-6E>$Syk?T!ZWY*HJM@21_#@lF|LJdBhQh=rQEP zy0!kR@F9s7V-GNCbVaAUh)96gjH;bdP@|VpDCrk$`GQ-<8dnmg^%Dqv@bjuS2hAu zwoTC5BhhuD-vBg`3WKpB(wR`VYu?VKy#7M358pLBuyIS zTJL}BpgjTHT9SBHMc8;4CpMSyl1*^ZYiENG9AIxZaYZt(0HEm)3*2DE!dqJw`<16Bp2isg%^$F7 zv0)^3VoOXI`Es}U)xF~8vjN#h-OQ3AP{KzkmP7r$0a4=}C{8?C$o*NACFk zjS5FDl=1A=(+X$!dT?d;{W@cMDmzF_C3DnY$><-OYK+^BXtQiAQLR z$3LI1K19voiIq>;PJ#(nyLF>MH5||69O6vpqljExcRFnu+R-L3;*6Y$$Dwzaz`xUB zy<*fh2~M}K5zqAv2au1}s|q+3OXy-}6C3%AG6Of2(oq#{&U0}zm(qhG+z*p_0J_=BIEckZ6n z`f7maQ%>vc-HcrO^Kpftzq~cTnt(w0(3zD@KAPCXWw6K6(_EB*ZY`f|oT4pz<_}3# z)IoYijgTs+j~p{=OOi~Y%Cj=C2o7)Y!Lo=#_h1#{%xnQ9n4jZ zd0EH+Uy5yZ6JLCgH%1~Bmij|44dkGRS~4$L^F=J;|9#W>f+-l3Fmk>bDtGXoY`76_ zL=dJC&54AGN>$+02r{nSY}Bg}>dB^u*m}*Y8^zsCj=)NbVemrENf2DP^1{P<@-h+P z$9BuBIT&YjKo7Hm<}gY3NZN6B{DlRTg*aR2z^<8qS|c-806l4=s#=zamW>7xRe@M4 zC#N4Gcg8CY4ic=cTu)R6eW0g06%JCMoOLtz*ACV_^hfEa4)HFOpppw;w40)Zc{J^T z*9^;;QqG)j$N`g_=6IqmsMAc4_hFCexrh!CMx3mc0`VGIz-?pZ;xJFlMI?RT)Cg;b zEsbBlbARl4zrUq5Y@Ip8YZiEP_;}xN>LLd_`*4y{qKkgNSZ)sw%Xc4BJ*lX$G9T~r zG@r8|Y&bMk2$y78LX{wU1$`wXDMLjW+65ywRwdjybbCg9XaWh0#q zAK(svL5hl>$^e9dt1o@Jt#s@5g>Ic8^5F0YZ^lJ;<)aI|mlP>?wFD@qlB^B!hWF)< zFLH(-jUZ|niV^f9=frm@GwakWW+hd4mN9lN;t`q5ucnRF_$aR zIDSS|p_bU)gP_OdJ$KP%m@+{bXD%vSM@Z8rrJJ!jy#SJ?S>*xs)^YOoug{yfa?C(6 zndI#bmzd}3qaIiq!(}VTiHp3=lLNXZ!swDKJtB7M<0ssAX4dMiK-<-sD4kNJA(Jo{ zN$D)aLE=8GKV&ftQ$p%b=Mmp@kMKp0b2PtsJXc0vdMYa_`(E)hS@9~OLZNz5p5-Ha$~C(HPC;>AF2;2nPyFO_VXcRyf#nt0lt z@3|xKfdw!fF+-2{UgqNIibwnp{Q3%PScyzpk?SQ(r>zb;?Q=0TJW#H2kE?_84R;;R z@|YPxVZ&)@!LdV5(*u-FeZ#C&I!w})*6+VHgUhu!xrkP1mz#UmE|-c-A%J*; z9GHA#M$_8CRp#Bq(Q(CW$R>yuEZ7JKisYQBt^mMPvTm}XHK6TiAhij=h}8p=Ll{t# z>H4&VV#Sih<(mEW%pT%=+`1;Ht7)!U;0TY$hw=7wTGKrk4Bj&SeyDGXr^D%gey0X` z6zKT!4$nE?Pw)1}``@PvfX(y=B~oNTIE`9Mc8Bv!-Ez}v>h0mwp?O?J3~TcU#EdO{#_n%QiON2_C3<<>{}j|s8jn1 z2LbFOW$(#kwM%$x%v(zc-1|C;ho%TNMS2uf6L#BXG-RvDZg<9wo zZWEwW{4(QVk1NE7weZUvtXCgp3paOE$Vf=_5y#rYQvu{>SECJb&nbBgW(5M2+rz&|O(m$uD4OD z%0lzEv$yuAXWX#fSc>CcL#Zf+*= z;jaCSh&r63UyF$=+T!3o)Y`9rp2$~VT1Ut@3}u@^AC~b|fRz-*EbTrAh2*mT71&Dl z0$w5tS(oo2#g#uPi@EDbkG*20CJ*aMDi<06tc0(Gkp{Y-rUf&fs8Eyf$AHT9Kz7*N zuDAYi=2Gt_T?4&1*0;+S@#MJX1*p|E9W3*zDO+kBeR&lT3wJDVOUkR@36FsZ7iboL zC9HgG?!e_Fg?R0hEZn;QEGe&qC+oQbCQ%?|p}q{ebk%)0et5Z!YdI(7$8<7pxs?KG zi0cxxxIbeRKU#^>H#OKD7CY<@hcPg*m0NCyd1V;sE}ExDucTb!;=nV{B_b8-c;&r6Y>DKR zTiG~pB62>6WO#1-+9|E%_=*c0Qdp`D}poQB}YukE0y`?Q8%L~^GN zd(FYWltpPAjk94rgTXUV%^*jmU8g7A+LvwM^jCGRR~V@9bjEJC-U_eF!K*>+d97Ps zF3=lDYX6a9R|8UOHkJ%AeGr6v{90h9mx7t36nH+df^}s@I6j`mXDiR2V5i2(cK1Q}l7}iv*xhCMDv9aRc8`D5 zT);`Jquz(HgR*kz%`O;PvRV`OU?+|=GCnrTIS(aX4JLAfa589464OE*Hi!G=gw(J} zz#1kg|4pjnL795{F>ZZfu*GrD4l%iNUv7>LM^q zsvzBvO_^+@kJ9BRLymv&aXR{DM=#?KV$3+o{lL?HTY27Z^yP8AJe`m0Q98P1rt>)6 z&e6tIh+&{8ldd6HE(7%wC2~R~)-~WVZUtOUcpW$+L4juR*8wv+7ho2D9k9}!1zbsa z9Y7g01(uq_R!jNB-}3d=ub4Ms#?GFJQR_yH2y3RDnAc^l05!YVycT#gD4%r1a>G{_ z8{>6k>LHVu5}5!X@a9-A+MqD7#q;uM^?<;sV0dNgN7w#D%=%SiPXSdYszI&X`p49I-?QK-c|fv+6~rIWfOvX5 z$f1h-Xo2Vy#kD3=B(=?vqti7fQD@?4K`yfHWrxxxpv8#IsT^$q;9@$DqXvsT48`P! z{8pQ8Fpx9<{M5m{W} ) : null}

- {transfersHistory.map((transfer: interfaces.TransferHistory, key: number) => ( + {transfersHistory.slice().reverse().map((transfer: interfaces.TransferHistory, key: number) => ( } > Close history @@ -65,7 +65,7 @@ export default function TransactionsHistory() { onClick={clearTransferHistory} color="error" size="medium" - className={cls(styles.btnAction, cmn.mle)} + className={cls(styles.btnAction)} startIcon={} > Clear history diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 0c3f916..f6fc8da 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -134,6 +134,19 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { > {emoji} Transfer completed

+

+ Transfer details are available in History section +

@@ -141,7 +154,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { onClick={startOver} color="primary" size="medium" - className={cls(styles.btnAction, cmn.mle)} + className={cls(styles.btnAction)} startIcon={} > Start over diff --git a/src/index.ts b/src/index.ts index 86749dd..55daa24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,7 @@ import CommunityPool from './components/CommunityPool' import SFuelWarning from './components/SFuelWarning' import WrappedTokens from './components/WrappedTokens' import History from './components/History' +import TransactionData from './components/TransactionData' import { CHAINS_META, getChainAlias } from './core/metadata' import { cls, styles, cmn } from './core/css' @@ -68,6 +69,7 @@ export { SFuelWarning, WrappedTokens, History, + TransactionData, cls, styles, cmn, diff --git a/src/metadata/faucet.json b/src/metadata/faucet.json index 8b1163f..6cf196b 100644 --- a/src/metadata/faucet.json +++ b/src/metadata/faucet.json @@ -45,6 +45,10 @@ "staging-faint-slimy-achird": { "address": "0xfd56A3456fbAB0fc013213edCc830B9d32403C8B", "func": "0x0c11dedd" + }, + "staging-fast-active-bellatrix": { + "address": "0x1B2e7E6E66a6c202cdC0C31DF996b530af22CBee", + "func": "0x0c11dedd" } }, "legacy": null, diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 7d08762..085d5d0 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -10,8 +10,9 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-legal-crazy-castor', // Europa 'staging-utter-unripe-menkar', // Calypso 'staging-faint-slimy-achird', // Nebula + 'staging-fast-active-bellatrix', // Chaos Testnet 'staging-perfect-parallel-gacrux', // Test Chain 1 - 'staging-severe-violet-wezen' // Test Chain 2 + 'staging-severe-violet-wezen', // Test Chain 2 ], tokens: { eth: { @@ -67,6 +68,12 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { name: 'Human Token', symbol: 'HMT', iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png' + }, + ubxs: { + name: 'UBXS Token', + symbol: 'UBXS', + decimals: '6', + iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/17242.png' } }, connections: { @@ -136,6 +143,15 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { hmt: { address: '0x4058d058ff62ED347dB8a69c43Ae9C67268B50b0', chains: {} + }, + ubxs: { + address: '0x5A4957cc54B21e1fa72BA549392f213030d34804', + chains: { + 'staging-legal-crazy-castor': {}, + 'staging-fast-active-bellatrix': { + hub: 'staging-legal-crazy-castor' + } + } } }, erc721meta: { @@ -198,8 +214,39 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { } } }, + 'staging-fast-active-bellatrix': { + // Chaos connections + erc20: { + ubxs: { + address: '0x1b54d4b074fed0cd6a0b5836fc82af13a7f9288a', + chains: { + mainnet: { + clone: true, + hub: 'staging-legal-crazy-castor' + }, + 'staging-legal-crazy-castor': { + clone: true + } + } + } + } + }, 'staging-faint-slimy-achird': { // Nebula connections + // eth: { + // eth: { + // address: '0x', + // chains: { + // 'staging-legal-crazy-castor': { + // clone: true + // }, + // mainnet: { + // hub: 'staging-legal-crazy-castor', + // clone: true + // }, + // } + // } + // }, erc20: { skl: { address: '0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d', @@ -299,6 +346,17 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { clone: true } } + }, + ubxs: { + address: '0x8e55e1cc37eca9636f4ef35874468876d52d623f', + chains: { + mainnet: { + clone: true + }, + 'staging-fast-active-bellatrix': { + wrapper: '0xaB5149362daCcC086bC4ABDde80aB6b09cBc118E' + } + } } } }, From fd261265fb41b80bd0c735ad67c50248a15f943d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 9 Oct 2023 12:52:25 +0100 Subject: [PATCH 063/110] Update submodules --- .eslintrc.js | 7 ++++++- skale-network | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 988ec8b..9af9e37 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,12 @@ module.exports = { browser: true, es2021: true, }, - extends: ['plugin:react/recommended', 'standard-with-typescript', 'prettier', 'plugin:storybook/recommended'], + extends: [ + 'plugin:react/recommended', + 'standard-with-typescript', + 'prettier', + 'plugin:storybook/recommended' + ], overrides: [], parserOptions: { ecmaVersion: 'latest', diff --git a/skale-network b/skale-network index d9d60d1..78a53ff 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit d9d60d12826ac8e0b34107856b0f9073c09cf663 +Subproject commit 78a53ff4c53bd9246dcde124be4d4d9cc1023716 From e6aea50bf9fab02d5c2273490842cc2265a4e4b4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 9 Oct 2023 19:54:24 +0100 Subject: [PATCH 064/110] Update metadata, add transformChainName --- skale-network | 2 +- src/core/metadata.ts | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/skale-network b/skale-network index 78a53ff..f1c51b7 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 78a53ff4c53bd9246dcde124be4d4d9cc1023716 +Subproject commit f1c51b7364c00cd1a4d545d94ecf4ea343d3063f diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 2475604..1994197 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -51,7 +51,17 @@ export const CHAINS_META: NetworksMetadataMap = { regression: regressionMeta } -export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app?: string): string { + +function transformChainName(chainName: string): string { + return chainName.split('-') + .map(word => + word.charAt(0).toUpperCase() + + word.slice(1).toLowerCase() + ).join(' '); +} + +export function getChainAlias( + skaleNetwork: SkaleNetwork, chainName: string, app?: string, prettify?: boolean): string { if (chainName === MAINNET_CHAIN_NAME) { if (skaleNetwork != MAINNET_CHAIN_NAME) { const network = skaleNetwork === 'staging' ? 'Goerli' : skaleNetwork @@ -69,6 +79,7 @@ export function getChainAlias(skaleNetwork: SkaleNetwork, chainName: string, app } return CHAINS_META[skaleNetwork][chainName].alias } + if (prettify) return transformChainName(chainName) return chainName } From 7e2f3fe84b781e43fb48dbfc2216092319b4a4ef Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 10 Oct 2023 16:12:41 +0100 Subject: [PATCH 065/110] Update staging config, fix explorer URL --- src/core/constants.ts | 2 +- src/metadata/metaportConfigStaging.ts | 29 ++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/core/constants.ts b/src/core/constants.ts index 9641de5..e1ebb18 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -60,7 +60,7 @@ export const MAINNET_EXPLORER_URLS: { [skaleNetwork: string]: string } = { export const BASE_EXPLORER_URLS: { [skaleNetwork: string]: string } = { mainnet: 'explorer.mainnet.skalenodes.com', staging: 'explorer.staging-v3.skalenodes.com', - legacy: 'explorer.staging-v3.skalenodes.com', + legacy: 'legacy-explorer.skalenodes.com', regression: 'regression-explorer.skalenodes.com' } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 085d5d0..61af2e6 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -85,6 +85,9 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-utter-unripe-menkar': { hub: 'staging-legal-crazy-castor' } + // 'staging-faint-slimy-achird': { + // hub: 'staging-legal-crazy-castor' + // } } } }, @@ -218,7 +221,7 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { // Chaos connections erc20: { ubxs: { - address: '0x1b54d4b074fed0cd6a0b5836fc82af13a7f9288a', + address: '0xB430a748Af4Ed4E07BA53454a8247f4FA0da7484', chains: { mainnet: { clone: true, @@ -278,6 +281,9 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-utter-unripe-menkar': { wrapper: '0xa270484784f043e159f74C03B691F80B6F6e3c24' } + // 'staging-faint-slimy-achird': { + // wrapper: '0xa270484784f043e159f74C03B691F80B6F6e3c24' + // } } } }, @@ -348,13 +354,13 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { } }, ubxs: { - address: '0x8e55e1cc37eca9636f4ef35874468876d52d623f', + address: '0xaB5149362daCcC086bC4ABDde80aB6b09cBc118E', chains: { mainnet: { clone: true }, 'staging-fast-active-bellatrix': { - wrapper: '0xaB5149362daCcC086bC4ABDde80aB6b09cBc118E' + wrapper: '0x8e55e1Cc37ecA9636F4eF35874468876d52d623F' } } } @@ -366,11 +372,24 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-perfect-parallel-gacrux': { erc20: {}, erc721: {}, - erc1155: {} + erc1155: { + // "skaliens": { + // "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", + // "chains": {} + // }, + // "medals": { + // "address": "0x5D8bD602dC5468B3998e8514A1851bd5888E9639", + // "chains": {} + // }, + // "_ANIMALS_0xDf87EEF0977148129969b01b329379b17756cdDE": { + // "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", + // "chains": {} + // } + } } }, theme: { mode: 'dark', vibrant: true } -} +} \ No newline at end of file From 18738c2715830b1a6c60c89f8da5cf40a8f0f0cb Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 10 Oct 2023 16:15:04 +0100 Subject: [PATCH 066/110] Update rainbowkit package --- bun.lockb | Bin 613415 -> 614497 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index 024c68a5dd1724d8a5df90ad6e284059e9a59a13..8bb8afc0c7b473ac4f5c8ec041e24afac7e432ac 100755 GIT binary patch delta 97635 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 diff --git a/package.json b/package.json index 24da89d..a5300f8 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@mui/icons-material": "^5.14.8", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@rainbow-me/rainbowkit": "^1.0.11", + "@rainbow-me/rainbowkit": "^1.1.1", "@skalenetwork/ima-js": "2.0.0-preview.5", "coingecko-api-v3": "^0.0.29", "react-jazzicon": "^1.0.4", From 698d7a5e0666ae7aa7148ce8ef668e3eaba10c9e Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 10 Oct 2023 16:17:13 +0100 Subject: [PATCH 067/110] Run prettier --- src/components/History.tsx | 185 ++++++++++++++------------ src/components/Stepper/SkStepper.tsx | 10 +- src/core/metadata.ts | 16 ++- src/metadata/metaportConfigStaging.ts | 4 +- 4 files changed, 111 insertions(+), 104 deletions(-) diff --git a/src/components/History.tsx b/src/components/History.tsx index 51097f7..8a643b1 100644 --- a/src/components/History.tsx +++ b/src/components/History.tsx @@ -75,111 +75,124 @@ export default function History(props: { size?: interfaces.SimplifiedSize }) { ) : null}
- {transfersHistory.slice().reverse().map((transfer: interfaces.TransferHistory, key: number) => ( - -
( +
- - - -
-
- -
-
- + + +
-

- {transfer.amount} {transfer.tokenKeyname} -

-

+ +
- •{' '} - {transfer.address.substring(0, 6) + - '...' + - transfer.address.substring(transfer.address.length - 4)} -

+
+ +
+

+ {transfer.amount} {transfer.tokenKeyname} +

+

+ •{' '} + {transfer.address.substring(0, 6) + + '...' + + transfer.address.substring(transfer.address.length - 4)} +

+
-
- - {transfer.transactions.map((transactionData: interfaces.TransactionHistory) => ( - - ))} + + {transfer.transactions.map((transactionData: interfaces.TransactionHistory) => ( + + ))} + - - ))} + ))}
) diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index f6fc8da..3ef133c 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -135,15 +135,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { {emoji} Transfer completed

Transfer details are available in History section

diff --git a/src/core/metadata.ts b/src/core/metadata.ts index 1994197..44f3c25 100644 --- a/src/core/metadata.ts +++ b/src/core/metadata.ts @@ -51,17 +51,19 @@ export const CHAINS_META: NetworksMetadataMap = { regression: regressionMeta } - function transformChainName(chainName: string): string { - return chainName.split('-') - .map(word => - word.charAt(0).toUpperCase() + - word.slice(1).toLowerCase() - ).join(' '); + return chainName + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' ') } export function getChainAlias( - skaleNetwork: SkaleNetwork, chainName: string, app?: string, prettify?: boolean): string { + skaleNetwork: SkaleNetwork, + chainName: string, + app?: string, + prettify?: boolean +): string { if (chainName === MAINNET_CHAIN_NAME) { if (skaleNetwork != MAINNET_CHAIN_NAME) { const network = skaleNetwork === 'staging' ? 'Goerli' : skaleNetwork diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 61af2e6..517de25 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -12,7 +12,7 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { 'staging-faint-slimy-achird', // Nebula 'staging-fast-active-bellatrix', // Chaos Testnet 'staging-perfect-parallel-gacrux', // Test Chain 1 - 'staging-severe-violet-wezen', // Test Chain 2 + 'staging-severe-violet-wezen' // Test Chain 2 ], tokens: { eth: { @@ -392,4 +392,4 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { mode: 'dark', vibrant: true } -} \ No newline at end of file +} From 244bac306613d012fb4c0f922fe9125c0d7837b4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 10 Oct 2023 18:04:58 +0100 Subject: [PATCH 068/110] Fix approve wrap step --- src/core/actions/erc20.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index eff3335..5985f32 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -161,7 +161,7 @@ export class WrapERC20S extends Action { const approveTx = await sChain.erc20.approve( this.token.keyname, MAX_APPROVE_AMOUNT, - this.token.address, + this.token.wrapper(this.chainName2), { address: this.address } From 005aaf22d3f5a0398db8294c1ad540423e8a3345 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 10 Oct 2023 18:16:55 +0100 Subject: [PATCH 069/110] Fix approve wrap method --- src/core/actions/erc20.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index eff3335..5985f32 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -161,7 +161,7 @@ export class WrapERC20S extends Action { const approveTx = await sChain.erc20.approve( this.token.keyname, MAX_APPROVE_AMOUNT, - this.token.address, + this.token.wrapper(this.chainName2), { address: this.address } From bfb7e436cddeecd3a3190c9a99effb297c5d30a0 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 11 Oct 2023 15:29:27 +0100 Subject: [PATCH 070/110] Add legacy faucets --- src/metadata/faucet.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/metadata/faucet.json b/src/metadata/faucet.json index 6cf196b..e87a19e 100644 --- a/src/metadata/faucet.json +++ b/src/metadata/faucet.json @@ -51,6 +51,15 @@ "func": "0x0c11dedd" } }, - "legacy": null, + "legacy": { + "skale-innocent-nasty": { + "address": "0xC0ED18Fe6654C8211582671E28E75d73BF61A60f", + "func": "0x0c11dedd" + }, + "international-villainous-zaurak": { + "address": "0x8bcA6a0E1427dBc2C2F40134d805d5929B80f56E", + "func": "0x0c11dedd" + } + }, "regression": null } \ No newline at end of file From ba6befc497634f3192c8bc6aabc68bbd2203cc14 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 12 Oct 2023 13:43:00 +0100 Subject: [PATCH 071/110] Minor css fix --- src/styles/cmn.module.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/styles/cmn.module.scss b/src/styles/cmn.module.scss index 50a7023..56e14e6 100644 --- a/src/styles/cmn.module.scss +++ b/src/styles/cmn.module.scss @@ -31,6 +31,11 @@ transform: rotate(180deg); } + +.bordRad { + border-radius: $sk-border-radius; +} + .bord { border: 1px $sk-paper-color solid; } @@ -268,11 +273,9 @@ } .pleft { - text-align: right; + text-align: left; } - - :global([rk-chain-button]) { display: none !important; } \ No newline at end of file From dd9319e3b44203a6b0b2bf04ba7944a57c0f7288 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 12 Oct 2023 15:57:51 +0100 Subject: [PATCH 072/110] Add project id for preview --- src/components/Metaport/Metaport.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Metaport/Metaport.stories.tsx b/src/components/Metaport/Metaport.stories.tsx index 8b2a84e..8ccf95b 100644 --- a/src/components/Metaport/Metaport.stories.tsx +++ b/src/components/Metaport/Metaport.stories.tsx @@ -3,6 +3,7 @@ import Metaport from './Metaport' import { METAPORT_CONFIG } from '../../metadata/metaportConfigStaging' METAPORT_CONFIG.mainnetEndpoint = import.meta.env.VITE_MAINNET_ENDPOINT +METAPORT_CONFIG.projectId = import.meta.env.VITE_WC_PROJECT_ID const meta: Meta = { title: 'Functional/Metaport', From 5f4953761ad500cbf26ed8280ed2982245af23ac Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 12 Oct 2023 16:01:30 +0100 Subject: [PATCH 073/110] Switch build to bun --- vercel.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vercel.json b/vercel.json index cab7b14..fe95999 100644 --- a/vercel.json +++ b/vercel.json @@ -1,8 +1,8 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "yarn build", - "devCommand": "yarn dev", - "installCommand": "bash prepare_meta.sh && yarn install && yarn build:lib", + "buildCommand": "bun build:lib", + "devCommand": "bun dev", + "installCommand": "bash prepare_meta.sh && bun install && bun build:lib", "framework": null, "outputDirectory": "./storybook-static" } \ No newline at end of file From 74bd6cbd021997a189cda98ca1310aaa8e845744 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 12 Oct 2023 16:06:25 +0100 Subject: [PATCH 074/110] Fix vercel build --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index fe95999..8a7b2a6 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "bun build:lib", + "buildCommand": "bun run build", "devCommand": "bun dev", "installCommand": "bash prepare_meta.sh && bun install && bun build:lib", "framework": null, From 2a9d8c4d99a987f9cc292ecc31cb91c62ff057fa Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 12 Oct 2023 16:29:00 +0100 Subject: [PATCH 075/110] Change build to yarn --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index 8a7b2a6..5f156c3 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "bun run build", + "buildCommand": "yarn build", "devCommand": "bun dev", "installCommand": "bash prepare_meta.sh && bun install && bun build:lib", "framework": null, From 98016b76e80d7894a9c95995b646a6bffe3e5ce2 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Oct 2023 15:40:01 +0100 Subject: [PATCH 076/110] Update chains metadata format --- helper-scripts | 2 +- skale-network | 2 +- src/components/SkConnect.tsx | 5 +++-- src/core/interfaces/ChainsMetadata.ts | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/helper-scripts b/helper-scripts index 45d533e..c253fa6 160000 --- a/helper-scripts +++ b/helper-scripts @@ -1 +1 @@ -Subproject commit 45d533ea5d3895ce79ebc275fa1cbeab11fe5036 +Subproject commit c253fa60f1862753e0546dd9ade72b41e425cf99 diff --git a/skale-network b/skale-network index f1c51b7..d9cf68a 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit f1c51b7364c00cd1a4d545d94ecf4ea343d3063f +Subproject commit d9cf68a4b863dcd2b3cc01b5b22a99e713e68293 diff --git a/src/components/SkConnect.tsx b/src/components/SkConnect.tsx index bb99750..87983ed 100644 --- a/src/components/SkConnect.tsx +++ b/src/components/SkConnect.tsx @@ -51,6 +51,7 @@ export default function SkConnect() { (!authenticationStatus || authenticationStatus === 'authenticated') return (
Wrong network diff --git a/src/core/interfaces/ChainsMetadata.ts b/src/core/interfaces/ChainsMetadata.ts index 2c41898..5de4555 100644 --- a/src/core/interfaces/ChainsMetadata.ts +++ b/src/core/interfaces/ChainsMetadata.ts @@ -25,9 +25,10 @@ import { SkaleNetwork } from './Config' export interface ChainMetadata { alias?: string + shortAlias?: string minSfuelWei?: string faucetUrl?: string - category: string + category: string | string[] background: string gradientBackground?: string description?: string From 044bafb6b1defef2feceef8344bc2be128ed0cf0 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 16 Oct 2023 16:46:24 +0100 Subject: [PATCH 077/110] metaport#189 Add checks for sfuel faucet --- src/components/SFuelWarning.tsx | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/components/SFuelWarning.tsx b/src/components/SFuelWarning.tsx index b5a5dbc..f0616d6 100644 --- a/src/components/SFuelWarning.tsx +++ b/src/components/SFuelWarning.tsx @@ -34,6 +34,7 @@ import ArrowOutwardRoundedIcon from '@mui/icons-material/ArrowOutwardRounded' import { BALANCE_UPDATE_INTERVAL_MS, MAINNET_CHAIN_NAME, SFUEL_TEXT } from '../core/constants' import { Station } from '../core/sfuel' +import { isFaucetAvailable } from '../core/faucet' import { useMetaportStore } from '../store/MetaportStore' import { useSFuelStore } from '../store/SFuelStore' @@ -198,13 +199,36 @@ export default function SFuelWarning(props: {}) {
) + const sourceFaucetAvailable = + chainName1 === MAINNET_CHAIN_NAME || isFaucetAvailable(chainName1, mpc.config.skaleNetwork) + const destFaucetAvailable = + chainName2 === MAINNET_CHAIN_NAME || isFaucetAvailable(chainName2, mpc.config.skaleNetwork) + const hubFaucetAvailable = !hubChain || isFaucetAvailable(hubChain, mpc.config.skaleNetwork) + const faucetsAvailable = sourceFaucetAvailable && destFaucetAvailable && hubFaucetAvailable + return (

⛽ {getSFuelText()}

- {sFuelBtn ? ( + {!faucetsAvailable ? ( +

+ ❗️ Faucet is not available for one of the selected chains, contact with chain owner to + get sFUEL +

+ ) : null} + {sFuelBtn && faucetsAvailable ? (
{mining ? ( Date: Mon, 16 Oct 2023 17:51:59 +0100 Subject: [PATCH 078/110] metaport#190 Update CP logic --- src/components/CommunityPool.tsx | 44 ++++++++++++++++++++++---------- src/store/CommunityPoolStore.ts | 7 ++++- src/styles/styles.module.scss | 2 +- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/components/CommunityPool.tsx b/src/components/CommunityPool.tsx index 214014a..4b96f79 100644 --- a/src/components/CommunityPool.tsx +++ b/src/components/CommunityPool.tsx @@ -42,13 +42,18 @@ import ErrorIcon from '@mui/icons-material/Error' import { fromWei } from '../core/convertation' import { withdraw, recharge } from '../core/community_pool' -import { BALANCE_UPDATE_INTERVAL_MS, DEFAULT_ERC20_DECIMALS } from '../core/constants' +import { + BALANCE_UPDATE_INTERVAL_MS, + DEFAULT_ERC20_DECIMALS, + MINIMUM_RECHARGE_AMOUNT +} from '../core/constants' import { cls, cmn, styles } from '../core/css' import { useCPStore } from '../store/CommunityPoolStore' import { useCollapseStore } from '../store/Store' import { useMetaportStore } from '../store/MetaportStore' +import { Collapse } from '@mui/material' export default function CommunityPool() { const { data: walletClient } = useWalletClient() @@ -120,6 +125,8 @@ export default function CommunityPool() { if (loading === 'recharge') return 'Recharging...' if (loading === 'activate') return 'Activating account...' if (Number(amount) > Number(accountBalanceEther)) return 'Insufficient ETH balance' + if (Number(amount) < MINIMUM_RECHARGE_AMOUNT) + return `Recharge amount should be bigger than ${MINIMUM_RECHARGE_AMOUNT}` if (amount === '' || amount === '0' || !amount) return 'Enter an amount' return 'Recharge exit gas wallet' } @@ -188,6 +195,12 @@ export default function CommunityPool() { This wallet is used to pay for gas fees on transactions that are send to the Ethereum Mainnet. You may withdraw funds from your SKALE Gas Wallet at anytime.

+ {cpData.recommendedRechargeAmount ? ( +

+ Minimum recommended recharge amount for your wallet is{' '} + {cpData.recommendedRechargeAmount} ETH. +

+ ) : null}

ETH Balance @@ -244,7 +257,7 @@ export default function CommunityPool() {

-
+
-
-
- + +
+ +
+
diff --git a/src/store/CommunityPoolStore.ts b/src/store/CommunityPoolStore.ts index 4f185ce..16af529 100644 --- a/src/store/CommunityPoolStore.ts +++ b/src/store/CommunityPoolStore.ts @@ -76,10 +76,15 @@ export const useCPStore = create()((set, get) => ({ get().mainnet, get().sChain ) + + const currentAmount = get().amount set({ chainName: chainName1, cpData: cpData, - amount: cpData.recommendedRechargeAmount ? cpData.recommendedRechargeAmount.toString() : '' + amount: + cpData.recommendedRechargeAmount && currentAmount === '' + ? cpData.recommendedRechargeAmount.toString() + : currentAmount }) } })) diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index 939188b..94087f7 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -352,7 +352,7 @@ button { } .accordionContent { - padding: 0 26px !important; + padding: 0 20px !important; } .accordionSummaryTokens { From 94a75c7a15adc30800ac43727276acd605df219c Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 18 Oct 2023 18:36:44 +0100 Subject: [PATCH 079/110] Add debug component, add rainbowkit tx history --- src/components/Debug.tsx | 88 +++++++++++++++++++++++++++ src/components/Metaport/Metaport.tsx | 2 + src/components/SkConnect.tsx | 1 - src/components/Stepper/SkStepper.tsx | 18 ++++++ src/metadata/metaportConfigStaging.ts | 2 +- 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 src/components/Debug.tsx diff --git a/src/components/Debug.tsx b/src/components/Debug.tsx new file mode 100644 index 0000000..d025da5 --- /dev/null +++ b/src/components/Debug.tsx @@ -0,0 +1,88 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file Debug.ts + * @copyright SKALE Labs 2023-Present + */ + +import Button from '@mui/material/Button' + +import Grid from '@mui/material/Grid'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; + +import { useMetaportStore } from '../store/MetaportStore' +import { cls, cmn } from '../core/css' + + +export default function Debug() { + + const debug = useMetaportStore((state) => state.mpc.config.debug) + const chainName1 = useMetaportStore((state) => state.chainName1) + const chainName2 = useMetaportStore((state) => state.chainName2) + const amount = useMetaportStore((state) => state.amount) + const stepsMetadata = useMetaportStore((state) => state.stepsMetadata) + + const rows = [ + { name: 'chainName1', value: chainName1 }, + { name: 'chainName2', value: chainName2 }, + { name: 'amount', value: amount }, + { name: 'stepsMetadata', value: JSON.stringify(stepsMetadata) }, + ] + + if (!debug) return + return ( +
+ + + + + + + Property + Value + + + + {rows.map((row) => ( + + + {row.name} + + {row.value} + + ))} + +
+
+
+
+ +
+ ) +} diff --git a/src/components/Metaport/Metaport.tsx b/src/components/Metaport/Metaport.tsx index ac6eb13..fa36e2f 100644 --- a/src/components/Metaport/Metaport.tsx +++ b/src/components/Metaport/Metaport.tsx @@ -23,11 +23,13 @@ import { MetaportConfig } from '../../core/interfaces' import WidgetUI from '../WidgetUI' +import Debug from '../Debug' import MetaportProvider from '../MetaportProvider' export default function Metaport(props: { config: MetaportConfig }) { return ( + ) diff --git a/src/components/SkConnect.tsx b/src/components/SkConnect.tsx index 87983ed..a74331e 100644 --- a/src/components/SkConnect.tsx +++ b/src/components/SkConnect.tsx @@ -51,7 +51,6 @@ export default function SkConnect() { (!authenticationStatus || authenticationStatus === 'authenticated') return (
state.ima2) const amount = useMetaportStore((state) => state.amount) + const transactionsHistory = useMetaportStore((state) => state.transactionsHistory) const cpData = useCPStore((state) => state.cpData) @@ -55,6 +58,21 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { setEmoji(getRandom(SUCCESS_EMOJIS)) }, []) + useEffect(() => { + try { + const latestTx = transactionsHistory[transactionsHistory.length - 1] + if (latestTx) { + addRecentTransaction({ + hash: latestTx.transactionHash, + description: latestTx.txName, + confirmations: 1 + }) + } + } catch { + console.error('Failed to add tx to rainbowkit') + } + }, [transactionsHistory]) + if (stepsMetadata.length === 0) return
return ( diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 517de25..396cd7c 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -4,7 +4,7 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { skaleNetwork: 'staging', openOnLoad: true, openButton: true, - debug: false, + debug: true, chains: [ 'mainnet', 'staging-legal-crazy-castor', // Europa From 8d71640e3894e2e2b2a699105b6b88a55698f09f Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 19 Oct 2023 19:01:32 +0100 Subject: [PATCH 080/110] Update station data after PoW --- helper-scripts | 2 +- skale-network | 2 +- src/components/SFuelWarning.tsx | 1 + src/metadata/metaportConfigStaging.ts | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/helper-scripts b/helper-scripts index 45d533e..c253fa6 160000 --- a/helper-scripts +++ b/helper-scripts @@ -1 +1 @@ -Subproject commit 45d533ea5d3895ce79ebc275fa1cbeab11fe5036 +Subproject commit c253fa60f1862753e0546dd9ade72b41e425cf99 diff --git a/skale-network b/skale-network index f1c51b7..d9cf68a 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit f1c51b7364c00cd1a4d545d94ecf4ea343d3063f +Subproject commit d9cf68a4b863dcd2b3cc01b5b22a99e713e68293 diff --git a/src/components/SFuelWarning.tsx b/src/components/SFuelWarning.tsx index b5a5dbc..cf7bcab 100644 --- a/src/components/SFuelWarning.tsx +++ b/src/components/SFuelWarning.tsx @@ -173,6 +173,7 @@ export default function SFuelWarning(props: {}) { if (toPowRes) log(chainName2, toPowRes.message) if (hubPowRes) log(hubChain, hubPowRes.message) } else { + await updateStationsData() setSFuelStatus('action') setSFuelOk(true) } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 3cd11d3..af9bebb 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -8,7 +8,8 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { chains: [ 'mainnet', 'skale-innocent-nasty', // europa - 'international-villainous-zaurak' // calypso + 'international-villainous-zaurak', // calypso + 'big-majestic-oval-SKALE' // QA chain ], tokens: { eth: { From a4c29d31a4e3369d97bddf22528f863029304fe3 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 20 Oct 2023 19:27:54 +0100 Subject: [PATCH 081/110] Improve error handling --- README.md | 19 +- bun.lockb | Bin 614497 -> 604625 bytes package.json | 30 +-- src/components/ErrorMessage.tsx | 83 +++++- src/components/WidgetBody.tsx | 6 - src/components/WidgetUI/WidgetUI.tsx | 4 +- src/core/constants.ts | 1 + src/core/dataclasses/ErrorMessage.ts | 21 +- src/metadata/metaportConfigStaging.ts | 357 ++++++++++++++++++++++---- src/store/MetaportStore.ts | 19 +- src/styles/styles.module.scss | 16 +- 11 files changed, 441 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index de0cb7b..95943b7 100644 --- a/README.md +++ b/README.md @@ -29,30 +29,13 @@ const metaport = new Metaport({ Additionally, you can enable debug logs in developer console by enabling `Verbose` level of logs. -### Storybook setup - -``` -yarn install -npx sb init --builder webpack5 -yarn run storybook -``` - -### Linter - -Used linter: https://palantir.github.io/tslint/ - -Install the global CLI and its peer dependency: - -```shell -yarn global add tslint typescript -``` #### Linter git hook Be sure to add pre-commit git hook: ```shell -echo 'yarn lint' > .git/hooks/pre-commit +echo 'bun lint' > .git/hooks/pre-commit chmod +x .git/hooks/pre-commit ``` diff --git a/bun.lockb b/bun.lockb index 8bb8afc0c7b473ac4f5c8ec041e24afac7e432ac..23081571002b3007bde457ed76f39df26cb7c241 100755 GIT binary patch delta 142524 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 delta 147486 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 ajZtgRJvLDo_*tWaL%Dz$XNKy+)&CFW@*I)? diff --git a/package.json b/package.json index a5300f8..fd85518 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,14 @@ "version": "node -e \"console.log(require('./package.json').version);\"" }, "devDependencies": { - "@storybook/addon-essentials": "7.4.0", - "@storybook/addon-interactions": "7.4.0", - "@storybook/addon-links": "7.4.0", + "@storybook/addon-essentials": "7.5.1", + "@storybook/addon-interactions": "7.5.1", + "@storybook/addon-links": "7.5.1", "@storybook/addon-styling": "1.3.7", - "@storybook/blocks": "7.4.0", - "@storybook/react": "7.4.0", - "@storybook/react-vite": "7.4.0", - "@storybook/testing-library": "0.2.0", + "@storybook/blocks": "7.5.1", + "@storybook/react": "7.5.1", + "@storybook/react-vite": "7.5.1", + "@storybook/testing-library": "0.2.2", "@testing-library/react": "14.0.0", "@types/node": "20.4.9", "@types/react": "18.2.20", @@ -57,7 +57,7 @@ "eslint-plugin-n": "16.0.1", "eslint-plugin-promise": "6.1.1", "eslint-plugin-react": "7.33.1", - "eslint-plugin-storybook": "0.6.13", + "eslint-plugin-storybook": "0.6.15", "jsdom": "22.1.0", "json": "11.0.0", "lint-staged": "13.2.3", @@ -67,7 +67,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.65.1", - "storybook": "7.4.0", + "storybook": "7.5.1", "typescript": "5.1.6", "vite": "4.4.9", "vite-plugin-dts": "3.5.1", @@ -75,16 +75,16 @@ "vitest": "0.34.1" }, "dependencies": { - "@mui/material": "^5.14.8", - "@mui/lab": "^5.0.0-alpha.143", - "@mui/icons-material": "^5.14.8", + "@mui/material": "^5.14.14", + "@mui/lab": "^5.0.0-alpha.149", + "@mui/icons-material": "^5.14.14", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@rainbow-me/rainbowkit": "^1.1.1", - "@skalenetwork/ima-js": "2.0.0-preview.5", + "@rainbow-me/rainbowkit": "^1.1.2", + "@skalenetwork/ima-js": "2.0.0-develop.4", "coingecko-api-v3": "^0.0.29", "react-jazzicon": "^1.0.4", - "viem": "^1.10.8", + "viem": "^1.16.6", "wagmi": "^1.4.1", "zustand": "^4.4.1" }, diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx index c669f0d..88b4ac7 100644 --- a/src/components/ErrorMessage.tsx +++ b/src/components/ErrorMessage.tsx @@ -1,3 +1,8 @@ +import { useState } from 'react' + +import Accordion from '@mui/material/Accordion' +import AccordionSummary from '@mui/material/AccordionSummary' +import AccordionDetails from '@mui/material/AccordionDetails' import Button from '@mui/material/Button' import { cls, cmn, styles } from '../core/css' @@ -8,15 +13,32 @@ import LinkOffRoundedIcon from '@mui/icons-material/LinkOffRounded' import PublicOffRoundedIcon from '@mui/icons-material/PublicOffRounded' import SentimentDissatisfiedRoundedIcon from '@mui/icons-material/SentimentDissatisfiedRounded' import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded' +import HourglassTopRoundedIcon from '@mui/icons-material/HourglassTopRounded'; +import CrisisAlertRoundedIcon from '@mui/icons-material/CrisisAlertRounded'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import TextSnippetRoundedIcon from '@mui/icons-material/TextSnippetRounded'; +import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded'; +import AvTimerRoundedIcon from '@mui/icons-material/AvTimerRounded'; + +import { DEFAULT_ERROR_MSG } from '../core/constants' const ERROR_ICONS = { 'link-off': , 'public-off': , sentiment: , - error: + warning: , + error: , + time: } export default function Error(props: { errorMessage: ErrorMessage }) { + + const [expanded, setExpanded] = useState(false) + + const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { + setExpanded(isExpanded ? panel : false) + } + if (!props.errorMessage) return return (

@@ -24,23 +46,64 @@ export default function Error(props: { errorMessage: ErrorMessage }) { {ERROR_ICONS[props.errorMessage.icon]}

- Oops! Something went wrong. + {props.errorMessage.headline ?? DEFAULT_ERROR_MSG}

-

+

Logs are available in your browser's developer console

-
-

- {props.errorMessage.text} + +

+
+ +
+

+ When transferring from SKALE to Ethereum Mainnet, there are frequency limitations. +

+
+
+
+ +
+

+ Sometimes transfers may take more time than expected.

+ + } + aria-controls="panel1a-content" + id="panel1a-header" + > +
+
+ +
+

+ {expanded === 'panel1' ? 'Hide' : 'Show'} error details +

+
+
+ +
+ + {props.errorMessage.text} + +
+ +
+
{props.errorMessage.fallback ? (
) : null} - - - - diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index 38a094a..d9ad015 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -89,8 +89,8 @@ export function WidgetUI(props: { config: MetaportConfig }) {
{fabTop ? fabButton : null}
- - + + diff --git a/src/core/constants.ts b/src/core/constants.ts index e1ebb18..3481e2e 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -44,6 +44,7 @@ export const DEFAULT_MIN_SFUEL_WEI = 21000000000000n export const DEFAULT_ERC20_DECIMALS = '18' export const DEFAULT_ERROR_MSG = 'Ooops... Something went wrong...' +export const TRANSFER_ERROR_MSG = 'Error during the transfer' export const DEFAULT_MP_MARGIN = '20pt' export const DEFAULT_MP_Z_INDEX = 99000 diff --git a/src/core/dataclasses/ErrorMessage.ts b/src/core/dataclasses/ErrorMessage.ts index e916def..84157e9 100644 --- a/src/core/dataclasses/ErrorMessage.ts +++ b/src/core/dataclasses/ErrorMessage.ts @@ -21,11 +21,17 @@ * @copyright SKALE Labs 2022-Present */ + +import { TRANSFER_ERROR_MSG } from "../constants" + + export class ErrorMessage { icon: string + headline: string text: string btnText?: string fallback?: Function + showTips?: boolean constructor(fallback?: Function) { this.fallback = fallback @@ -50,11 +56,22 @@ export class WrongNetworkMessage extends ErrorMessage { } export class TransactionErrorMessage extends ErrorMessage { - constructor(text: string, fallback: Function) { + constructor(text: string, fallback: Function, headline?: string, showTips?: boolean) { super(fallback) - this.icon = 'sentiment' + this.icon = 'warning' + this.headline = headline ?? TRANSFER_ERROR_MSG this.text = text this.btnText = 'Try again' + this.showTips = showTips + } +} + +export class TimeoutErrorMessage extends ErrorMessage { + constructor(text: string, fallback: Function) { + super(fallback) + this.icon = 'time' + this.text = text + this.btnText = 'Close message' } } diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index f2afa2a..76296b2 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -1,15 +1,18 @@ import * as interfaces from '../core/interfaces' export const METAPORT_CONFIG: interfaces.MetaportConfig = { - skaleNetwork: 'legacy', + skaleNetwork: 'staging', openOnLoad: true, openButton: true, - debug: true, + debug: false, chains: [ 'mainnet', - 'skale-innocent-nasty', // europa - 'international-villainous-zaurak', // calypso - 'big-majestic-oval-SKALE' // QA chain + 'staging-legal-crazy-castor', // Europa + 'staging-utter-unripe-menkar', // Calypso + 'staging-faint-slimy-achird', // Nebula + 'staging-fast-active-bellatrix', // Chaos Testnet + 'staging-perfect-parallel-gacrux', // Test Chain 1 + 'staging-severe-violet-wezen' // Test Chain 2 ], tokens: { eth: { @@ -24,6 +27,53 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { decimals: '6', symbol: 'USDC', name: 'USD Coin' + }, + usdt: { + decimals: '6', + symbol: 'USDT', + name: 'Tether USD' + }, + wbtc: { + decimals: '18', + symbol: 'WBTC', + name: 'WBTC' + }, + _SPACE_1: { + name: 'SKALE Space', + symbol: 'SPACE', + iconUrl: + 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Rocket/3D/rocket_3d.png' + }, + _SKALIENS_1: { + name: 'SKALIENS Collection', + symbol: 'SKALIENS', + iconUrl: + 'https://raw.githubusercontent.com/microsoft/fluentui-emoji/main/assets/Alien/3D/alien_3d.png' + }, + ruby: { + name: 'Ruby Token', + iconUrl: 'https://ruby.exchange/images/tokens/ruby-square.png', + symbol: 'RUBY' + }, + dai: { + name: 'DAI Stablecoin', + symbol: 'DAI' + }, + usdp: { + name: 'Pax Dollar', + symbol: 'USDP', + iconUrl: 'https://ruby.exchange/images/tokens/usdp-square.png' + }, + hmt: { + name: 'Human Token', + symbol: 'HMT', + iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/10347.png' + }, + ubxs: { + name: 'UBXS Token', + symbol: 'UBXS', + decimals: '6', + iconUrl: 'https://s2.coinmarketcap.com/static/img/coins/64x64/17242.png' } }, connections: { @@ -31,78 +81,195 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { eth: { eth: { chains: { - 'skale-innocent-nasty': {}, - 'international-villainous-zaurak': { - hub: 'skale-innocent-nasty' + 'staging-legal-crazy-castor': {}, + 'staging-utter-unripe-menkar': { + hub: 'staging-legal-crazy-castor' } + // 'staging-faint-slimy-achird': { + // hub: 'staging-legal-crazy-castor' + // } } } }, erc20: { skl: { - address: '0x17A7Cf31a11554e75246973663262dA56F84F89b', + address: '0x493D4442013717189C9963a2e275Ad33bfAFcE11', chains: { - 'skale-innocent-nasty': {}, - 'international-villainous-zaurak': { - hub: 'skale-innocent-nasty' + 'staging-legal-crazy-castor': {}, + 'staging-utter-unripe-menkar': { + hub: 'staging-legal-crazy-castor' + }, + 'staging-faint-slimy-achird': { + hub: 'staging-legal-crazy-castor' } } }, - // usdc: { - // address: '0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9', - // chains: { - // 'skale-innocent-nasty': {}, - // 'international-villainous-zaurak': { - // hub: 'skale-innocent-nasty' - // } - // } - // } + ruby: { + address: '0xd66641E25E9D36A995682572eaD74E24C11Bb422', + chains: { + 'staging-legal-crazy-castor': {} + } + }, + dai: { + address: '0x83B38f79cFFB47CF74f7eC8a5F8D7DD69349fBf7', + chains: { + 'staging-legal-crazy-castor': {} + } + }, + usdp: { + address: '0x66259E472f8d09083ecB51D42F9F872A61001426', + chains: { + 'staging-legal-crazy-castor': {} + } + }, + usdt: { + address: '0xD1E44e3afd6d3F155e7704c67705E3bAC2e491b6', + chains: { + 'staging-legal-crazy-castor': {} + } + }, + usdc: { + address: '0x85dedAA65D33210E15911Da5E9dc29F5C93a50A9', + chains: { + 'staging-legal-crazy-castor': {}, + 'staging-utter-unripe-menkar': { + hub: 'staging-legal-crazy-castor' + } + } + }, + wbtc: { + address: '0xd80BC0126A38c9F7b915e1B2B9f78280639cadb3', + chains: { + 'staging-legal-crazy-castor': {} + } + }, + hmt: { + address: '0x4058d058ff62ED347dB8a69c43Ae9C67268B50b0', + chains: {} + }, + ubxs: { + address: '0x5A4957cc54B21e1fa72BA549392f213030d34804', + chains: { + 'staging-legal-crazy-castor': {}, + 'staging-fast-active-bellatrix': { + hub: 'staging-legal-crazy-castor' + } + } + } + }, + erc721meta: { + _SPACE_1: { + address: '0x1b7729d7E1025A031aF9D6E68598b57f4C2adfF6', + chains: {} + } + }, + erc1155: { + _SKALIENS_1: { + address: '0x6cb73D413970ae9379560aA45c769b417Fbf33D6', + chains: {} + } } }, - 'international-villainous-zaurak': { + 'staging-utter-unripe-menkar': { // Calypso connections eth: { eth: { - address: '0x9C0e8bC2B2D403299214c80081F93fAB5e10b593', + address: '0xECabAE592Eb56D96115FcF4c7F772ADB7BF573d0', chains: { - 'skale-innocent-nasty': { + 'staging-legal-crazy-castor': { clone: true }, mainnet: { clone: true, - hub: 'skale-innocent-nasty' + hub: 'staging-legal-crazy-castor' } } } }, erc20: { skl: { - address: '0xFbbDF9aC97093b1E88aB79F7D0c296d9cc5eD0d0', + address: '0x7E1B8750C21AebC3bb2a0bDf40be104C609a9852', chains: { - 'skale-innocent-nasty': { + 'staging-legal-crazy-castor': { + clone: true + }, + 'staging-faint-slimy-achird': { + hub: 'staging-legal-crazy-castor', clone: true }, mainnet: { - hub: 'skale-innocent-nasty', + hub: 'staging-legal-crazy-castor', clone: true } } }, - // usdc: { - // address: '0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6', - // chains: { - // 'skale-innocent-nasty': { - // clone: true - // }, - // mainnet: { - // hub: 'skale-innocent-nasty', - // clone: true - // } - // } - // } + usdc: { + address: '0x49c37d0Bb6238933eEe2157e9Df417fd62723fF6', + chains: { + 'staging-legal-crazy-castor': { + clone: true + }, + mainnet: { + hub: 'staging-legal-crazy-castor', + clone: true + } + } + } + } + }, + 'staging-fast-active-bellatrix': { + // Chaos connections + erc20: { + ubxs: { + address: '0xB430a748Af4Ed4E07BA53454a8247f4FA0da7484', + chains: { + mainnet: { + clone: true, + hub: 'staging-legal-crazy-castor' + }, + 'staging-legal-crazy-castor': { + clone: true + } + } + } + } + }, + 'staging-faint-slimy-achird': { + // Nebula connections + // eth: { + // eth: { + // address: '0x', + // chains: { + // 'staging-legal-crazy-castor': { + // clone: true + // }, + // mainnet: { + // hub: 'staging-legal-crazy-castor', + // clone: true + // }, + // } + // } + // }, + erc20: { + skl: { + address: '0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d', + chains: { + 'staging-legal-crazy-castor': { + clone: true + }, + mainnet: { + hub: 'staging-legal-crazy-castor', + clone: true + }, + 'staging-utter-unripe-menkar': { + hub: 'staging-legal-crazy-castor', + clone: true + } + } + } } }, - 'skale-innocent-nasty': { + 'staging-legal-crazy-castor': { // Europa connections eth: { eth: { @@ -111,34 +278,112 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { mainnet: { clone: true }, - 'international-villainous-zaurak': { - wrapper: '0x321e1aa81B4c6CC3B8EFe3D9c0AD67E6eC949c2c' + 'staging-utter-unripe-menkar': { + wrapper: '0xa270484784f043e159f74C03B691F80B6F6e3c24' } + // 'staging-faint-slimy-achird': { + // wrapper: '0xa270484784f043e159f74C03B691F80B6F6e3c24' + // } } } }, erc20: { skl: { - address: '0xa101902B3119f4830292bb79ebAB56967229207B', + address: '0xbA1E9BA7CDd4815Da6a51586bE56e8643d1bEAb6', chains: { mainnet: { clone: true }, - 'international-villainous-zaurak': { - wrapper: '0x51A1eD016633Afb00C25Eb404745C61D8c16BBd4' + 'staging-utter-unripe-menkar': { + wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6' + }, + 'staging-faint-slimy-achird': { + wrapper: '0x6a679eF80aF3fE01A646F858Ca1e26D58b5430B6' } } }, - // usdc: { - // address: '0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33', - // chains: { - // mainnet: { - // clone: true - // }, - // 'international-villainous-zaurak': { - // wrapper: '0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0' - // } - // } + ruby: { + address: '0xf06De9214B1Db39fFE9db2AebFA74E52f1e46e39', + chains: { + mainnet: { + clone: true + } + } + }, + dai: { + address: '0x3595E2f313780cb2f23e197B8e297066fd410d30', + chains: { + mainnet: { + clone: true + } + } + }, + usdp: { + address: '0xe0E2cb3A5d6f94a5bc2D00FAa3e64460A9D241E1', + chains: { + mainnet: { + clone: true + } + } + }, + usdt: { + address: '0xa388F9783d8E5B0502548061c3b06bf4300Fc0E1', + chains: { + mainnet: { + clone: true + } + } + }, + usdc: { + address: '0x5d42495D417fcd9ECf42F3EA8a55FcEf44eD9B33', + chains: { + mainnet: { + clone: true + }, + 'staging-utter-unripe-menkar': { + wrapper: '0x4f250cCE5b8B39caA96D1144b9A32E1c6a9f97b0' + } + } + }, + wbtc: { + address: '0xf5E880E1066DDc90471B9BAE6f183D5344fd289F', + chains: { + mainnet: { + clone: true + } + } + }, + ubxs: { + address: '0xaB5149362daCcC086bC4ABDde80aB6b09cBc118E', + chains: { + mainnet: { + clone: true + }, + 'staging-fast-active-bellatrix': { + wrapper: '0x8e55e1Cc37ecA9636F4eF35874468876d52d623F' + } + } + } + } + }, + 'staging-severe-violet-wezen': { + erc20: {} + }, + 'staging-perfect-parallel-gacrux': { + erc20: {}, + erc721: {}, + erc1155: { + // "skaliens": { + // "address": "0xBA9fF38A2b22edDfa8e05805bD22C8f20c40546e", + // "chains": {} + // }, + // "medals": { + // "address": "0x5D8bD602dC5468B3998e8514A1851bd5888E9639", + // "chains": {} + // }, + // "_ANIMALS_0xDf87EEF0977148129969b01b329379b17756cdDE": { + // "address": "0xDf87EEF0977148129969b01b329379b17756cdDE", + // "chains": {} // } } } diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts index d34ba86..d8b051d 100644 --- a/src/store/MetaportStore.ts +++ b/src/store/MetaportStore.ts @@ -32,7 +32,7 @@ import MetaportCore from '../core/metaport' import * as interfaces from '../core/interfaces' import * as dataclasses from '../core/dataclasses' import { getEmptyTokenDataMap } from '../core/tokens/helper' -import { MAINNET_CHAIN_NAME, DEFAULT_ERROR_MSG } from '../core/constants' +import { MAINNET_CHAIN_NAME, DEFAULT_ERROR_MSG, TRANSFER_ERROR_MSG } from '../core/constants' import { ACTIONS } from '../core/actions' debug.enable('*') @@ -123,11 +123,24 @@ export const useMetaportStore = create()((set, get) => ({ ).execute() } catch (err) { console.error(err) - const msg = err.message ? err.message : DEFAULT_ERROR_MSG + const msg = err.message + let showTips = true + let headline + if (err.code && err.code === 'ACTION_REJECTED') { + headline = 'Transaction signing was rejected' + showTips = false + } else { + headline = TRANSFER_ERROR_MSG + } + if (err.info && err.info.error && err.info.error.data && err.info.error.data.message) { + headline = err.info.error.data.message + } set({ errorMessage: new dataclasses.TransactionErrorMessage( msg, - get().errorMessageClosedFallback + get().errorMessageClosedFallback, + headline, + true ) }) return diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index 94087f7..88fb215 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -169,6 +169,9 @@ button { background-image: none !important; } + :global(.MuiAccordion-root:before) { + display: none !important; + } :global(.MuiLinearProgress-root) { border-radius: 20px; @@ -205,6 +208,11 @@ button { border-radius: $sk-border-radius-outter !important; } +.contentHeight { + max-height: calc(100vh - 180px); + overflow-y: auto; +} + .imaWidgetBody { border-radius: $sk-border-radius-outter !important; @@ -336,7 +344,7 @@ button { .infoIcon { svg { - height: 45pt; + height: 35pt; width: 100%; } } @@ -345,12 +353,14 @@ button { transform: rotate(360deg); } - - .accordionSummary { padding: 10px 26px !important; } +.accordionSummarySm { + padding: 10px !important; +} + .accordionContent { padding: 0 20px !important; } From eff25be996ffe7d66037672c1a6374911d572f8d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Sun, 22 Oct 2023 16:15:26 +0100 Subject: [PATCH 082/110] Fix ts build --- src/components/Debug.tsx | 2 -- src/store/MetaportStore.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/components/Debug.tsx b/src/components/Debug.tsx index d025da5..92e6525 100644 --- a/src/components/Debug.tsx +++ b/src/components/Debug.tsx @@ -21,8 +21,6 @@ * @copyright SKALE Labs 2023-Present */ -import Button from '@mui/material/Button' - import Grid from '@mui/material/Grid'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts index d8b051d..f8e05c0 100644 --- a/src/store/MetaportStore.ts +++ b/src/store/MetaportStore.ts @@ -124,11 +124,9 @@ export const useMetaportStore = create()((set, get) => ({ } catch (err) { console.error(err) const msg = err.message - let showTips = true let headline if (err.code && err.code === 'ACTION_REJECTED') { headline = 'Transaction signing was rejected' - showTips = false } else { headline = TRANSFER_ERROR_MSG } From be3d397fcdfa6cd034d386be42881bb4fcdaac4c Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 12:28:27 +0100 Subject: [PATCH 083/110] Update build cmd --- vercel.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index 5f156c3..b5dfd92 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "yarn build", + "buildCommand": "bun storybook build", "devCommand": "bun dev", "installCommand": "bash prepare_meta.sh && bun install && bun build:lib", "framework": null, From b47649f6abb3c9306b8b2a193a6493b5c777efce Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 12:43:33 +0100 Subject: [PATCH 084/110] Switch back to yarn in install script --- vercel.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vercel.json b/vercel.json index b5dfd92..861702d 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,7 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "bun storybook build", - "devCommand": "bun dev", + "buildCommand": "yarn storybook build", + "devCommand": "yarn dev", "installCommand": "bash prepare_meta.sh && bun install && bun build:lib", "framework": null, "outputDirectory": "./storybook-static" From da194f999c63109eb064953ccbe4780aa0318020 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 13:10:32 +0100 Subject: [PATCH 085/110] Change ima-js version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd85518..fc81fd3 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@rainbow-me/rainbowkit": "^1.1.2", - "@skalenetwork/ima-js": "2.0.0-develop.4", + "@skalenetwork/ima-js": "2.0.0-preview.5", "coingecko-api-v3": "^0.0.29", "react-jazzicon": "^1.0.4", "viem": "^1.16.6", From 7a24cfd412aef4b630faf3bb707786e2fe3ca302 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 13:15:06 +0100 Subject: [PATCH 086/110] Downgrade storybook --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index fc81fd3..8213c6d 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,14 @@ "version": "node -e \"console.log(require('./package.json').version);\"" }, "devDependencies": { - "@storybook/addon-essentials": "7.5.1", - "@storybook/addon-interactions": "7.5.1", - "@storybook/addon-links": "7.5.1", + "@storybook/addon-essentials": "7.4.0", + "@storybook/addon-interactions": "7.4.0", + "@storybook/addon-links": "7.4.0", "@storybook/addon-styling": "1.3.7", - "@storybook/blocks": "7.5.1", - "@storybook/react": "7.5.1", - "@storybook/react-vite": "7.5.1", - "@storybook/testing-library": "0.2.2", + "@storybook/blocks": "7.4.0", + "@storybook/react": "7.4.0", + "@storybook/react-vite": "7.4.0", + "@storybook/testing-library": "0.2.0", "@testing-library/react": "14.0.0", "@types/node": "20.4.9", "@types/react": "18.2.20", @@ -67,7 +67,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.65.1", - "storybook": "7.5.1", + "storybook": "7.4.0", "typescript": "5.1.6", "vite": "4.4.9", "vite-plugin-dts": "3.5.1", @@ -81,7 +81,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@rainbow-me/rainbowkit": "^1.1.2", - "@skalenetwork/ima-js": "2.0.0-preview.5", + "@skalenetwork/ima-js": "2.0.0-develop.4", "coingecko-api-v3": "^0.0.29", "react-jazzicon": "^1.0.4", "viem": "^1.16.6", From f0192e29b369005fa3a9e4dcd789fc3148a071e1 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 13:20:43 +0100 Subject: [PATCH 087/110] Update packages --- bun.lockb | Bin 604625 -> 615986 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index 23081571002b3007bde457ed76f39df26cb7c241..9ffe1e388df18582022fdbf8b8e7998d094c31f1 100755 GIT binary patch delta 103129 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

GXIqY(zM7H6l!D>E&snkDKeSz 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

Logs are available in your browser's developer console

- -
-
- + {props.errorMessage.showTips ?
+
+
+ +
+

+ When transferring from SKALE to Ethereum Mainnet, there are frequency limitations. +

-

- When transferring from SKALE to Ethereum Mainnet, there are frequency limitations. -

-
-
-
- +
+
+ +
+

+ Sometimes transfers may take more time than expected. +

-

- Sometimes transfers may take more time than expected. -

-
- +
: null} ${this.chainName2}`) + const amountWei = toWei(this.amount, this.token.meta.decimals) externalEvents.actionStateUpdated({ actionName: this.constructor.name, actionState: currentState, @@ -175,7 +171,7 @@ export class Action { chainName2: this.chainName2, address: this.address, amount: this.amount, - amountWei: this.amountWei, + amountWei: amountWei, tokenId: this.tokenId }, transactionHash, diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index 1470b8b..97bed94 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -25,7 +25,7 @@ import debug from 'debug' import { Contract } from 'ethers' import { MainnetChain, SChain } from '@skalenetwork/ima-js' -import { fromWei } from '../convertation' +import { fromWei, toWei } from '../convertation' import { TokenData } from '../dataclasses/TokenData' import * as interfaces from '../interfaces' import { addressesEqual } from '../helper' @@ -73,6 +73,16 @@ export async function checkERC20Balance( ): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes + try { + toWei(amount, tokenData.meta.decimals) + } catch (err) { + if (err.fault && err.fault === 'underflow') { + checkRes.msg = 'The amount is too small' + } else { + checkRes.msg = 'Incorrect amount' + } + return checkRes + } try { const balance = await tokenContract.balanceOf(address) log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`) diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index 5985f32..a1a13d7 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -116,9 +116,10 @@ export class WrapSFuelERC20S extends Action { async execute() { log('WrapSFuelERC20S:execute - starting') this.updateState('wrap') + const amountWei = toWei(this.amount, this.token.meta.decimals) const tx = await this.sChain1.erc20.fundExit(this.token.keyname, { address: this.address, - value: this.amountWei + value: amountWei }) const block = await this.sChain1.provider.getBlock(tx.blockNumber) this.updateState('wrapDone', tx.hash, block.timestamp) @@ -212,7 +213,7 @@ export class UnWrapERC20 extends Action { this.updateState('unwrapDone', tx.hash, block.timestamp) } - async preAction() {} + async preAction() { } } export class UnWrapERC20S extends Action { diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts index f8e05c0..934ab9b 100644 --- a/src/store/MetaportStore.ts +++ b/src/store/MetaportStore.ts @@ -191,21 +191,36 @@ export const useMetaportStore = create()((set, get) => ({ loading: true, btnText: 'Checking balance...' }) - const stepMetadata = get().stepsMetadata[get().currentStep] - const actionClass = ACTIONS[stepMetadata.type] - await new actionClass( - get().mpc, - stepMetadata.from, - stepMetadata.to, - address, - amount, - get().tokenId, - get().token, - get().setAmountErrorMessage, - get().setBtnText, - null, - null - ).preAction() + try { + const stepMetadata = get().stepsMetadata[get().currentStep] + const actionClass = ACTIONS[stepMetadata.type] + await new actionClass( + get().mpc, + stepMetadata.from, + stepMetadata.to, + address, + amount, + get().tokenId, + get().token, + get().setAmountErrorMessage, + get().setBtnText, + null, + null + ).preAction() + } catch (err) { + console.error(err) + const msg = err.code && err.fault ? `${err.code} - ${err.fault}` : 'Something went wrong' + set({ + errorMessage: new dataclasses.TransactionErrorMessage( + err.message, + get().errorMessageClosedFallback, + msg, + false + ) + }) + } finally { + set({ loading: false }) + } } set({ loading: false }) }, From 7f7cfbc2ab6cdb64af281395c5f27e1e2f7a6bc1 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 18:48:42 +0100 Subject: [PATCH 092/110] metaport#193 Add dynamic font size resizing --- src/components/AmountInput.tsx | 16 ++++++++++++---- src/core/actions/checks.ts | 22 +++++++++++++++++++++- src/styles/styles.module.scss | 3 ++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/components/AmountInput.tsx b/src/components/AmountInput.tsx index ce75cd8..245fdf1 100644 --- a/src/components/AmountInput.tsx +++ b/src/components/AmountInput.tsx @@ -18,11 +18,18 @@ export default function AmountInput() { const handleChange = (event: React.ChangeEvent) => { if (parseFloat(event.target.value) < 0) { - setAmount('', address) - return + setAmount('', address); + return; } - setAmount(event.target.value, address) - } + if (event.target.value.length > 12) { + let initialSize = 22 - (event.target.value.length / 3); + initialSize = initialSize <= 12 ? 12 : initialSize; + event.target.style.fontSize = initialSize + "px"; + } else { + event.target.style.fontSize = '22px' + } + setAmount(event.target.value, address); + }; return (
@@ -35,6 +42,7 @@ export default function AmountInput() { value={amount} onChange={handleChange} disabled={transferInProgress} + style={{ width: '100%' }} />
)} diff --git a/src/core/actions/checks.ts b/src/core/actions/checks.ts index 97bed94..965ab2d 100644 --- a/src/core/actions/checks.ts +++ b/src/core/actions/checks.ts @@ -41,7 +41,17 @@ export async function checkEthBalance( // TODO: optimize balance checks tokenData: TokenData ): Promise { const checkRes: interfaces.CheckRes = { res: false } - + if (!amount || Number(amount) === 0) return checkRes + try { + toWei(amount, tokenData.meta.decimals) + } catch (err) { + if (err.fault && err.fault === 'underflow') { + checkRes.msg = 'The amount is too small' + } else { + checkRes.msg = 'Incorrect amount' + } + return checkRes + } try { const balance = await chain.ethBalance(address) log(`address: ${address}, eth balance: ${balance}, amount: ${amount}`) @@ -107,6 +117,16 @@ export async function checkSFuelBalance( ): Promise { const checkRes: interfaces.CheckRes = { res: false } if (!amount || Number(amount) === 0) return checkRes + try { + toWei(amount, DEFAULT_ERC20_DECIMALS) + } catch (err) { + if (err.fault && err.fault === 'underflow') { + checkRes.msg = 'The amount is too small' + } else { + checkRes.msg = 'Incorrect amount' + } + return checkRes + } try { const balance = await sChain.provider.getBalance(address) log(`address: ${address}, balanceWei: ${balance}, amount: ${amount}`) diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index 88fb215..c2f1260 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -384,7 +384,8 @@ button { border-radius: 4px 0 0 4px; padding: 9pt 15pt; font-weight: bold !important; - font-size: 1.3rem !important; + font-size: 22px; + transition: font-size 0.2s ease-in-out; } input::-webkit-outer-spin-button, From 0ea5d4e8404eaa2aa4996c29e187b72f4321bfa4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 19:01:19 +0100 Subject: [PATCH 093/110] metaport#193 Fix wrapped tokens lookup --- src/core/metaport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/metaport.ts b/src/core/metaport.ts index da4e79f..7678f95 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -99,7 +99,7 @@ export function createWrappedTokensMap( ): TokenDataTypesMap { const wrappedTokens: TokenDataTypesMap = getEmptyTokenDataMap() const tokenType = TokenType.erc20 - if (!chainName1) return wrappedTokens + if (!chainName1 || !config.connections[chainName1][tokenType]) return wrappedTokens Object.keys(config.connections[chainName1][tokenType]).forEach((tokenKeyname) => { const token = config.connections[chainName1][tokenType][tokenKeyname] const wrapperAddress = findFirstWrapperAddress(token) From 64b0087c1271866960699bee312fe87d408a889a Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 19:18:53 +0100 Subject: [PATCH 094/110] metaport#194 Fix zero tokens input --- README.md | 7 +++ src/components/AmountInput.tsx | 14 ++--- src/components/Debug.tsx | 24 ++++---- src/components/ErrorMessage.tsx | 81 +++++++++++++++++---------- src/components/Stepper/SkStepper.tsx | 8 ++- src/components/WidgetUI/WidgetUI.tsx | 2 +- src/core/actions/erc20.ts | 2 +- src/core/dataclasses/ErrorMessage.ts | 4 +- src/metadata/metaportConfigStaging.ts | 2 +- 9 files changed, 85 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 95943b7..b900c97 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,13 @@ See https://docs.skale.network/metaport/1.1.x/ ## Development +### Storybook preview + +``` +bash prepare_meta.sh && bun install && bun build:lib +bun dev +``` + ### Debug mode To enable debug mode, set `debug` environment variable to `true`: diff --git a/src/components/AmountInput.tsx b/src/components/AmountInput.tsx index 245fdf1..500b529 100644 --- a/src/components/AmountInput.tsx +++ b/src/components/AmountInput.tsx @@ -18,18 +18,18 @@ export default function AmountInput() { const handleChange = (event: React.ChangeEvent) => { if (parseFloat(event.target.value) < 0) { - setAmount('', address); - return; + setAmount('', address) + return } if (event.target.value.length > 12) { - let initialSize = 22 - (event.target.value.length / 3); - initialSize = initialSize <= 12 ? 12 : initialSize; - event.target.style.fontSize = initialSize + "px"; + let initialSize = 22 - event.target.value.length / 3 + initialSize = initialSize <= 12 ? 12 : initialSize + event.target.style.fontSize = initialSize + 'px' } else { event.target.style.fontSize = '22px' } - setAmount(event.target.value, address); - }; + setAmount(event.target.value, address) + } return (
diff --git a/src/components/Debug.tsx b/src/components/Debug.tsx index 92e6525..cc2f877 100644 --- a/src/components/Debug.tsx +++ b/src/components/Debug.tsx @@ -21,21 +21,19 @@ * @copyright SKALE Labs 2023-Present */ -import Grid from '@mui/material/Grid'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; +import Grid from '@mui/material/Grid' +import Table from '@mui/material/Table' +import TableBody from '@mui/material/TableBody' +import TableCell from '@mui/material/TableCell' +import TableContainer from '@mui/material/TableContainer' +import TableHead from '@mui/material/TableHead' +import TableRow from '@mui/material/TableRow' +import Paper from '@mui/material/Paper' import { useMetaportStore } from '../store/MetaportStore' import { cls, cmn } from '../core/css' - export default function Debug() { - const debug = useMetaportStore((state) => state.mpc.config.debug) const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) @@ -46,15 +44,14 @@ export default function Debug() { { name: 'chainName1', value: chainName1 }, { name: 'chainName2', value: chainName2 }, { name: 'amount', value: amount }, - { name: 'stepsMetadata', value: JSON.stringify(stepsMetadata) }, + { name: 'stepsMetadata', value: JSON.stringify(stepsMetadata) } ] if (!debug) return return (
- + @@ -80,7 +77,6 @@ export default function Debug() { - ) } diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx index 0912fed..b0a4794 100644 --- a/src/components/ErrorMessage.tsx +++ b/src/components/ErrorMessage.tsx @@ -13,12 +13,12 @@ import LinkOffRoundedIcon from '@mui/icons-material/LinkOffRounded' import PublicOffRoundedIcon from '@mui/icons-material/PublicOffRounded' import SentimentDissatisfiedRoundedIcon from '@mui/icons-material/SentimentDissatisfiedRounded' import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded' -import HourglassTopRoundedIcon from '@mui/icons-material/HourglassTopRounded'; -import CrisisAlertRoundedIcon from '@mui/icons-material/CrisisAlertRounded'; +import HourglassTopRoundedIcon from '@mui/icons-material/HourglassTopRounded' +import CrisisAlertRoundedIcon from '@mui/icons-material/CrisisAlertRounded' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import TextSnippetRoundedIcon from '@mui/icons-material/TextSnippetRounded'; -import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded'; -import AvTimerRoundedIcon from '@mui/icons-material/AvTimerRounded'; +import TextSnippetRoundedIcon from '@mui/icons-material/TextSnippetRounded' +import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded' +import AvTimerRoundedIcon from '@mui/icons-material/AvTimerRounded' import { DEFAULT_ERROR_MSG } from '../core/constants' @@ -26,13 +26,12 @@ const ERROR_ICONS = { 'link-off': , 'public-off': , sentiment: , - warning: , + warning: , error: , time: } export default function Error(props: { errorMessage: ErrorMessage }) { - const [expanded, setExpanded] = useState(false) const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { @@ -54,28 +53,45 @@ export default function Error(props: { errorMessage: ErrorMessage }) {

Logs are available in your browser's developer console

- {props.errorMessage.showTips ?
-
-
- + {props.errorMessage.showTips ? ( +
+
+
+ +
+

+ When transferring from SKALE to Ethereum Mainnet, there are frequency limitations. +

-

- When transferring from SKALE to Ethereum Mainnet, there are frequency limitations. -

-
-
-
- +
+
+ +
+

+ Sometimes transfers may take more time than expected. +

-

- Sometimes transfers may take more time than expected. -

-
: null} - + ) : null} + } @@ -95,13 +111,20 @@ export default function Error(props: { errorMessage: ErrorMessage }) {
{props.errorMessage.text}
-
{props.errorMessage.fallback ? ( diff --git a/src/components/Stepper/SkStepper.tsx b/src/components/Stepper/SkStepper.tsx index 6198db4..0e47e81 100644 --- a/src/components/Stepper/SkStepper.tsx +++ b/src/components/Stepper/SkStepper.tsx @@ -74,6 +74,10 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { }, [transactionsHistory]) if (stepsMetadata.length === 0) return
+ + const actionDisabled = + amountErrorMessage || loading || amount == '' || Number(amount) === 0 || !cpData.exitGasOk + return ( @@ -121,9 +125,7 @@ export default function SkStepper(props: { skaleNetwork: SkaleNetwork }) { size="medium" className={cls(styles.btnAction, cmn.mtop5)} onClick={() => execute(address, switchNetworkAsync, walletClient)} - disabled={ - !!(amountErrorMessage || loading || amount == '' || !cpData.exitGasOk) - } + disabled={!!actionDisabled} > {step.btnText} diff --git a/src/components/WidgetUI/WidgetUI.tsx b/src/components/WidgetUI/WidgetUI.tsx index d9ad015..b522192 100644 --- a/src/components/WidgetUI/WidgetUI.tsx +++ b/src/components/WidgetUI/WidgetUI.tsx @@ -89,7 +89,7 @@ export function WidgetUI(props: { config: MetaportConfig }) {
{fabTop ? fabButton : null}
- + diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index a1a13d7..eecd984 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -213,7 +213,7 @@ export class UnWrapERC20 extends Action { this.updateState('unwrapDone', tx.hash, block.timestamp) } - async preAction() { } + async preAction() {} } export class UnWrapERC20S extends Action { diff --git a/src/core/dataclasses/ErrorMessage.ts b/src/core/dataclasses/ErrorMessage.ts index 84157e9..2e46e33 100644 --- a/src/core/dataclasses/ErrorMessage.ts +++ b/src/core/dataclasses/ErrorMessage.ts @@ -21,9 +21,7 @@ * @copyright SKALE Labs 2022-Present */ - -import { TRANSFER_ERROR_MSG } from "../constants" - +import { TRANSFER_ERROR_MSG } from '../constants' export class ErrorMessage { icon: string diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 76296b2..517de25 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -392,4 +392,4 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { mode: 'dark', vibrant: true } -} \ No newline at end of file +} From 6b02e77a67275071bed7ad35a649ba8f1dd665b4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 24 Oct 2023 13:46:15 +0100 Subject: [PATCH 095/110] metaport#193 lock amount during the transfer, update error handling --- src/components/AmountInput.tsx | 3 +- src/components/ErrorMessage.tsx | 49 ++++++++++++++++++++++++++++----- src/core/metaport.ts | 3 +- src/store/MetaportStore.ts | 3 ++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/components/AmountInput.tsx b/src/components/AmountInput.tsx index 500b529..86a8724 100644 --- a/src/components/AmountInput.tsx +++ b/src/components/AmountInput.tsx @@ -12,6 +12,7 @@ import { useCollapseStore } from '../store/Store' export default function AmountInput() { const { address } = useAccount() const transferInProgress = useMetaportStore((state) => state.transferInProgress) + const currentStep = useMetaportStore((state) => state.currentStep) const setAmount = useMetaportStore((state) => state.setAmount) const amount = useMetaportStore((state) => state.amount) const expandedTokens = useCollapseStore((state) => state.expandedTokens) @@ -41,7 +42,7 @@ export default function AmountInput() { placeholder="0.00" value={amount} onChange={handleChange} - disabled={transferInProgress} + disabled={transferInProgress || currentStep !== 0} style={{ width: '100%' }} />
diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx index b0a4794..722e953 100644 --- a/src/components/ErrorMessage.tsx +++ b/src/components/ErrorMessage.tsx @@ -16,10 +16,11 @@ import ErrorRoundedIcon from '@mui/icons-material/ErrorRounded' import HourglassTopRoundedIcon from '@mui/icons-material/HourglassTopRounded' import CrisisAlertRoundedIcon from '@mui/icons-material/CrisisAlertRounded' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import TextSnippetRoundedIcon from '@mui/icons-material/TextSnippetRounded' import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded' import AvTimerRoundedIcon from '@mui/icons-material/AvTimerRounded' - +import RestartAltRoundedIcon from '@mui/icons-material/RestartAltRounded'; +import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded'; +import SortRoundedIcon from '@mui/icons-material/SortRounded'; import { DEFAULT_ERROR_MSG } from '../core/constants' const ERROR_ICONS = { @@ -66,10 +67,44 @@ export default function Error(props: { errorMessage: ErrorMessage }) { )} >
- + +
+

+ Transfers might occasionally delay, but all tokens will be sent. +

+
+
+
+ +
+

+ If a transfer is interrupted, you can continue from where you stopped. +

+
+
+
+

- When transferring from SKALE to Ethereum Mainnet, there are frequency limitations. + Transfers from SKALE to Ethereum Mainnet have frequency limits.

- +

- Sometimes transfers may take more time than expected. + If you still have questions, consult FAQ or contact the support team.

@@ -100,7 +135,7 @@ export default function Error(props: { errorMessage: ErrorMessage }) { >
- +

{expanded === 'panel1' ? 'Hide' : 'Show'} error details diff --git a/src/core/metaport.ts b/src/core/metaport.ts index 7678f95..c5a8793 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -283,7 +283,8 @@ export default class MetaportCore { destTokenContract, destTokenBalance: null, destChains: Object.keys(token.connections), - amount: '' + amount: '', + currentStep: 0 } } diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts index 934ab9b..588c692 100644 --- a/src/store/MetaportStore.ts +++ b/src/store/MetaportStore.ts @@ -133,6 +133,9 @@ export const useMetaportStore = create()((set, get) => ({ if (err.info && err.info.error && err.info.error.data && err.info.error.data.message) { headline = err.info.error.data.message } + if (err.shortMessage) { + headline = err.shortMessage + } set({ errorMessage: new dataclasses.TransactionErrorMessage( msg, From 12ec1de934bf9ec265cf5898f71f347f66b009e0 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 24 Oct 2023 19:18:52 +0100 Subject: [PATCH 096/110] metaport#190 metaport#193 Fix small amounts, hide wrapped tokens --- src/components/DestTokenBalance.tsx | 1 - src/components/WidgetBody.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/DestTokenBalance.tsx b/src/components/DestTokenBalance.tsx index 1b7f44c..5967896 100644 --- a/src/components/DestTokenBalance.tsx +++ b/src/components/DestTokenBalance.tsx @@ -29,7 +29,6 @@ export default function DestTokenBalance() { balance={destTokenBalance} symbol={token.meta.symbol} decimals={token.meta.decimals} - truncate={9} /> ) } diff --git a/src/components/WidgetBody.tsx b/src/components/WidgetBody.tsx index ef82f92..8f844d6 100644 --- a/src/components/WidgetBody.tsx +++ b/src/components/WidgetBody.tsx @@ -116,7 +116,8 @@ export function WidgetBody(props) { !expandedCP && !expandedTH && sFuelOk && - !!address + !!address && + !!token const showTH = !expandedFrom && !expandedTo && @@ -154,7 +155,6 @@ export function WidgetBody(props) { balance={tokenBalances[token.keyname]} symbol={token.meta.symbol} decimals={token.meta.decimals} - truncate={9} /> ) : null}

From 479e9e797f952f1221101b593a613ee64bd690e9 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 26 Oct 2023 16:39:53 +0100 Subject: [PATCH 097/110] metaport#199 Fix token change error, update debug component, fix block explorer link --- src/components/CommunityPool.tsx | 3 +- src/components/Debug.tsx | 230 ++++++++++++++++++++++---- src/components/ErrorMessage.tsx | 6 +- src/components/MetaportProvider.tsx | 2 +- src/components/SFuelWarning.tsx | 2 +- src/components/TokenList.tsx | 5 +- src/core/metaport.ts | 17 +- src/index.ts | 2 + src/metadata/metaportConfigStaging.ts | 2 +- src/store/MetaportState.ts | 4 +- src/store/MetaportStore.ts | 5 +- src/styles/styles.module.scss | 11 ++ 12 files changed, 241 insertions(+), 48 deletions(-) diff --git a/src/components/CommunityPool.tsx b/src/components/CommunityPool.tsx index 4b96f79..4539361 100644 --- a/src/components/CommunityPool.tsx +++ b/src/components/CommunityPool.tsx @@ -81,7 +81,8 @@ export default function CommunityPool() { let chainName if (token && chainName2) { chainName = chainName1 - if (token.connections[chainName2].hub) chainName = token.connections[chainName2].hub + if (token.connections[chainName2] && token.connections[chainName2].hub) + chainName = token.connections[chainName2].hub } const handleChange = (panel: string) => (_: React.SyntheticEvent, isExpanded: boolean) => { diff --git a/src/components/Debug.tsx b/src/components/Debug.tsx index cc2f877..f091283 100644 --- a/src/components/Debug.tsx +++ b/src/components/Debug.tsx @@ -21,6 +21,8 @@ * @copyright SKALE Labs 2023-Present */ +import { useState, useEffect, useReducer } from 'react' + import Grid from '@mui/material/Grid' import Table from '@mui/material/Table' import TableBody from '@mui/material/TableBody' @@ -29,54 +31,216 @@ import TableContainer from '@mui/material/TableContainer' import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import Paper from '@mui/material/Paper' +import Button from '@mui/material/Button' + +import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded' +import CloseRoundedIcon from '@mui/icons-material/CloseRounded' +import ExpandRoundedIcon from '@mui/icons-material/ExpandRounded' import { useMetaportStore } from '../store/MetaportStore' -import { cls, cmn } from '../core/css' +import { cls, cmn, styles } from '../core/css' +import { Collapse } from '@mui/material' + +const initialState = { queue: [] } + +function reducer(state, action) { + switch (action.type) { + case 'enqueue': + return { queue: [...state.queue, action.payload] } + case 'dequeue': + return { queue: state.queue.slice(1) } + case 'empty': + return { queue: [] } + default: + throw new Error() + } +} + +function useQueue() { + const [state, dispatch] = useReducer(reducer, initialState) + + const enqueue = (item) => { + dispatch({ type: 'enqueue', payload: item }) + } + + const dequeue = () => { + dispatch({ type: 'dequeue' }) + } + + const empty = () => { + dispatch({ type: 'empty' }) + } + + return { + queue: state.queue, + enqueue, + dequeue, + empty + } +} + +function formatUTCTime() { + const now = new Date() + const hours = String(now.getUTCHours()).padStart(2, '0') + const minutes = String(now.getUTCMinutes()).padStart(2, '0') + const seconds = String(now.getUTCSeconds()).padStart(2, '0') + const milliseconds = String(now.getUTCMilliseconds()).padStart(3, '0') + return `${hours}:${minutes}:${seconds}.${milliseconds}` +} export default function Debug() { const debug = useMetaportStore((state) => state.mpc.config.debug) + if (!debug) return + + function stringifyBigInt(obj) { + return JSON.stringify(obj, (_, value) => (typeof value === 'bigint' ? value.toString() : value)) + } + + const { queue, enqueue, empty } = useQueue() + const [expanded, setExpanded] = useState(false) + const chainName1 = useMetaportStore((state) => state.chainName1) const chainName2 = useMetaportStore((state) => state.chainName2) const amount = useMetaportStore((state) => state.amount) const stepsMetadata = useMetaportStore((state) => state.stepsMetadata) - const rows = [ + const token = useMetaportStore((state) => state.token) + const tokenBalances = useMetaportStore((state) => state.tokenBalances) + const destTokenBalance = useMetaportStore((state) => state.destTokenBalance) + const tokenContracts = useMetaportStore((state) => state.tokenContracts) + const tokens = useMetaportStore((state) => state.tokens) + + const getRows = () => [ { name: 'chainName1', value: chainName1 }, { name: 'chainName2', value: chainName2 }, - { name: 'amount', value: amount }, - { name: 'stepsMetadata', value: JSON.stringify(stepsMetadata) } + { name: 'token', value: JSON.stringify(token) }, + { name: 'tokenBalances', value: stringifyBigInt(tokenBalances) }, + { name: 'destTokenBalance', value: stringifyBigInt(destTokenBalance) }, + { + name: 'tokenContracts', + value: + '(' + Object.keys(tokenContracts).length + ') ' + Object.keys(tokenContracts).join(', ') + }, + { + name: 'tokens', + value: '(' + Object.keys(tokens.erc20).length + ') ' + Object.keys(tokens.erc20).join(', ') + } ] - if (!debug) return + const [prevRows, setPrevRows] = useState(getRows) + + useEffect(() => { + const currentRows = getRows() + currentRows.forEach((row, index) => { + if (row.value !== prevRows[index]?.value) { + enqueue({ + name: row.name, + oldValue: prevRows[index]?.value, + value: row.value, + time: formatUTCTime() + }) + } + }) + setPrevRows(currentRows) + }, [ + chainName1, + chainName2, + amount, + stepsMetadata, + token, + tokenBalances, + destTokenBalance, + tokenContracts, + tokens + ]) + return ( -
- - - -
- - - Property - Value - - - - {rows.map((row) => ( - - - {row.name} - - {row.value} - - ))} - -
-
-
-
+
+ + +
+ + + + + + + Property + Value + + + + {getRows().map((row) => ( + + + {row.name} + + + {row.value} + + + ))} + +
+
+ + + + + + Property + Old value + New value + Time + + + + {queue.map((row, i) => ( + + + {row.name} + + + {row.oldValue} + + + {row.value} + + + {row.time} + + + ))} + +
+
+
+
+
+
) } diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx index 722e953..a2b0414 100644 --- a/src/components/ErrorMessage.tsx +++ b/src/components/ErrorMessage.tsx @@ -18,9 +18,9 @@ import CrisisAlertRoundedIcon from '@mui/icons-material/CrisisAlertRounded' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import HourglassBottomRoundedIcon from '@mui/icons-material/HourglassBottomRounded' import AvTimerRoundedIcon from '@mui/icons-material/AvTimerRounded' -import RestartAltRoundedIcon from '@mui/icons-material/RestartAltRounded'; -import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded'; -import SortRoundedIcon from '@mui/icons-material/SortRounded'; +import RestartAltRoundedIcon from '@mui/icons-material/RestartAltRounded' +import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded' +import SortRoundedIcon from '@mui/icons-material/SortRounded' import { DEFAULT_ERROR_MSG } from '../core/constants' const ERROR_ICONS = { diff --git a/src/components/MetaportProvider.tsx b/src/components/MetaportProvider.tsx index 0e43de3..4f8a435 100644 --- a/src/components/MetaportProvider.tsx +++ b/src/components/MetaportProvider.tsx @@ -125,7 +125,7 @@ export default function MetaportProvider(props: { if (actionStateUpdate.transactionHash) { let chainName = actionStateUpdate.actionData.chainName1 if ( - actionStateUpdate.actionState === 'transferETHDone' || + actionStateUpdate.actionState === 'unlockDone' || actionStateUpdate.actionState === 'unwrapDone' ) { chainName = actionStateUpdate.actionData.chainName2 diff --git a/src/components/SFuelWarning.tsx b/src/components/SFuelWarning.tsx index 32c3d10..0030475 100644 --- a/src/components/SFuelWarning.tsx +++ b/src/components/SFuelWarning.tsx @@ -74,7 +74,7 @@ export default function SFuelWarning(props: {}) { let hubChain - if (token && chainName2 && token.connections[chainName2].hub) { + if (token && chainName2 && token.connections[chainName2] && token.connections[chainName2].hub) { hubChain = token.connections[chainName2].hub } diff --git a/src/components/TokenList.tsx b/src/components/TokenList.tsx index e81f6c8..e6e8a53 100644 --- a/src/components/TokenList.tsx +++ b/src/components/TokenList.tsx @@ -41,10 +41,13 @@ export default function TokenList() { const intervalId = setInterval(() => { updateTokenBalances(address) }, BALANCE_UPDATE_INTERVAL_MS) - return () => { clearInterval(intervalId) // Clear interval on component unmount } + }, [updateTokenBalances]) + + useEffect(() => { + updateTokenBalances(address) }, [updateTokenBalances, tokenContracts, address]) useEffect(() => { diff --git a/src/core/metaport.ts b/src/core/metaport.ts index c5a8793..403747e 100644 --- a/src/core/metaport.ts +++ b/src/core/metaport.ts @@ -262,10 +262,18 @@ export default class MetaportCore { tokenChanged( chainName1: string, ima2: MainnetChain | SChain, - token: TokenData, + token: TokenData | null | undefined, destChainName?: string ): Partial { - if (!token) return {} + if (token === undefined || token === null) + return { + token: null, + destTokenContract: null, + destTokenBalance: null, + stepsMetadata: [], + amount: '', + currentStep: 0 + } let destTokenContract if (destChainName) { destTokenContract = this.tokenContract( @@ -324,7 +332,10 @@ export default class MetaportCore { const prevTokenKeyname = prevToken?.keyname const prevTokenType = prevToken?.type - const token = prevTokenKeyname ? tokens[prevTokenType][prevTokenKeyname] : null + const token = + prevTokenKeyname && tokens[prevTokenType][prevTokenKeyname] + ? tokens[prevTokenType][prevTokenKeyname] + : null return { ima1, diff --git a/src/index.ts b/src/index.ts index 55daa24..0433dad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import SFuelWarning from './components/SFuelWarning' import WrappedTokens from './components/WrappedTokens' import History from './components/History' import TransactionData from './components/TransactionData' +import Debug from './components/Debug' import { CHAINS_META, getChainAlias } from './core/metadata' import { cls, styles, cmn } from './core/css' @@ -70,6 +71,7 @@ export { WrappedTokens, History, TransactionData, + Debug, cls, styles, cmn, diff --git a/src/metadata/metaportConfigStaging.ts b/src/metadata/metaportConfigStaging.ts index 517de25..396cd7c 100644 --- a/src/metadata/metaportConfigStaging.ts +++ b/src/metadata/metaportConfigStaging.ts @@ -4,7 +4,7 @@ export const METAPORT_CONFIG: interfaces.MetaportConfig = { skaleNetwork: 'staging', openOnLoad: true, openButton: true, - debug: false, + debug: true, chains: [ 'mainnet', 'staging-legal-crazy-castor', // Europa diff --git a/src/store/MetaportState.ts b/src/store/MetaportState.ts index 060eb0d..bc5e7f1 100644 --- a/src/store/MetaportState.ts +++ b/src/store/MetaportState.ts @@ -82,8 +82,8 @@ export interface MetaportState { tokens: interfaces.TokenDataTypesMap - token: dataclasses.TokenData - setToken: (token: dataclasses.TokenData) => void + token: dataclasses.TokenData | null + setToken: (token: dataclasses.TokenData | null) => void tokenContracts: interfaces.TokenContractsMap tokenBalances: interfaces.TokenBalancesMap diff --git a/src/store/MetaportStore.ts b/src/store/MetaportStore.ts index 588c692..68f674e 100644 --- a/src/store/MetaportStore.ts +++ b/src/store/MetaportStore.ts @@ -256,7 +256,7 @@ export const useMetaportStore = create()((set, get) => ({ token: null, - setToken: async (token: dataclasses.TokenData) => { + setToken: async (token: dataclasses.TokenData | null) => { set(get().mpc.tokenChanged(get().chainName1, get().ima2, token, get().chainName2)) }, @@ -290,7 +290,8 @@ export const useMetaportStore = create()((set, get) => ({ }, updateTokenBalances: async (address: string) => { - if (!address) { + const tokenContracts = get().tokenContracts + if (!address || Object.keys(tokenContracts).length === 0) { set({ tokenBalances: {} }) return } diff --git a/src/styles/styles.module.scss b/src/styles/styles.module.scss index c2f1260..0ab375b 100644 --- a/src/styles/styles.module.scss +++ b/src/styles/styles.module.scss @@ -400,4 +400,15 @@ button { -moz-appearance: textfield; /* Firefox */ } +} + +.smallTable{ + code { + font-size: 0.7rem; + display: block; + max-width: 400px; + white-space: nowrap; + overflow: auto; + text-overflow: ellipsis; + } } \ No newline at end of file From 81e52f2c6b29c0c47f7bcb499c252a3689f04063 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 26 Oct 2023 17:25:06 +0100 Subject: [PATCH 098/110] Restructure display conditions --- src/components/WidgetBody.tsx | 90 ++++----------------- src/core/constants.ts | 2 + src/store/DisplayFunctions.ts | 146 ++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 74 deletions(-) create mode 100644 src/store/DisplayFunctions.ts diff --git a/src/components/WidgetBody.tsx b/src/components/WidgetBody.tsx index 8f844d6..c1288f0 100644 --- a/src/components/WidgetBody.tsx +++ b/src/components/WidgetBody.tsx @@ -1,10 +1,11 @@ import { useEffect } from 'react' import { useAccount } from 'wagmi' +import { Collapse } from '@mui/material' import { useCollapseStore } from '../store/Store' import { useMetaportStore } from '../store/MetaportStore' -import { useSFuelStore } from '../store/SFuelStore' import { useUIStore } from '../store/Store' +import { useDisplayFunctions } from '../store/DisplayFunctions' import ChainsList from './ChainsList' import AmountInput from './AmountInput' @@ -22,24 +23,19 @@ import TransactionsHistory from './HistorySection' import HistoryButton from './HistoryButton' import { cls, cmn } from '../core/css' -import { Collapse } from '@mui/material' -import { MAINNET_CHAIN_NAME } from '../core/constants' import { chainBg } from '../core/metadata' +import { GRAY_BG } from '../core/constants' export function WidgetBody(props) { + const { showFrom, showTo, showInput, showSwitch, showStepper, showCP, showWT, showTH } = + useDisplayFunctions() + const expandedFrom = useCollapseStore((state) => state.expandedFrom) const setExpandedFrom = useCollapseStore((state) => state.setExpandedFrom) - const expandedTo = useCollapseStore((state) => state.expandedTo) const setExpandedTo = useCollapseStore((state) => state.setExpandedTo) - const expandedCP = useCollapseStore((state) => state.expandedCP) - const expandedWT = useCollapseStore((state) => state.expandedWT) - const expandedTokens = useCollapseStore((state) => state.expandedTokens) - const expandedTH = useCollapseStore((state) => state.expandedTH) - const destChains = useMetaportStore((state) => state.destChains) - const token = useMetaportStore((state) => state.token) const chainName1 = useMetaportStore((state) => state.chainName1) @@ -57,12 +53,8 @@ export function WidgetBody(props) { const setToken = useMetaportStore((state) => state.setToken) const tokenBalances = useMetaportStore((state) => state.tokenBalances) - const errorMessage = useMetaportStore((state) => state.errorMessage) - const transferInProgress = useMetaportStore((state) => state.transferInProgress) - const sFuelOk = useSFuelStore((state) => state.sFuelOk) - const theme = useUIStore((state) => state.theme) const { address } = useAccount() @@ -78,58 +70,8 @@ export function WidgetBody(props) { } }, [tokens]) - const showFrom = !expandedTo && !expandedTokens && !errorMessage && !expandedCP && !expandedTH - const showTo = - !expandedFrom && !expandedTokens && !errorMessage && !expandedCP && !expandedWT && !expandedTH - const showInput = - !expandedFrom && !expandedTo && !errorMessage && !expandedCP && !expandedWT && !expandedTH - const showSwitch = - !expandedFrom && - !expandedTo && - !expandedTokens && - !errorMessage && - !expandedCP && - !expandedWT && - !expandedTH - const showStepper = - !expandedFrom && - !expandedTo && - !expandedTokens && - !errorMessage && - !expandedCP && - sFuelOk && - !expandedWT && - !expandedTH && - !!address - const showCP = - !expandedFrom && - !expandedTo && - !expandedTokens && - !expandedTH && - chainName2 === MAINNET_CHAIN_NAME && - !expandedWT - const showWT = - !expandedFrom && - !expandedTo && - !expandedTokens && - !errorMessage && - !expandedCP && - !expandedTH && - sFuelOk && - !!address && - !!token - const showTH = - !expandedFrom && - !expandedTo && - !expandedTokens && - !errorMessage && - !expandedCP && - !expandedWT && - !!address - - const grayBg = 'rgb(136 135 135 / 15%)' - const sourceBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName1, appName1) : grayBg - const destBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName2, appName2) : grayBg + const sourceBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName1, appName1) : GRAY_BG + const destBg = theme.vibrant ? chainBg(mpc.config.skaleNetwork, chainName2, appName2) : GRAY_BG return (
@@ -144,7 +86,7 @@ export function WidgetBody(props) { ) : null} - +

From {appName1 ? 'app' : ''} @@ -174,18 +116,18 @@ export function WidgetBody(props) { /> - + - + - +

@@ -209,19 +151,19 @@ export function WidgetBody(props) { - + - + - + @@ -230,7 +172,7 @@ export function WidgetBody(props) { - + diff --git a/src/core/constants.ts b/src/core/constants.ts index 3481e2e..6966000 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -112,3 +112,5 @@ export const BALANCE_UPDATE_INTERVAL_MS = _BALANCE_UPDATE_INTERVAL_SECONDS * 100 export const SFUEL_RESERVE_AMOUNT = 0.02 export const SUCCESS_EMOJIS = ['🎉', '👌', '✅', '🙌', '🎊'] + +export const GRAY_BG = 'rgb(136 135 135 / 15%)' diff --git a/src/store/DisplayFunctions.ts b/src/store/DisplayFunctions.ts new file mode 100644 index 0000000..7678d8e --- /dev/null +++ b/src/store/DisplayFunctions.ts @@ -0,0 +1,146 @@ +/** + * @license + * SKALE Metaport + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** + * @file DisplayStore.ts + * @copyright SKALE Labs 2023-Present + */ + +import { useCollapseStore } from '../store/Store' +import { useMetaportStore } from '../store/MetaportStore' +import { useSFuelStore } from '../store/SFuelStore' + +import { MAINNET_CHAIN_NAME } from '../core/constants' +import { AddressType } from '../core/interfaces' + +type DisplayFunctions = { + showFrom: () => boolean + showTo: () => boolean + showSwitch: () => boolean + showInput: () => boolean + showStepper: (address: AddressType) => boolean + showCP: () => boolean + showWT: (address: AddressType) => boolean + showTH: (address: AddressType) => boolean +} + +export const useDisplayFunctions = (): DisplayFunctions => { + const expandedFrom = useCollapseStore((state) => state.expandedFrom) + const expandedTo = useCollapseStore((state) => state.expandedTo) + const expandedTokens = useCollapseStore((state) => state.expandedTokens) + const expandedCP = useCollapseStore((state) => state.expandedCP) + const expandedTH = useCollapseStore((state) => state.expandedTH) + const expandedWT = useCollapseStore((state) => state.expandedWT) + + const chainName2 = useMetaportStore((state) => state.chainName2) + const token = useMetaportStore((state) => state.token) + const errorMessage = useMetaportStore((state) => state.errorMessage) + + const sFuelOk = useSFuelStore((state) => state.sFuelOk) + + const showFrom = (): boolean => { + return !expandedTo && !expandedTokens && !errorMessage && !expandedCP && !expandedTH + } + + const showTo = (): boolean => { + return ( + !expandedFrom && !expandedTokens && !errorMessage && !expandedCP && !expandedWT && !expandedTH + ) + } + + const showInput = (): boolean => { + return ( + !expandedFrom && !expandedTo && !errorMessage && !expandedCP && !expandedWT && !expandedTH + ) + } + + const showSwitch = (): boolean => { + return ( + !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + !expandedWT && + !expandedTH + ) + } + + const showStepper = (address: AddressType): boolean => { + return ( + !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + sFuelOk && + !expandedWT && + !expandedTH && + !!address + ) + } + + const showCP = (): boolean => { + return ( + !expandedFrom && + !expandedTo && + !expandedTokens && + !expandedTH && + chainName2 === MAINNET_CHAIN_NAME && + !expandedWT && + !!token + ) + } + + const showWT = (address: AddressType): boolean => { + return ( + !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + !expandedTH && + sFuelOk && + !!address && + !!token + ) + } + + const showTH = (address: AddressType): boolean => { + return ( + !expandedFrom && + !expandedTo && + !expandedTokens && + !errorMessage && + !expandedCP && + !expandedWT && + !!address + ) + } + + return { + showFrom, + showTo, + showInput, + showSwitch, + showStepper, + showCP, + showWT, + showTH + } +} From 371ae1093c2fb6f1c8489b156863233219267519 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 26 Oct 2023 17:27:40 +0100 Subject: [PATCH 099/110] metaport#199 Export display functions --- src/index.ts | 4 +++- src/store/DisplayFunctions.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0433dad..0719241 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export { useMetaportStore } from './store/MetaportStore' export { type MetaportState } from './store/MetaportState' export { useUIStore, useCollapseStore, type UIState, type CollapseState } from './store/Store' export { useSFuelStore, type SFuelState } from './store/SFuelStore' +export { useDisplayFunctions, type DisplayFunctions } from './store/DisplayFunctions' import Metaport from './components/Metaport' import MetaportProvider from './components/MetaportProvider' @@ -37,7 +38,7 @@ import { CHAINS_META, getChainAlias } from './core/metadata' import { cls, styles, cmn } from './core/css' import MetaportCore from './core/metaport' import { chainBg } from './core/metadata' -import { BASE_EXPLORER_URLS } from './core/constants' +import { BASE_EXPLORER_URLS, GRAY_BG } from './core/constants' import { toWei, fromWei } from './core/convertation' import { getWidgetTheme as getMetaportTheme } from './core/themes' @@ -84,6 +85,7 @@ export { PROXY_ENDPOINTS, BASE_EXPLORER_URLS, CHAINS_META, + GRAY_BG, chainBg, getChainAlias, RainbowConnectButton diff --git a/src/store/DisplayFunctions.ts b/src/store/DisplayFunctions.ts index 7678d8e..dd43814 100644 --- a/src/store/DisplayFunctions.ts +++ b/src/store/DisplayFunctions.ts @@ -28,7 +28,7 @@ import { useSFuelStore } from '../store/SFuelStore' import { MAINNET_CHAIN_NAME } from '../core/constants' import { AddressType } from '../core/interfaces' -type DisplayFunctions = { +export type DisplayFunctions = { showFrom: () => boolean showTo: () => boolean showSwitch: () => boolean From 03ab3eb6d06b9a53edae3931028c145b669ca796 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 26 Oct 2023 20:09:51 +0100 Subject: [PATCH 100/110] metaport#201 Improve network switching --- src/components/AddToken.tsx | 8 +++++++- src/components/MetaportProvider.tsx | 3 ++- src/core/actions/action.ts | 27 ++++++++++++--------------- src/core/actions/erc20.ts | 3 ++- src/core/actions/eth.ts | 7 ++++++- src/core/network.ts | 18 +++++++++++++----- 6 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/components/AddToken.tsx b/src/components/AddToken.tsx index 1dccb66..5bad728 100644 --- a/src/components/AddToken.tsx +++ b/src/components/AddToken.tsx @@ -69,7 +69,13 @@ export default function AddToken(props: { ) const iconUrl = getIconUrl(props.token) try { - await enforceNetwork(props.ima.provider, walletClient, switchNetworkAsync) + await enforceNetwork( + props.ima.provider, + walletClient, + switchNetworkAsync, + props.mpc.config.skaleNetwork, + props.destChainName + ) const wasAdded = await window.ethereum.request({ method: 'wallet_watchAsset', params: { diff --git a/src/components/MetaportProvider.tsx b/src/components/MetaportProvider.tsx index 4f8a435..64b2a08 100644 --- a/src/components/MetaportProvider.tsx +++ b/src/components/MetaportProvider.tsx @@ -162,7 +162,8 @@ export default function MetaportProvider(props: { > setBtnText: (btnText: string) => void - _switchNetwork: (chainId: number | bigint) => Chain | undefined + _switchNetwork: (chainId: number | bigint) => Promise constructor( mpc: MetaportCore, @@ -93,7 +93,7 @@ export class Action { token: TokenData, setAmountErrorMessage: (amountErrorMessage: string) => void, setBtnText: (btnText: string) => void, - switchNetwork: (chainId: number | bigint) => Chain | undefined, + switchNetwork: (chainId: number | bigint) => Promise, walletClient: WalletClient ) { this.mpc = mpc @@ -183,21 +183,18 @@ export class Action { async getConnectedChain( provider: Provider, customAbiTokenType?: CustomAbiTokenType, - destChainName?: string + destChainName?: string, + chainName?: string ): Promise { let chain: MainnetChain | SChain this.updateState('switch') - const currentChainId = this.walletClient.chain.id - const { chainId } = await provider.getNetwork() - log(`Current chainId: ${currentChainId}, required chainId: ${chainId} `) - if (currentChainId !== Number(chainId)) { - log(`Switching network to ${chainId}...`) - const chain = await this._switchNetwork(Number(chainId)) - if (!chain) { - throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `) - } - log(`Network switched to ${chainId}...`) - } + const chainId = await enforceNetwork( + provider, + this.walletClient, + this._switchNetwork, + this.mpc.config.skaleNetwork, + chainName ?? this.chainName1 + ) const signer = walletClientToSigner(this.walletClient) if (isMainnetChainId(chainId, this.mpc.config.skaleNetwork)) { chain = new MainnetChain(signer.provider, getMainnetAbi(this.mpc.config.skaleNetwork)) diff --git a/src/core/actions/erc20.ts b/src/core/actions/erc20.ts index eecd984..9cb3dc5 100644 --- a/src/core/actions/erc20.ts +++ b/src/core/actions/erc20.ts @@ -221,7 +221,8 @@ export class UnWrapERC20S extends Action { const sChain = (await this.getConnectedChain( this.sChain2.provider, CustomAbiTokenType.erc20wrap, - this.chainName1 + this.chainName1, + this.chainName2 )) as SChain this.updateState('unwrap') let tx diff --git a/src/core/actions/eth.ts b/src/core/actions/eth.ts index b0dbcff..87caa76 100644 --- a/src/core/actions/eth.ts +++ b/src/core/actions/eth.ts @@ -100,7 +100,12 @@ export class UnlockEthM extends Action { async execute() { this.updateState('init') - const mainnet = (await this.getConnectedChain(this.mainnet.provider)) as MainnetChain + const mainnet = (await this.getConnectedChain( + this.mainnet.provider, + undefined, + undefined, + this.chainName2 + )) as MainnetChain this.updateState('unlock') const tx = await mainnet.eth.getMyEth({ address: this.address }) const block = await this.mainnet.provider.getBlock(tx.blockNumber) diff --git a/src/core/network.ts b/src/core/network.ts index ba5708d..35382ee 100644 --- a/src/core/network.ts +++ b/src/core/network.ts @@ -32,6 +32,7 @@ import proxyEndpoints from '../metadata/proxy.json' import { MAINNET_CHAIN_NAME } from './constants' import { IMA_ADDRESSES, IMA_ABIS } from './contracts' import { SkaleNetwork } from './interfaces' +import { constructWagmiChain } from './wagmi_network' export { proxyEndpoints as PROXY_ENDPOINTS } @@ -117,17 +118,24 @@ export function initSChain(network: SkaleNetwork, chainName: string): SChain { export async function enforceNetwork( provider: Provider, walletClient: WalletClient, - switchNetwork: (chainId: number | bigint) => Promise -): Promise { + switchNetwork: (chainId: number | bigint) => Promise, + skaleNetwork: SkaleNetwork, + chainName: string +): Promise { const currentChainId = walletClient.chain.id const { chainId } = await provider.getNetwork() log(`Current chainId: ${currentChainId}, required chainId: ${chainId} `) if (currentChainId !== Number(chainId)) { log(`Switching network to ${chainId}...`) - const chain = await switchNetwork(Number(chainId)) - if (!chain) { - throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `) + if (chainId !== 1n && chainId !== 5n) { + await walletClient.addChain({ chain: constructWagmiChain(skaleNetwork, chainName) }) + } else { + const chain = await switchNetwork(Number(chainId)) + if (!chain) { + throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `) + } } log(`Network switched to ${chainId}...`) } + return chainId } From 783271b694668f17cf871d32162be9ec3c0d5050 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 30 Oct 2023 17:10:48 +0000 Subject: [PATCH 101/110] Minor UI changes --- src/components/TokenListSection.tsx | 2 +- src/styles/_variables.scss | 2 +- src/styles/styles.module.scss | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/TokenListSection.tsx b/src/components/TokenListSection.tsx index 29cea40..6ac5921 100644 --- a/src/components/TokenListSection.tsx +++ b/src/components/TokenListSection.tsx @@ -39,7 +39,7 @@ export default function TokenListSection(props: { className={cmn.fullWidth} onClick={() => handle(props.tokens[key])} > -

+
Date: Mon, 30 Oct 2023 17:22:04 +0000 Subject: [PATCH 102/110] Fix CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4500cbe..f0f8a57 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ -* @dmitry-tk +* @dmytrotkk *.md @skalenetwork/docowners /docs/ @skalenetwork/docowners From 0aa33d8c76d4956e92e2295f041aefc5cd6ab05f Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 31 Oct 2023 11:44:20 +0000 Subject: [PATCH 103/110] Update apps metadata --- skale-network | 2 +- src/components/ChainApps.tsx | 2 +- src/styles/styles.module.scss | 8 +------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/skale-network b/skale-network index d9cf68a..3dd97b8 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit d9cf68a4b863dcd2b3cc01b5b22a99e713e68293 +Subproject commit 3dd97b8c4af21af8077a20346ee63d8360e830ae diff --git a/src/components/ChainApps.tsx b/src/components/ChainApps.tsx index 55c9cb8..9b6a199 100644 --- a/src/components/ChainApps.tsx +++ b/src/components/ChainApps.tsx @@ -21,7 +21,7 @@ export default function ChainApps(props: { return (
-
+
{Object.keys(apps).map((key, _) => ( + + + + + Action + chainName1 + chainName2 + amountWei + address + Time + + + + {queueAction.map((row, i) => ( + + + {row.action} + + + {row.chainName1} + + + {row.chainName2} + + + {row.amountWei} + + + {row.address} + + + {row.time} + + + ))} + +
+
diff --git a/src/components/TokenListSection.tsx b/src/components/TokenListSection.tsx index 6ac5921..fcf8d70 100644 --- a/src/components/TokenListSection.tsx +++ b/src/components/TokenListSection.tsx @@ -63,10 +63,10 @@ export default function TokenListSection(props: {
diff --git a/src/components/WidgetBody.tsx b/src/components/WidgetBody.tsx index c1288f0..571eede 100644 --- a/src/components/WidgetBody.tsx +++ b/src/components/WidgetBody.tsx @@ -65,8 +65,15 @@ export function WidgetBody(props) { }, []) useEffect(() => { - if (tokens && tokens.erc20 && Object.values(tokens.erc20)[0] && !token) { - setToken(Object.values(tokens.erc20)[0]) + if (tokens && !token) { + if (tokens.erc20 && Object.values(tokens.erc20)[0]) { + setToken(Object.values(tokens.erc20)[0]) + return + } + if (tokens.eth && tokens.eth.eth) { + setToken(tokens.eth.eth) + return + } } }, [tokens]) diff --git a/src/components/WrappedTokens.tsx b/src/components/WrappedTokens.tsx index 8bc8fe4..fbf0943 100644 --- a/src/components/WrappedTokens.tsx +++ b/src/components/WrappedTokens.tsx @@ -165,7 +165,7 @@ export default function WrappedTokens() { ()((set, get) => ({ }, updateTokenBalances: async (address: string) => { - const tokenContracts = get().tokenContracts - if (!address || Object.keys(tokenContracts).length === 0) { + if (!address) { set({ tokenBalances: {} }) return } From d361a2ffeb7fa3082b030f130e58e526b0fa944d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 1 Nov 2023 16:30:44 +0000 Subject: [PATCH 105/110] Update chains display conditions, add sorting to tokens, update interfaces --- skale-network | 2 +- src/components/Chain.tsx | 5 ++++- src/components/ChainApps.tsx | 13 +++++++----- src/components/ChainsList.tsx | 31 +++++++++++++++++++++++++++-- src/components/Debug.tsx | 3 +-- src/components/TokenListSection.tsx | 2 +- src/components/WidgetBody.tsx | 3 ++- src/core/constants.ts | 2 +- src/core/dataclasses/TokenData.ts | 6 +++--- src/core/interfaces/Tokens.ts | 7 +++++-- 10 files changed, 55 insertions(+), 19 deletions(-) diff --git a/skale-network b/skale-network index 3dd97b8..676c221 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 3dd97b8c4af21af8077a20346ee63d8360e830ae +Subproject commit 676c2213b917f48a759c61ebe34be3520ece239f diff --git a/src/components/Chain.tsx b/src/components/Chain.tsx index 2ae10eb..7dc0118 100644 --- a/src/components/Chain.tsx +++ b/src/components/Chain.tsx @@ -35,8 +35,10 @@ export default function Chain(props: { app?: string size?: Size decIcon?: boolean + prim?: boolean }) { const size = props.size ?? 'sm' + const prim = props.prim ?? true return (
{getChainAlias(props.skaleNetwork, props.chainName, props.app)} diff --git a/src/components/ChainApps.tsx b/src/components/ChainApps.tsx index 9b6a199..0dce9f5 100644 --- a/src/components/ChainApps.tsx +++ b/src/components/ChainApps.tsx @@ -11,7 +11,8 @@ export default function ChainApps(props: { skaleNetwork: SkaleNetwork chainName: string handle?: (schainName: string, app?: string) => void - size?: 'sm' | 'md' + size?: 'sm' | 'md', + prim?: boolean }) { const apps = getChainAppsMeta(props.chainName, props.skaleNetwork) if (!apps || !Object.keys(apps) || Object.keys(apps).length === 0) return
@@ -19,6 +20,8 @@ export default function ChainApps(props: { const size = props.size ?? 'sm' const iconSize = props.size === 'sm' ? 'xs' : 'sm' + const prim = props.prim ?? size === 'md' + return (
@@ -55,8 +58,8 @@ export default function ChainApps(props: { cmn.p, [cmn.p3, size === 'md'], [cmn.p4, size === 'sm'], - [cmn.pSec, size === 'sm'], - [cmn.pPrim, size === 'md'], + [cmn.pSec, !prim], + [cmn.pPrim, prim], cmn.p600, cmn.mleft10 )} @@ -66,8 +69,8 @@ export default function ChainApps(props: {
(_: React.SyntheticEvent, isExpanded: boolean) => { props.setExpanded(isExpanded ? panel : false) @@ -134,9 +137,32 @@ export default function ChainsList(props: { cmn.fullWidth )} > - +
- + {props.destChains && !props.destChains?.includes(name) ? ( + + + + ) : null} +
@@ -145,6 +171,7 @@ export default function ChainsList(props: { chainName={name} handle={handle} size={size} + prim={props.destChains?.includes(name)} />
diff --git a/src/components/Debug.tsx b/src/components/Debug.tsx index baabde8..ad6dcf1 100644 --- a/src/components/Debug.tsx +++ b/src/components/Debug.tsx @@ -99,7 +99,7 @@ export default function Debug() { const { queue, enqueue, empty } = useQueue() - const { queue: queueAction, enqueue: enqueueAction, empty: emptyAction } = useQueue(); + const { queue: queueAction, enqueue: enqueueAction, empty: emptyAction } = useQueue() const [expanded, setExpanded] = useState(false) @@ -137,7 +137,6 @@ export default function Debug() { window.addEventListener('metaport_actionStateUpdated', actionStateUpdated, false) }, []) - function actionStateUpdated(e: CustomEvent) { const actionStateUpdate: ActionStateUpdate = e.detail enqueueAction({ diff --git a/src/components/TokenListSection.tsx b/src/components/TokenListSection.tsx index fcf8d70..9fbc87e 100644 --- a/src/components/TokenListSection.tsx +++ b/src/components/TokenListSection.tsx @@ -31,7 +31,7 @@ export default function TokenListSection(props: { > {props.type}

- {Object.keys(props.tokens).map((key, _) => ( + {Object.keys(props.tokens).sort().map((key, _) => (
- - ))} + + ))}
) } diff --git a/src/core/interfaces/Tokens.ts b/src/core/interfaces/Tokens.ts index 81e4ed4..1329ced 100644 --- a/src/core/interfaces/Tokens.ts +++ b/src/core/interfaces/Tokens.ts @@ -21,8 +21,7 @@ * @copyright SKALE Labs 2022-Present */ -import { AddressType } from "." - +import { AddressType } from '.' export interface EthToken { chains: ConnectedChainMap From 4285d47f794eed59742396fa625185b9790e2954 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 6 Nov 2023 13:24:28 +0000 Subject: [PATCH 108/110] Update ensure network --- skale-network | 2 +- src/core/constants.ts | 3 +++ src/core/helper.ts | 4 ++++ src/core/network.ts | 52 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/skale-network b/skale-network index 676c221..261d890 160000 --- a/skale-network +++ b/skale-network @@ -1 +1 @@ -Subproject commit 676c2213b917f48a759c61ebe34be3520ece239f +Subproject commit 261d890cb564312cc0d1fae4dc500bd7d2b2759d diff --git a/src/core/constants.ts b/src/core/constants.ts index cd1bf8d..ec1e8ac 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -114,3 +114,6 @@ export const SFUEL_RESERVE_AMOUNT = 0.02 export const SUCCESS_EMOJIS = ['🎉', '👌', '✅', '🙌', '🎊'] export const GRAY_BG = 'rgb(136 135 135 / 15%)' + +export const DEFAULT_SLEEP = 6000 +export const DEFAULT_ITERATIONS = 60 diff --git a/src/core/helper.ts b/src/core/helper.ts index eedd40b..6ace6f4 100644 --- a/src/core/helper.ts +++ b/src/core/helper.ts @@ -52,3 +52,7 @@ export function delay(ms: number) { export function getRandom(list: Array) { return list[Math.floor(Math.random() * list.length)] } + +export async function sleep(ms: number): Promise { + return await new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/src/core/network.ts b/src/core/network.ts index 35382ee..2df301b 100644 --- a/src/core/network.ts +++ b/src/core/network.ts @@ -22,17 +22,18 @@ */ import debug from 'debug' -import { MainnetChain, SChain } from '@skalenetwork/ima-js' +import { MainnetChain, SChain, TimeoutException } from '@skalenetwork/ima-js' import { JsonRpcProvider, Provider } from 'ethers' import { WalletClient } from 'viem' import { Chain } from '@wagmi/core' import proxyEndpoints from '../metadata/proxy.json' -import { MAINNET_CHAIN_NAME } from './constants' +import { MAINNET_CHAIN_NAME, DEFAULT_ITERATIONS, DEFAULT_SLEEP } from './constants' import { IMA_ADDRESSES, IMA_ABIS } from './contracts' import { SkaleNetwork } from './interfaces' import { constructWagmiChain } from './wagmi_network' +import { sleep } from './helper' export { proxyEndpoints as PROXY_ENDPOINTS } @@ -115,6 +116,36 @@ export function initSChain(network: SkaleNetwork, chainName: string): SChain { return new SChain(provider, IMA_ABIS.schain) } +async function waitForNetworkChange( + walletClient: WalletClient, + initialChainId: number | bigint, + requiredChainId: number | bigint, + sleepInterval: number = DEFAULT_SLEEP, + iterations: number = DEFAULT_ITERATIONS +): Promise { + const logData = `${initialChainId} -> ${requiredChainId}, sleep ${sleepInterval}ms` + for (let i = 1; i <= iterations; i++) { + const chainId = await walletClient.getChainId() + if (BigInt(chainId) === BigInt(requiredChainId)) { + return + } + log(`🔎 ${i}/${iterations} Waiting for network change - ${logData}`) + await sleep(sleepInterval) + } + throw new TimeoutException('waitForNetworkChange timeout - ' + logData) +} + +async function _networkSwitch( + chainId: number | bigint, + currentChainId: number | bigint, + switchNetwork: (chainId: number | bigint) => Promise +): Promise { + const chain = await switchNetwork(Number(chainId)) + if (!chain) { + throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `) + } +} + export async function enforceNetwork( provider: Provider, walletClient: WalletClient, @@ -124,17 +155,22 @@ export async function enforceNetwork( ): Promise { const currentChainId = walletClient.chain.id const { chainId } = await provider.getNetwork() - log(`Current chainId: ${currentChainId}, required chainId: ${chainId} `) + log( + `Current chainId: ${currentChainId}, required chainId: ${chainId}, required network: ${chainName} ` + ) if (currentChainId !== Number(chainId)) { log(`Switching network to ${chainId}...`) if (chainId !== 1n && chainId !== 5n) { await walletClient.addChain({ chain: constructWagmiChain(skaleNetwork, chainName) }) - } else { - const chain = await switchNetwork(Number(chainId)) - if (!chain) { - throw new Error(`Failed to switch from ${currentChainId} to ${chainId} `) - } } + try { + // tmp fix for coinbase wallet + _networkSwitch(chainId, currentChainId, switchNetwork) + } catch (e) { + await sleep(DEFAULT_SLEEP) + _networkSwitch(chainId, currentChainId, switchNetwork) + } + await waitForNetworkChange(walletClient, currentChainId, chainId) log(`Network switched to ${chainId}...`) } return chainId From 8e089234ac5e92e01673946b4688b2a077f7f101 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 6 Nov 2023 16:05:33 +0000 Subject: [PATCH 109/110] metaport#206 Fix tokens unwrap --- src/core/actions/action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/actions/action.ts b/src/core/actions/action.ts index a3cef14..09723bb 100644 --- a/src/core/actions/action.ts +++ b/src/core/actions/action.ts @@ -162,7 +162,7 @@ export class Action { updateState(currentState: interfaces.ActionState, transactionHash?: string, timestamp?: number) { log(`actionStateUpd: ${this.constructor.name} - ${currentState} - ${this.token.keyname} \ - ${this.chainName1} -> ${this.chainName2}`) - const amountWei = toWei(this.amount, this.token.meta.decimals) + const amountWei = this.amount ? toWei(this.amount, this.token.meta.decimals) : 0n externalEvents.actionStateUpdated({ actionName: this.constructor.name, actionState: currentState, From 082770e461cb085641e047197d69737c789b3d73 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 6 Nov 2023 16:09:10 +0000 Subject: [PATCH 110/110] metaport#210 Disable broken wallets --- src/components/MetaportProvider.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/MetaportProvider.tsx b/src/components/MetaportProvider.tsx index 64b2a08..95bd694 100644 --- a/src/components/MetaportProvider.tsx +++ b/src/components/MetaportProvider.tsx @@ -31,9 +31,9 @@ import { PaletteMode } from '@mui/material' import { injectedWallet, - coinbaseWallet, - metaMaskWallet, - enkryptWallet + // coinbaseWallet, + metaMaskWallet + // enkryptWallet } from '@rainbow-me/rainbowkit/wallets' import { MetaportConfig, ActionStateUpdate } from '../core/interfaces' @@ -74,9 +74,9 @@ export default function MetaportProvider(props: { ) const wallets = [ - enkryptWallet({ chains }), - injectedWallet({ chains }), - coinbaseWallet({ chains, appName: 'SKALE Metaport' }) + // enkryptWallet({ chains }), + injectedWallet({ chains }) + // coinbaseWallet({ chains, appName: 'SKALE Metaport' }) ] if (props.config.projectId) {

hd?PXq}$L;b^22M;qUqhY1i!^~#T*7yl#jw`WCvf?fT3lD?c?jI>~ z(%eHNBO0`ya-kCmFUjW4WO8cd#GO2!VAVG#=rX(zwR=z?;dqX}bOfV_=2l!ySi5B~ z9eZqktalLB6;{p9cR2)W<~zX$5u9QQox!dRqRR;dCWZ%}e%$V`TdCyut6t>d2D@5J zpO_N9F7OYJ!jWDr()p|3A}MY%xp|p zcm^!^cI@sZWv#ak>)m0Z1=iza9RuUz75CF!q_q74tAHSFYs+H+)B-K-+ zO{jaPe|Q`=zxhsW-t6IN;fIv+lON@TCZ*$!2+9NKxh28b$%~c2q_k?B7jq)v=V5%y z;k;;AidO}8%O<4}5aT%LR>90hR-i1087pe*wjF2Z;O9Tfj9}Vk1OS}=I84tgCkO6! z^Ic{%!LxC#w0q2pL2S+E-$!A^0*|QP%H<-EdDUP(C zY@!M!xi}WV6dQu8X?UfWw|pMI3RK~J9$&ofi=V=r+ux<;6Q-8Z@g( zxHxNy875X_ZF|AYL6pMH@gbNw22}Dbm=0ayRtAA3ajKyVis;@NrV9kN;}-+Qz_h3= zJQu_@Ff9}=f?d9ZX==Q0=Sysp2hD&@1n&pyMqV({gl8V~n@wZ*ji#APXb|%g3a0r> zr9OeGc8KA;KKo^w-)uT+(R35m%p=DuFmo-EQ&;!QbohmgS0drgF#c8;zbjYwA>&{? z_WYV!6~dcnzB9f-N*$PE@0=NivA9gBKTI~{fW(KNf!WImc1oCOreqG~t6`FrQ z{V+RdR^oM-_8cXtcfN0i!ILpQn+Y>JNjK*cXK^bJJ~7@vs=z-|n-`{}a{TQ2PD?&x zHS-mrX9s&8|2b_=g_lvgKdhsl?DKklG0EFavg*UQyGiyWX$w3?vb&!=i|5!IgG{&m zBPOoY3HI4NFvXCX!6y0m5&!UPEzzUqrilr;Th4~*S^13MY3Dwa$;5e^sWGS^rla0<5!rjjgsL26Zz;5chE&-kBYUK>_s z;YlzKE<)iKq=I9`Z8_fzkq!ycC9s>k?EEzMD^gm@n!(VoTwp?-c}_S2vkpP{?7q-2 zv&{2g8dfmK8Xv;6ar6=KR31$)^!oQnbWeZW7zr05TJ(Y$7b5&7ta#jCIv+!Pi(2v4 z$z8t42|h^H3IbQJ#rE+wcr=?1lW()fxb5tPRqPx$`3YlW0zHm3-7<6KejTQfazALn zTc0KV;f3_ueu;S^GGm(n3+@ns72bg<;2SHPQC2YvT)#K0;^ER=2-6d{DcNVK86j;F zD;L22eCGNL_Gg>To-|t~aEdAupLD#h#+B4$5|*{|>qKgsNOtuRiFEs0*IJQd%-YVA6h zM!`*;$Cx=!`G=QLV=wBTvuUuhvK0zpdO1YA!a_@6L65R!_)AzP|HuYH^(y0OY#EN< zk1Zn(GUkh(His(qz|&f;wuf=>5d8*>PjvhhxM$3X7r0z_E$mvo#R`YkC<^F()*wq*h(ajJ@OuEfYx?Rb71wyE-VNh}7X(KEzb8MX(zy*iSIsLQL;% z*9A91diO|~-ajK{?pc^Iq1tnPv(*@`+jDknD9VLBSf605-;%Q9;FRyM!Oq5=s#MUA zmhYnHE20PE8w}HC4aVR;Q<;SmHnP0}dt6J3+Z^_Ij8sJ*?vF4#J7VPU7q~G5rS_5v zHg6D>BQKhlpWG)oH2#3`)gp&qyne|nYmMND+X0g`g3TDdYLiOoW7h;y_5@{rt}>?WhhfTN3;dCq2r≠zpY-PN;{e#_uNiuuK@IC!YIn*kG7B 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 Date: Mon, 23 Oct 2023 13:26:01 +0100 Subject: [PATCH 088/110] Downgrade pacakges --- bun.lockb | Bin 615986 -> 616289 bytes package.json | 6 +++--- vercel.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bun.lockb b/bun.lockb index 9ffe1e388df18582022fdbf8b8e7998d094c31f1..3aa6688c6d38462b5b75d4c97879dbf05590eea2 100755 GIT binary patch delta 67542 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%0DD+ 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 delta 69794 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 OjGxqGqnmxbi2nyuxZ!62 diff --git a/package.json b/package.json index 12633fb..f6d6ed4 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "eslint-plugin-n": "16.0.1", "eslint-plugin-promise": "6.1.1", "eslint-plugin-react": "7.33.1", - "eslint-plugin-storybook": "0.6.15", + "eslint-plugin-storybook": "0.6.13", "jsdom": "22.1.0", "json": "11.0.0", "lint-staged": "13.2.3", @@ -80,11 +80,11 @@ "@mui/icons-material": "^5.14.14", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@rainbow-me/rainbowkit": "^1.1.2", + "@rainbow-me/rainbowkit": "^1.1.1", "@skalenetwork/ima-js": "2.0.0-beta.0", "coingecko-api-v3": "^0.0.29", "react-jazzicon": "^1.0.4", - "viem": "^1.16.6", + "viem": "^1.10.8", "wagmi": "^1.4.1", "zustand": "^4.4.1" }, diff --git a/vercel.json b/vercel.json index 861702d..8a7b2a6 100644 --- a/vercel.json +++ b/vercel.json @@ -1,7 +1,7 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "yarn storybook build", - "devCommand": "yarn dev", + "buildCommand": "bun run build", + "devCommand": "bun dev", "installCommand": "bash prepare_meta.sh && bun install && bun build:lib", "framework": null, "outputDirectory": "./storybook-static" From d7e2294e2e9b7eb11628731165eb7590d9f7cff0 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 13:26:12 +0100 Subject: [PATCH 089/110] Downgrade pacakges --- bun.lockb | Bin 616289 -> 616289 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index 3aa6688c6d38462b5b75d4c97879dbf05590eea2..bd2af25fc35f6c0039f6b7938a79655aec9ae3fa 100755 GIT binary patch delta 195 zcmV;!06hQU&L!c_C6F#42ac=4Eu!S^FqkPooK?;g3$o8Ebm>LBBu0z+dy|6?fljqf z0XlS(gkLC=a9jw7xO4%BxO4)yxO4+mGeELuehDoWA6UVuK?&bE#!Il&;jDoZ-1?S8 z04r>(NlW$my1WHH?TC&-{<4JbxuU8dAEG-=VU%Icjz_NPTycj@r~`*hs06o7s0FNU x0W-Jy76u^#0Wp^x83twpFfKTUg&78ig&7C8g&7E;*a0(_&mjpHw?6U+BXz`QQ!D@g delta 195 zcmV;!06hQU&L!c_C6F#44BO_^V#p<{FP;B&-e#@)m`+DWd9#iUBybadiTYakfljqf z0XlS(#O^4Qa9jw7xO4%BxO4)yxO4+mGeDF9s#F3-p}spBxUSpMmtu?v;BLym$_WhV z1d5+7jwvn-yj1#yiPl{fFQImzGq^%5lofmJ4nZU#BMv`0KJSN3r~`*hs06o7s0FNU x0X4V!76u^#0Wy~y83twpHZC@Yg&78ig&7C8g&7E;*a0<{&mjpHw?6U+BX#%qP_Y02 From 2fe74cf37e7592385a5b1a9159a4bff46ed820ae Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 13:58:59 +0100 Subject: [PATCH 090/110] Downgrade all dependencies --- bun.lockb | Bin 616289 -> 616289 bytes package.json | 6 +++--- vercel.json | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bun.lockb b/bun.lockb index bd2af25fc35f6c0039f6b7938a79655aec9ae3fa..ae60204cbe823a8ce93cc41afac035e33d20188c 100755 GIT binary patch delta 54 zcmaEOPW9nA)rJF7M2#)Eo|o`m@F8kUyxuk0@Cd)l5E>qB-sxZFdDZX>tx@4 JtdnE*T>uR36662? delta 54 zcmaEOPW9nA)rJF7M2#)Eo|o`m<&y(Uyxuk0@Cd)l5E>qB-sxZFj}@B>tx@4 JtdnE*T>uuK6GQ+2 diff --git a/package.json b/package.json index f6d6ed4..775ddc6 100644 --- a/package.json +++ b/package.json @@ -75,9 +75,9 @@ "vitest": "0.34.1" }, "dependencies": { - "@mui/material": "^5.14.14", - "@mui/lab": "^5.0.0-alpha.149", - "@mui/icons-material": "^5.14.14", + "@mui/material": "^5.14.8", + "@mui/lab": "^5.0.0-alpha.143", + "@mui/icons-material": "^5.14.8", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@rainbow-me/rainbowkit": "^1.1.1", diff --git a/vercel.json b/vercel.json index 8a7b2a6..cab7b14 100644 --- a/vercel.json +++ b/vercel.json @@ -1,8 +1,8 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", - "buildCommand": "bun run build", - "devCommand": "bun dev", - "installCommand": "bash prepare_meta.sh && bun install && bun build:lib", + "buildCommand": "yarn build", + "devCommand": "yarn dev", + "installCommand": "bash prepare_meta.sh && yarn install && yarn build:lib", "framework": null, "outputDirectory": "./storybook-static" } \ No newline at end of file From 644fd2668e0ff5a63b6618c27768cfbbebd95d93 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 23 Oct 2023 16:41:09 +0100 Subject: [PATCH 091/110] metaport#192 handle small transfer amounts --- src/components/ErrorMessage.tsx | 32 +++++++++++------------ src/core/actions/action.ts | 8 ++---- src/core/actions/checks.ts | 12 ++++++++- src/core/actions/erc20.ts | 5 ++-- src/store/MetaportStore.ts | 45 ++++++++++++++++++++++----------- 5 files changed, 62 insertions(+), 40 deletions(-) diff --git a/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx index 88b4ac7..0912fed 100644 --- a/src/components/ErrorMessage.tsx +++ b/src/components/ErrorMessage.tsx @@ -54,24 +54,24 @@ export default function Error(props: { errorMessage: ErrorMessage }) {