From e1a4532baa273766f42c8eeb50a3258bdc5d140f Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Wed, 21 Jun 2023 11:46:34 +0100
Subject: [PATCH] [release] Interlay 2.34.0 (#1312)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: redirect when access from forbidden country is detected (#1209)
* Feature/updated transfer UI (#876)
* refactor: use updated tab component
* refactor: duplicated form titles
* refactor: remove redundant hook calls
* refactor: prefer title case
* wip: XCM transfer form UI
* wip: updated form UI
* wip: account selector placeholder component
* wip: account selector modal
* wip: modal open and close actions
* wip: update modal type
* wip: get accounts
* wip: add identicon and rename component for consistency
* wip: account input component
* fix: remove redundant icons prop
* feat: implement with SelectTrigger
* wip: styling and account selection value
* wip: handle setting account data
* refactor: better naming
* wip: address list styling
* refactor: rename defaultAccount
* wip: chain selector placeholder component
* wip: duplicate account component and rename
* chore: delete redundant legacy component
* wip: logic for fetching and rendering chain ids
* wip: chain item styling
* wip: selected chain styling
* chore: add comment
* refactor: pass through native token to icon component
* feature: add chain icon component
* chore: add comment
* chore: correct file name casing
* refactor: improve folder structure
* wip: form layout styling
* chore: add arrow icon
* chore: add logos and correct svg titles
* chore: remove redundant svg prop
* chore: rename arrow icon
* chore: consistent use of styled components
* refactor: remove padding from modal body
* wip: formik integration work
* wip: extend useXCMBridge to return available chains and utility methods
* chore: move Chain and Chains types to types directory
* feat: layout and form implementation
* feat: add schema
* feat: final
* wip: refactor useXCMBridge hook
* refactor: add endpoints type
* refactor: wrap methods in useCallback
* refactor: fix bug in hook method
* chore: bump bridge version
* wip: set originating and destination chain values
* refactor: set from chain value on field change
* wip: set originating chain value
* refactor: mergeProps to set field value
* refactor: handle setting origin/destination chain values
* wip: get tokens method
* wip: first iteration of balances function
* wip: handle tokens array
* wip: set token value
* wip: get token balances
* wip: return token and balances in single method
* wip: mapped tokens
* refactor: handle default chain values
* refactor: better organised function order
* wip: handle change events
* wip: handle setting tokens
* wip: handle fetching tokens and balances
* wip: convert input configs
* wip: handle token change
* wip: get token USD price
* Trigger Build
* chore: remove unused import
* chore: correct eslintignore syntax
* wip: handle breaking changes
* wip: disable token input when select items value is 1
* chore: set first token item as variable
* wip: handle setting and changing values
* chire: add loading spinner
* refactor: add loading state
* refactor: filter destination chains
* chore: remove console log
* chore: bump XCM bridge version
* chore: update config
* refactor: configure validation
* chore: revert change to useForm hook
* wip: form validation
* wip: working form validation
* wip: undefined validation parameters
* refactor: return dest fee estimate from bridge hook
* feature: show fees and fee estimates
* chore: conditional operators
* refactor: handle ticker change correctly
* wip: sendTransaction method
* Revert "wip: sendTransaction method"
This reverts commit 3ade26dda26c7cc14f9db9e7c005b66863fa9139.
* fix: USD amounts
* wip: send transactions
* refactor: bump bridge and use getNativeToken method
* chore: bump bridge
* refactor: move submit logic to useMutation hook
* fix: type mismatches
* refactor: white space/comments
* refactor: add transaction fee validation
* chore: typo
* chore: remove console log
* refactor: remove duplicated monetary conversion
* refactor: remove duplicate code
* Revert "refactor: remove duplicate code"
This reverts commit bd29f8c5661e327c5285d1020c534dab2deae806.
* Revert "refactor: remove duplicated monetary conversion"
This reverts commit 5fd3d645eb7d8edc00cfe8ced186d4e2432af9fc.
* refactor: use monetaryAmount when constructing transaction
* refactor: remove duplicated code for fetching tokens
* refactor: default XCM origin
* Revert "refactor: remove duplicated code for fetching tokens"
This reverts commit 8f31ee8667adcd49f5aaebb7db2f205afb5e9725.
* chore: remove comment
* chore: fix errors
* fix: set default value to empty string to prevent React error
* refactor: removed unwanted force validation parameters
* refactor: remove redundant method
* refactor: add method return type
* refactor: add method return type
* refactor: correct type error
* refactor: fix destFee type error
* refactor: remove fees validation and revert destFee return value
* chore: remove console log
* refactor: remove redundant method
* refactor: disable validation on change
* chore: remove commented out code
* wip: use select component for chain selector
* fix: handle chain select functions
* refactor: type chain id as ChainName
* Revert "refactor: type chain id as ChainName"
This reverts commit d05e0128cb4b5ac1d00ac07808ebdf9858739165.
* chore: remove unused component files
* refactor: remove duplicated transaction logic
* fix: make to/from field types more specific
* fix: revert yup.custom changes and cast validation
* fix: set correct destination chain
* refator: handle token data
* refactor: add use callback
* fix: correct rendering logic
* fix: update dependencies
* chore: delete unused styles
* chore: fix merge issue with transfer form
* fix: change validation handling
* Revert "fix: change validation handling"
This reverts commit c0cb3062aad3540b2afad7d375024d872924a62c.
* refactor: only display transfer amount if amount has been entered
* chore: config changes
* chore: add missing icons
* chore: Hydra chain icon
* fix: add error text to CTA
* Tom/xcm fixes (#1213)
* refactor: specify endpoints and remove unnecessary logic
* fix: save file before committing
* fix: disable refetch
* chore: update endpoints
* chore: remove log
* chore: rename file
* chore: add additional acala/karura endpoints
---------
Co-authored-by: Rui Simão
* chore: release v2.32.0
* Update API healthchecks (#778)
* Chore - add vault healthcheck
* Chore - add vault healthcheck
* Chore - add vault healthcheck
* [earn strategies] placeholder page, nav and feature flag (#1216)
* chore: bump icons dependency
* feature: earn strategies placeholder page and feature flag
* feat: add useTransaction (#1189)
* chore: update monetary to latest 0.7.3 (#1214)
* chore: update monetary to latest 0.7.3
* chore: update lib
* chore: bump lib and bridge (#1219)
* chore: release v2.32.1
* fix: add missing icons and remove erroring RPC (#1222)
* fix: add missing icons and remove erroring RPC
* Update src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Acala.tsx
Co-authored-by: Peter Slaný <47864599+peterslany@users.noreply.github.com>
* Update src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Astar.tsx
Co-authored-by: Peter Slaný <47864599+peterslany@users.noreply.github.com>
* Update src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Parallel.tsx
Co-authored-by: Peter Slaný <47864599+peterslany@users.noreply.github.com>
---------
Co-authored-by: Peter Slaný <47864599+peterslany@users.noreply.github.com>
* chore: release v2.32.2
* fix: compare input configs with method not operator (#1225)
* refactor: reset selected account on account change (#1226)
* chore: release v2.32.3
* feature: add geoblock feature flag (#1230)
* chore: release v2.32.4
* chore: bump bridge (#1233)
* chore: release v2.32.5
* Peter/earn strategies feat deposit withdraw form (#1229)
* chore: update monetary to latest 0.7.3
* wip
* feat(earn-strategies): add deposit and withdrawal form components
* refactor: add padding under tabs in earn strategy forms
* chore(earn-strategies): change file structure
* feat: add Popover, Underlay and ProgressBar. Changes to Dialog, Modal and Overlay. (#1236)
* fix: Dialog, Modal and Popover (#1245)
* chore: rename strategies feature (#1247)
* chore: release v2.32.6
* Fix: back button behaviour from bridge page (#1246)
* fix: use history replace instead of push to fix looping of bridge page
* chore: clean up and bump version
---------
Co-authored-by: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
* feat: add transaction notifications (#1177)
* chore: remove console.log (#1262)
* fix(TokenInput): adorment ticker (#1257)
* fix: get vesting data (#1264)
* Peter/chore update lib 2.3.0 (#1267)
* chore: update monetary to latest 0.7.3
* chore: update lib version
* fix: sort notifications (#1270)
* fix: transaction none (#1271)
* fix(Loans): apy label (#1275)
* Peter/loans fix subsidy rewards (#1276)
* chore: update monetary to latest 0.7.3
* fix(loans): display correct subsidy rewards accrued amount and APY
* chore: console log cleanup
* chore: replace GOVERNANCE_TOKEN_SYMBOL with GOVERNANCE_TOKEN.ticker
* Peter/fix loans incentive apr computation (#1256)
* chore: update monetary to latest 0.7.3
* fix: convert incentives apr computation to percentage
* fix: change loans incentives annualized return to have label APR
* chore: release v2.33.0
* Peter/chore update lib 2.3.3 (#1282)
* chore: update monetary to latest 0.7.3
* chore: update lib to 2.3.3.
* fix: enable faucet on Interlay testnet (#1289)
* fix: enable faucet on Interlay testnet
* fix: prefer governance token ticker to symbol
* chore: bump bridge (#1285)
* fix(Swap): update trade object on each block (#1297)
* api: use diadata as main datasource (#1277)
* api: use diadata as main datasource
* api: add header to select price source
---------
Co-authored-by: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
* Peter/fix interlay issues (#1300)
* chore: update monetary to latest 0.7.3
* fix: add missing translation and fix lend APY display
* refactor: bring back formatting with 0 amount case covered
* refactor: code review
* refactor: code review
* api: select price source via query param and ticker renaming (#1307)
* api: fix tether label for dia (#1309)
* chore: release v2.34.0
* chore: fix merge conflict
* Trigger build
---------
Co-authored-by: Peter Slaný <47864599+peterslany@users.noreply.github.com>
Co-authored-by: Rui Simão
Co-authored-by: ns212 <73105077+ns212@users.noreply.github.com>
Co-authored-by: Chanakya Kilaru
---
api/market_data.py | 66 +++-
package.json | 8 +-
src/App.tsx | 3 +-
src/assets/icons/CheckCircle.tsx | 25 ++
src/assets/icons/ListBullet.tsx | 25 ++
src/assets/icons/XCircle.tsx | 25 ++
src/assets/icons/index.ts | 3 +
src/assets/locales/en/translation.json | 76 ++++
src/common/actions/general.actions.ts | 20 +-
src/common/reducers/general.reducer.ts | 29 +-
src/common/types/actions.types.ts | 21 +-
src/common/types/util.types.ts | 22 ++
.../TokenInput/TokenInput.tsx | 2 +-
.../LoanApyTooltip/BreakdownGroup.tsx | 2 +-
.../LoanApyTooltip/LoanApyTooltip.tsx | 9 +-
src/components/LoanPositionsTable/ApyCell.tsx | 8 +-
.../LoanPositionsTable/LoanPositionsTable.tsx | 8 +-
.../NotificationsList.tsx | 35 ++
.../NotificationsListItem.tsx | 42 ++
.../NotificationsPopover.styles.tsx | 18 +
.../NotificationsPopover.tsx | 55 +++
src/components/NotificationsPopover/index.tsx | 2 +
.../ToastContainer/ToastContainer.styles.tsx | 36 ++
.../ToastContainer/ToastContainer.tsx | 5 +
src/components/ToastContainer/index.tsx | 2 +
.../TransactionModal.style.tsx | 21 +
.../TransactionModal/TransactionModal.tsx | 112 ++++++
src/components/TransactionModal/index.tsx | 1 +
.../TransactionToast.styles.tsx | 13 +
.../TransactionToast/TransactionToast.tsx | 132 +++++++
src/components/TransactionToast/index.tsx | 2 +
src/components/index.tsx | 6 +
src/index.tsx | 9 +-
src/legacy-components/ErrorModal/index.tsx | 34 --
.../ConfirmedIssueRequest/index.tsx | 33 --
.../ManualIssueExecutionUI/index.tsx | 16 +-
src/legacy-components/IssueUI/index.tsx | 5 +-
.../RedeemUI/ReimburseStatusUI/index.tsx | 70 ++--
src/legacy-components/RedeemUI/index.tsx | 5 +-
.../index.tsx | 3 +-
.../components/DepositForm/DepositForm.tsx | 24 +-
.../DepositForm/DepositOutputAssets.tsx | 2 +-
.../Pools/components/PoolModal/PoolModal.tsx | 9 +-
.../PoolsInsights/PoolsInsights.tsx | 8 +-
.../components/WithdrawForm/WithdrawForm.tsx | 21 +-
.../AMM/Swap/components/SwapForm/SwapCTA.tsx | 8 +-
.../AMM/Swap/components/SwapForm/SwapForm.tsx | 58 ++-
src/pages/Bridge/BurnForm/index.tsx | 51 +--
.../SubmittedIssueRequestModal/index.tsx | 42 +-
src/pages/Bridge/IssueForm/index.tsx | 80 ++--
.../SubmittedRedeemRequestModal/index.tsx | 16 +-
src/pages/Bridge/RedeemForm/index.tsx | 18 +-
.../BorrowAssetsTable/BorrowAssetsTable.tsx | 6 +-
.../CollateralModal/CollateralModal.tsx | 49 +--
.../LendAssetsTable/LendAssetsTable.tsx | 6 +-
.../LoanActionInfo/RewardsGroup.tsx | 2 +-
.../components/LoanForm/LoanForm.tsx | 58 ++-
.../LoansInsights/LoansInsights.tsx | 18 +-
.../Staking/ClaimRewardsButton/index.tsx | 29 +-
src/pages/Staking/WithdrawButton/index.tsx | 39 +-
src/pages/Staking/index.tsx | 12 -
.../IssueRequestModal/index.tsx | 23 +-
.../RedeemRequestModal/index.tsx | 23 +-
.../CrossChainTransferForm.tsx | 62 +--
src/pages/Transfer/TransferForm/index.tsx | 53 +--
.../Vaults/Vault/RequestIssueModal/index.tsx | 68 ++--
.../Vaults/Vault/RequestRedeemModal/index.tsx | 26 +-
.../Vault/RequestReplacementModal/index.tsx | 23 +-
.../Vault/UpdateCollateralModal/index.tsx | 23 +-
.../CollateralForm/CollateralForm.styles.tsx | 44 ---
.../CollateralForm/CollateralForm.tsx | 312 ---------------
.../Vault/components/CollateralForm/index.tsx | 2 -
.../Vault/components/Rewards/Rewards.tsx | 11 -
src/pages/Vaults/Vault/components/index.tsx | 4 +-
.../DespositCollateralStep.tsx | 12 +-
.../AvailableAssetsTable/ActionsCell.tsx | 22 +-
src/parts/Topbar/index.tsx | 10 +-
src/utils/constants/links.ts | 21 +-
src/utils/context/Notifications.tsx | 141 +++++++
src/utils/helpers/loans.ts | 5 +-
.../use-get-account-lending-statistics.tsx | 1 +
src/utils/hooks/api/use-get-vesting-data.tsx | 2 +-
.../transaction/extrinsics/extrinsics.ts | 46 +++
.../hooks/transaction/extrinsics/index.ts | 1 +
.../{utils/extrinsic.ts => extrinsics/lib.ts} | 44 +--
src/utils/hooks/transaction/extrinsics/xcm.ts | 27 ++
src/utils/hooks/transaction/types/index.ts | 29 +-
src/utils/hooks/transaction/types/vesting.ts | 13 +
src/utils/hooks/transaction/types/xcm.ts | 21 +
.../use-transaction-notifications.tsx | 107 ++++++
.../hooks/transaction/use-transaction.ts | 95 +++--
.../hooks/transaction/utils/description.ts | 363 ++++++++++++++++++
src/utils/hooks/transaction/utils/submit.ts | 37 +-
src/utils/hooks/use-copy-tooltip.tsx | 1 +
src/utils/hooks/use-countdown.ts | 67 ++++
src/utils/hooks/use-sign-message.ts | 1 +
.../hooks/use-update-query-parameters.ts | 2 +-
src/utils/hooks/use-window-focus.ts | 26 ++
yarn.lock | 94 +++--
99 files changed, 2154 insertions(+), 1273 deletions(-)
create mode 100644 src/assets/icons/CheckCircle.tsx
create mode 100644 src/assets/icons/ListBullet.tsx
create mode 100644 src/assets/icons/XCircle.tsx
create mode 100644 src/components/NotificationsPopover/NotificationsList.tsx
create mode 100644 src/components/NotificationsPopover/NotificationsListItem.tsx
create mode 100644 src/components/NotificationsPopover/NotificationsPopover.styles.tsx
create mode 100644 src/components/NotificationsPopover/NotificationsPopover.tsx
create mode 100644 src/components/NotificationsPopover/index.tsx
create mode 100644 src/components/ToastContainer/ToastContainer.styles.tsx
create mode 100644 src/components/ToastContainer/ToastContainer.tsx
create mode 100644 src/components/ToastContainer/index.tsx
create mode 100644 src/components/TransactionModal/TransactionModal.style.tsx
create mode 100644 src/components/TransactionModal/TransactionModal.tsx
create mode 100644 src/components/TransactionModal/index.tsx
create mode 100644 src/components/TransactionToast/TransactionToast.styles.tsx
create mode 100644 src/components/TransactionToast/TransactionToast.tsx
create mode 100644 src/components/TransactionToast/index.tsx
delete mode 100644 src/legacy-components/ErrorModal/index.tsx
delete mode 100644 src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.styles.tsx
delete mode 100644 src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.tsx
delete mode 100644 src/pages/Vaults/Vault/components/CollateralForm/index.tsx
create mode 100644 src/utils/context/Notifications.tsx
create mode 100644 src/utils/hooks/transaction/extrinsics/extrinsics.ts
create mode 100644 src/utils/hooks/transaction/extrinsics/index.ts
rename src/utils/hooks/transaction/{utils/extrinsic.ts => extrinsics/lib.ts} (70%)
create mode 100644 src/utils/hooks/transaction/extrinsics/xcm.ts
create mode 100644 src/utils/hooks/transaction/types/vesting.ts
create mode 100644 src/utils/hooks/transaction/types/xcm.ts
create mode 100644 src/utils/hooks/transaction/use-transaction-notifications.tsx
create mode 100644 src/utils/hooks/transaction/utils/description.ts
create mode 100644 src/utils/hooks/use-countdown.ts
create mode 100644 src/utils/hooks/use-window-focus.ts
diff --git a/api/market_data.py b/api/market_data.py
index f63b4b7cd6..82fd2d076f 100644
--- a/api/market_data.py
+++ b/api/market_data.py
@@ -8,6 +8,9 @@
api_key = os.environ.get("CG_API_KEY")
+tickers = {
+ "Tether USD": "tether",
+}
@app.after_request
def add_header(response):
@@ -15,11 +18,7 @@ def add_header(response):
response.cache_control.s_maxage = 300
return response
-
-@app.route("/marketdata/price", methods=["GET"])
-def get_price():
- args = request.args
-
+def coingecko(args):
headers_dict = {
"content-type": "application/json",
"accept": "application/json",
@@ -28,6 +27,63 @@ def get_price():
url = "https://api.coingecko.com/api/v3/simple/price"
resp = requests.get(url, params=args, headers=headers_dict)
data = resp.json()
+ return data
+
+def dia(asset):
+ headers_dict = {
+ "content-type": "application/json",
+ "accept": "application/json",
+ "x-cg-pro-api-key": api_key,
+ }
+ url = "https://api.diadata.org/v1/assetQuotation"
+ if asset == "bitcoin":
+ url += "/Bitcoin/0x0000000000000000000000000000000000000000"
+ elif asset == "interlay":
+ url += "/Interlay/0x0000000000000000000000000000000000000000"
+ elif asset == "liquid-staking-dot":
+ return { "liquid-staking-dot": None }
+ elif asset == "polkadot":
+ url += "/Polkadot/0x0000000000000000000000000000000000000000/"
+ elif asset == "tether":
+ url += "/Ethereum/0xdAC17F958D2ee523a2206206994597C13D831ec7"
+
+ resp = requests.get(url, headers=headers_dict)
+ data = resp.json()
+
+ # optionally rename the ticker
+ ticker = tickers.get(data["Name"], data["Name"]).lower()
+
+ return {
+ ticker: {
+ "usd": data["Price"],
+ }
+ }
+
+
+@app.route("/marketdata/price", methods=["GET"])
+def get_price():
+ args = request.args
+
+ price_source = args.get('price-source')
+
+ data = {}
+
+ def _dia():
+ ticker_ids = args["ids"].split(",")
+ for ticker_id in ticker_ids:
+ data.update(dia(ticker_id))
+
+ if price_source == "dia":
+ _dia()
+ elif price_source == "coingecko":
+ data = coingecko(args)
+ else:
+ try:
+ _dia()
+ except Exception as e:
+ print("Error", e)
+ data = coingecko(args)
+
return jsonify(data)
diff --git a/package.json b/package.json
index 581d654255..138d888dde 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,13 @@
{
"name": "interbtc-ui",
- "version": "2.32.6",
+ "version": "2.34.0",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
"@headlessui/react": "^1.1.1",
"@heroicons/react": "^2.0.18",
- "@interlay/bridge": "^0.3.11",
- "@interlay/interbtc-api": "2.2.4",
+ "@interlay/bridge": "^0.3.13",
+ "@interlay/interbtc-api": "2.3.3",
"@interlay/monetary-js": "0.7.3",
"@polkadot/api": "9.14.2",
"@polkadot/extension-dapp": "0.44.1",
@@ -69,7 +69,7 @@
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"react-table": "^7.6.3",
- "react-toastify": "^6.0.5",
+ "react-toastify": "^9.1.2",
"react-transition-group": "^4.4.5",
"react-use": "^17.2.3",
"redux": "^4.0.5",
diff --git a/src/App.tsx b/src/App.tsx
index a94a8a4eb2..1722f65d75 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,3 @@
-import 'react-toastify/dist/ReactToastify.css';
import './i18n';
import { FaucetClient, SecurityStatusCode } from '@interlay/interbtc-api';
@@ -21,6 +20,7 @@ import vaultsByAccountIdQuery from '@/services/queries/vaults-by-accountId-query
import { BitcoinNetwork } from '@/types/bitcoin';
import { PAGES } from '@/utils/constants/links';
+import { TransactionModal } from './components/TransactionModal';
import * as constants from './constants';
import TestnetBanner from './legacy-components/TestnetBanner';
import { FeatureFlags, useFeatureFlag } from './utils/hooks/use-feature-flag';
@@ -234,6 +234,7 @@ const App = (): JSX.Element => {
)}
/>
+
>
);
};
diff --git a/src/assets/icons/CheckCircle.tsx b/src/assets/icons/CheckCircle.tsx
new file mode 100644
index 0000000000..2bcca13aed
--- /dev/null
+++ b/src/assets/icons/CheckCircle.tsx
@@ -0,0 +1,25 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const CheckCircle = forwardRef((props, ref) => (
+
+
+
+));
+
+CheckCircle.displayName = 'CheckCircle';
+
+export { CheckCircle };
diff --git a/src/assets/icons/ListBullet.tsx b/src/assets/icons/ListBullet.tsx
new file mode 100644
index 0000000000..21eb5ba490
--- /dev/null
+++ b/src/assets/icons/ListBullet.tsx
@@ -0,0 +1,25 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const ListBullet = forwardRef((props, ref) => (
+
+
+
+));
+
+ListBullet.displayName = 'ListBullet';
+
+export { ListBullet };
diff --git a/src/assets/icons/XCircle.tsx b/src/assets/icons/XCircle.tsx
new file mode 100644
index 0000000000..c1b84d58cd
--- /dev/null
+++ b/src/assets/icons/XCircle.tsx
@@ -0,0 +1,25 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const XCircle = forwardRef((props, ref) => (
+
+
+
+));
+
+XCircle.displayName = 'XCircle';
+
+export { XCircle };
diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts
index bf537097cc..2508fb9299 100644
--- a/src/assets/icons/index.ts
+++ b/src/assets/icons/index.ts
@@ -2,11 +2,14 @@ export { ArrowRight } from './ArrowRight';
export { ArrowRightCircle } from './ArrowRightCircle';
export { ArrowsUpDown } from './ArrowsUpDown';
export { ArrowTopRightOnSquare } from './ArrowTopRightOnSquare';
+export { CheckCircle } from './CheckCircle';
export { ChevronDown } from './ChevronDown';
export { Cog } from './Cog';
export { DocumentDuplicate } from './DocumentDuplicate';
export { InformationCircle } from './InformationCircle';
+export { ListBullet } from './ListBullet';
export { PencilSquare } from './PencilSquare';
export { PlusCircle } from './PlusCircle';
export { Warning } from './Warning';
+export { XCircle } from './XCircle';
export { XMark } from './XMark';
diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json
index 9e03c16050..e6cd47edf2 100644
--- a/src/assets/locales/en/translation.json
+++ b/src/assets/locales/en/translation.json
@@ -156,6 +156,7 @@
"staked": "Staked",
"sign_t&cs": "Sign T&Cs",
"receivable_assets": "Receivable Assets",
+ "dismiss": "Dismiss",
"redeem_page": {
"maximum_in_single_request": "Max redeemable in single request",
"redeem": "Redeem",
@@ -636,5 +637,80 @@
"strategy": {
"withdraw_rewards_in_wrapped": "Withdraw rewards in {{wrappedCurrencySymbol}}:",
"update_position": "Update position"
+ },
+ "transaction": {
+ "recent_transactions": "Recent transactions",
+ "no_recent_transactions": "No recent transactions",
+ "confirm_transaction_wallet": "Confirm this transaction in your wallet",
+ "confirm_transaction": "Confirm transaction",
+ "transaction_processing": "Transaction processing",
+ "transaction_failed": "Transaction failed",
+ "transaction_successful": "Transaction successful",
+ "swapping_to": "Swapping {{fromAmount}} {{fromCurrency}} to {{toAmount}} {{toCurrency}}",
+ "swapped_to": "Swapped {{fromAmount}} {{fromCurrency}} to {{toAmount}} {{toCurrency}}",
+ "adding_liquidity_to_pool": "Adding liquidity to {{poolName}} Pool",
+ "added_liquidity_to_pool": "Added liquidity to {{poolName}} Pool",
+ "removing_liquidity_from_pool": "Removing liquidity from {{poolName}} Pool",
+ "removed_liquidity_from_pool": "Removed liquidity from {{poolName}} Pool",
+ "claiming_pool_rewards": "Claiming pools rewards",
+ "claimed_pool_rewards": "Claimed pools rewards",
+ "issuing_amount": "Issuing {{amount}} {{currency}}",
+ "issued_amount": "Issuing {{amount}} {{currency}}",
+ "redeeming_amount": "Redeeming {{amount}} {{currency}}",
+ "redeemed_amount": "Redeemed {{amount}} {{currency}}",
+ "burning_amount": "Burning {{amount}} {{currency}}",
+ "burned_amount": "Burned {{amount}} {{currency}}",
+ "retrying_redeem_id": "Retrying redeem {{resquestId}}",
+ "retried_redeem_id": "Retried redeem {{resquestId}}",
+ "reimbursing_redeem_id": "Reimbursing redeem {{resquestId}}",
+ "reimbersed_redeem_id": "Reimbursed redeem {{resquestId}}",
+ "executing_issue": "Executing issue",
+ "executed_issue": "Executed issue",
+ "transfering_amount_to_address": "Transfering {{amount}} {{currency}} to {{address}}",
+ "transfered_amount_to_address": "Transfered {{amount}} {{currency}} to {{address}}",
+ "transfering_amount_from_chain_to_chain": "Transfering {{amount}} {{currency}} from {{fromChain}} to {{toChain}}",
+ "transfered_amount_from_chain_to_chain": "Transfered {{amount}} {{currency}} from {{fromChain}} to {{toChain}}",
+ "claiming_lending_rewards": "Claiming lending rewards",
+ "claimed_lending_rewards": "Claimed lending rewards",
+ "borrowing_amount": "Borrowing {{amount}} {{currency}}",
+ "borrowed_amount": "Borrowed {{amount}} {{currency}}",
+ "lending_amount": "Lending {{amount}} {{currency}}",
+ "lent_amount": "Lent {{amount}} {{currency}}",
+ "repaying_amount": "Repaying {{amount}} {{currency}}",
+ "repaid_amount": "Repaid {{amount}} {{currency}}",
+ "repaying": "Repaying {{currency}}",
+ "repaid": "Repaid {{currency}}",
+ "withdrawing_amount": "Withdrawing {{amount}} {{currency}}",
+ "withdrew_amount": "Withdrew {{amount}} {{currency}}",
+ "withdrawing": "Withdrawing {{currency}}",
+ "withdrew": "Withdrew {{currency}}",
+ "disabling_loan_as_collateral": "Disabling {{currency}} as collateral",
+ "disabled_loan_as_collateral": "Disabled {{currency}} as collateral",
+ "enabling_loan_as_collateral": "Enabling {{currency}} as collateral",
+ "enabled_loan_as_collateral": "Enabled {{currency}} as collateral",
+ "creating_currency_vault": "Creating {{currency}} vault",
+ "created_currency_vault": "Created {{currency}} vault",
+ "depositing_amount_to_vault": "Depositing {{amount}} {{currency}} to vault",
+ "deposited_amount_to_vault": "Deposited {{amount}} {{currency}} to vault",
+ "withdrawing_amount_from_vault": "Withdrawing {{amount}} {{currency}} from vault",
+ "withdrew_amount_from_vault": "Withdrew {{amount}} {{currency}} from vault",
+ "claiming_vault_rewards": "Claiming vault rewards",
+ "claimed_vault_rewards": "Claimed vault rewards",
+ "staking_amount": "Staking {{amount}} {{currency}}",
+ "staked_amount": "Staking {{amount}} {{currency}}",
+ "adding_amount_to_staked_amount": "Adding {{amount}} {{currency}} to staked amount",
+ "added_amount_to_staked_amount": "Added {{amount}} {{currency}} to staked amount",
+ "increasing_stake_lock_time": "Increasing stake lock time",
+ "increased_stake_lock_time": "Increased stake lock time",
+ "withdrawing_stake": "Withdrawing stake",
+ "withdrew_stake": "Withdrew stake",
+ "claiming_staking_rewards": "Claiming staking rewards",
+ "claimed_staking_rewards": "Claimed staking rewards",
+ "increasing_stake_locked_time_amount": "Increasing stake locked time and amount",
+ "increased_stake_locked_time_amount": "Increased stake locked time and amount",
+ "requesting_vault_replacement": "Requesting vault replacement",
+ "requested_vault_replacement": "Requested vault replacement",
+ "claiming_vesting": "Claiming vesting",
+ "claimed_vesting": "Claimed vesting"
}
}
diff --git a/src/common/actions/general.actions.ts b/src/common/actions/general.actions.ts
index 8bfaa85c76..880e1da1ce 100644
--- a/src/common/actions/general.actions.ts
+++ b/src/common/actions/general.actions.ts
@@ -4,6 +4,8 @@ import { BitcoinAmount, MonetaryAmount } from '@interlay/monetary-js';
import { GovernanceTokenMonetaryAmount } from '@/config/relay-chains';
import {
+ ADD_NOTIFICATION,
+ AddNotification,
INIT_GENERAL_DATA_ACTION,
InitGeneralDataAction,
IS_BRIDGE_LOADED,
@@ -20,10 +22,12 @@ import {
ShowSignTermsModal,
UPDATE_HEIGHTS,
UPDATE_TOTALS,
+ UPDATE_TRANSACTION_MODAL_STATUS,
UpdateHeights,
- UpdateTotals
+ UpdateTotals,
+ UpdateTransactionModal
} from '../types/actions.types';
-import { ParachainStatus } from '../types/util.types';
+import { Notification, ParachainStatus, TransactionModalData } from '../types/util.types';
export const isBridgeLoaded = (isLoaded = false): IsBridgeLoaded => ({
type: IS_BRIDGE_LOADED,
@@ -86,3 +90,15 @@ export const updateTotalsAction = (
totalLockedCollateralTokenAmount,
totalWrappedTokenAmount
});
+
+export const addNotification = (accountAddress: string, notification: Notification): AddNotification => ({
+ type: ADD_NOTIFICATION,
+ accountAddress,
+ notification
+});
+
+export const updateTransactionModal = (isOpen: boolean, data: TransactionModalData): UpdateTransactionModal => ({
+ type: UPDATE_TRANSACTION_MODAL_STATUS,
+ isOpen,
+ data
+});
diff --git a/src/common/reducers/general.reducer.ts b/src/common/reducers/general.reducer.ts
index cc89bc33e7..23093c810a 100644
--- a/src/common/reducers/general.reducer.ts
+++ b/src/common/reducers/general.reducer.ts
@@ -2,8 +2,10 @@ import { newMonetaryAmount } from '@interlay/interbtc-api';
import { BitcoinAmount } from '@interlay/monetary-js';
import { RELAY_CHAIN_NATIVE_TOKEN } from '@/config/relay-chains';
+import { TransactionStatus } from '@/utils/hooks/transaction/types';
import {
+ ADD_NOTIFICATION,
GeneralActions,
INIT_GENERAL_DATA_ACTION,
IS_BRIDGE_LOADED,
@@ -12,7 +14,8 @@ import {
SHOW_BUY_MODAL,
SHOW_SIGN_TERMS_MODAL,
UPDATE_HEIGHTS,
- UPDATE_TOTALS
+ UPDATE_TOTALS,
+ UPDATE_TRANSACTION_MODAL_STATUS
} from '../types/actions.types';
import { GeneralState, ParachainStatus } from '../types/util.types';
@@ -33,6 +36,11 @@ const initialState = {
relayChainNativeToken: { usd: 0 },
governanceToken: { usd: 0 },
wrappedToken: { usd: 0 }
+ },
+ notifications: {},
+ transactionModal: {
+ isOpen: false,
+ data: { variant: TransactionStatus.CONFIRM }
}
};
@@ -65,6 +73,25 @@ export const generalReducer = (state: GeneralState = initialState, action: Gener
return { ...state, isBuyModalOpen: action.isBuyModalOpen };
case SHOW_SIGN_TERMS_MODAL:
return { ...state, isSignTermsModalOpen: action.isSignTermsModalOpen };
+ case ADD_NOTIFICATION: {
+ const newAccountNotifications = [...(state.notifications[action.accountAddress] || []), action.notification];
+
+ return {
+ ...state,
+ notifications: {
+ ...state.notifications,
+ [action.accountAddress]: newAccountNotifications
+ }
+ };
+ }
+ case UPDATE_TRANSACTION_MODAL_STATUS:
+ return {
+ ...state,
+ transactionModal: {
+ ...state.transactionModal,
+ ...action
+ }
+ };
default:
return state;
}
diff --git a/src/common/types/actions.types.ts b/src/common/types/actions.types.ts
index 4ebf3cb5df..f4744b03ac 100644
--- a/src/common/types/actions.types.ts
+++ b/src/common/types/actions.types.ts
@@ -3,7 +3,7 @@ import { BitcoinAmount, MonetaryAmount } from '@interlay/monetary-js';
import { GovernanceTokenMonetaryAmount } from '@/config/relay-chains';
-import { ParachainStatus, StoreType } from './util.types';
+import { Notification, ParachainStatus, StoreType, TransactionModalData } from './util.types';
// GENERAL ACTIONS
export const IS_BRIDGE_LOADED = 'IS_BRIDGE_LOADED';
@@ -20,6 +20,9 @@ export const SHOW_SIGN_TERMS_MODAL = 'SHOW_SIGN_TERMS_MODAL';
export const UPDATE_HEIGHTS = 'UPDATE_HEIGHTS';
export const UPDATE_TOTALS = 'UPDATE_TOTALS';
export const SHOW_BUY_MODAL = 'SHOW_BUY_MODAL';
+export const ADD_NOTIFICATION = 'ADD_NOTIFICATION';
+export const SHOW_TRANSACTION_MODAL = 'SHOW_TRANSACTION_MODAL';
+export const UPDATE_TRANSACTION_MODAL_STATUS = 'UPDATE_TRANSACTION_MODAL_STATUS';
export interface UpdateTotals {
type: typeof UPDATE_TOTALS;
@@ -98,6 +101,18 @@ export interface ShowBuyModal {
isBuyModalOpen: boolean;
}
+export interface AddNotification {
+ type: typeof ADD_NOTIFICATION;
+ accountAddress: string;
+ notification: Notification;
+}
+
+export interface UpdateTransactionModal {
+ type: typeof UPDATE_TRANSACTION_MODAL_STATUS;
+ isOpen: boolean;
+ data: TransactionModalData;
+}
+
export type GeneralActions =
| IsBridgeLoaded
| InitGeneralDataAction
@@ -110,7 +125,9 @@ export type GeneralActions =
| UpdateHeights
| UpdateTotals
| ShowBuyModal
- | ShowSignTermsModal;
+ | ShowSignTermsModal
+ | AddNotification
+ | UpdateTransactionModal;
// REDEEM
export const ADD_VAULT_REDEEMS = 'ADD_VAULT_REDEEMS';
diff --git a/src/common/types/util.types.ts b/src/common/types/util.types.ts
index b49a70f30b..922531dad0 100644
--- a/src/common/types/util.types.ts
+++ b/src/common/types/util.types.ts
@@ -3,6 +3,8 @@ import { BitcoinAmount, MonetaryAmount } from '@interlay/monetary-js';
import { u256 } from '@polkadot/types/primitive';
import { CombinedState, Store } from 'redux';
+import { TransactionStatus } from '@/utils/hooks/transaction/types';
+
import { rootReducer } from '../reducers/index';
import { GeneralActions, RedeemActions, VaultActions } from './actions.types';
import { RedeemState } from './redeem.types';
@@ -45,6 +47,21 @@ export enum ParachainStatus {
Shutdown
}
+export type Notification = {
+ status: TransactionStatus;
+ description: string;
+ date: Date;
+ url?: string;
+};
+
+export type TransactionModalData = {
+ variant: TransactionStatus;
+ timestamp?: number;
+ description?: string;
+ url?: string;
+ errorMessage?: string;
+};
+
export type GeneralState = {
bridgeLoaded: boolean;
vaultClientLoaded: boolean;
@@ -56,6 +73,11 @@ export type GeneralState = {
btcRelayHeight: number;
bitcoinHeight: number;
parachainStatus: ParachainStatus;
+ notifications: Record;
+ transactionModal: {
+ isOpen: boolean;
+ data: TransactionModalData;
+ };
};
export type AppState = ReturnType;
diff --git a/src/component-library/TokenInput/TokenInput.tsx b/src/component-library/TokenInput/TokenInput.tsx
index ebf89b542f..109f6148ad 100644
--- a/src/component-library/TokenInput/TokenInput.tsx
+++ b/src/component-library/TokenInput/TokenInput.tsx
@@ -63,7 +63,7 @@ const TokenInput = forwardRef(
const itemsArr = Array.from(selectProps?.items || []);
const isSelectAdornment = itemsArr.length > 1;
- const adornmentTicker = !isSelectAdornment && selectProps?.items ? itemsArr[0]?.value : ticker;
+ const adornmentTicker = !isSelectAdornment && selectProps?.items ? itemsArr[0]?.value : tickerProp;
useEffect(() => {
if (selectProps?.value === undefined) return;
diff --git a/src/components/LoanApyTooltip/BreakdownGroup.tsx b/src/components/LoanApyTooltip/BreakdownGroup.tsx
index ebfbd86e95..be97f0249f 100644
--- a/src/components/LoanApyTooltip/BreakdownGroup.tsx
+++ b/src/components/LoanApyTooltip/BreakdownGroup.tsx
@@ -29,7 +29,7 @@ const BreakdownGroup = ({ apy, rewardsApy, ticker, rewardsTicker, isBorrow }: Br
{!!rewardsApy && (
- Rewards APY {rewardsTicker}:
+ Rewards APR {rewardsTicker}:
{getApyLabel(rewardsApy)}
)}
diff --git a/src/components/LoanApyTooltip/LoanApyTooltip.tsx b/src/components/LoanApyTooltip/LoanApyTooltip.tsx
index 37a2dbc5d2..f6cb684837 100644
--- a/src/components/LoanApyTooltip/LoanApyTooltip.tsx
+++ b/src/components/LoanApyTooltip/LoanApyTooltip.tsx
@@ -4,12 +4,12 @@ import { TooltipProps } from '@reach/tooltip';
import Big from 'big.js';
import { Dd, Dl, DlGroup } from '@/component-library';
+import { GOVERNANCE_TOKEN } from '@/config/relay-chains';
import { Prices } from '@/utils/hooks/api/use-get-prices';
import { AssetGroup } from './AssetGroup';
import { BreakdownGroup } from './BreakdownGroup';
import { StyledApyTooltipTitle, StyledTooltip } from './LoanApyTooltip.style';
-import { RewardsGroup } from './RewardsGroup';
type Props = {
apy: Big;
@@ -17,7 +17,6 @@ type Props = {
earnedInterest?: MonetaryAmount;
accumulatedDebt?: MonetaryAmount;
rewardsApy?: Big;
- rewards: MonetaryAmount | null;
prices: Prices;
isBorrow: boolean;
};
@@ -32,12 +31,11 @@ const LoanApyTooltip = ({
earnedInterest,
accumulatedDebt,
rewardsApy,
- rewards,
prices,
isBorrow,
...props
}: LoanApyTooltipProps): JSX.Element => {
- const showEarnedRewards = !!rewards || !!earnedInterest;
+ const showEarnedRewards = !!earnedInterest;
const label = (
@@ -45,7 +43,7 @@ const LoanApyTooltip = ({
apy={apy}
isBorrow={isBorrow}
rewardsApy={rewardsApy}
- rewardsTicker={rewards?.currency.ticker}
+ rewardsTicker={GOVERNANCE_TOKEN.ticker}
ticker={currency.ticker}
/>
{accumulatedDebt && (
@@ -64,7 +62,6 @@ const LoanApyTooltip = ({
-
{earnedInterest && }
- {!!rewards && }
diff --git a/src/components/LoanPositionsTable/ApyCell.tsx b/src/components/LoanPositionsTable/ApyCell.tsx
index 1860138278..d36911f7be 100644
--- a/src/components/LoanPositionsTable/ApyCell.tsx
+++ b/src/components/LoanPositionsTable/ApyCell.tsx
@@ -15,7 +15,6 @@ type ApyCellProps = {
earnedInterest?: MonetaryAmount;
accumulatedDebt?: MonetaryAmount;
rewardsPerYear: MonetaryAmount | null;
- accruedRewards: MonetaryAmount | null;
prices?: Prices;
isBorrow?: boolean;
onClick?: () => void;
@@ -25,7 +24,6 @@ const ApyCell = ({
apy,
currency,
rewardsPerYear,
- accruedRewards,
accumulatedDebt,
earnedInterest,
prices,
@@ -34,8 +32,9 @@ const ApyCell = ({
}: ApyCellProps): JSX.Element => {
const rewardsApy = getSubsidyRewardApy(currency, rewardsPerYear, prices);
- const totalApy = isBorrow ? apy.sub(rewardsApy || 0) : apy.add(rewardsApy || 0);
- const totalApyLabel = isBorrow ? `-${getApyLabel(totalApy)}` : getApyLabel(totalApy);
+ const totalApy = isBorrow ? (rewardsApy || Big(0)).sub(apy) : apy.add(rewardsApy || 0);
+
+ const totalApyLabel = getApyLabel(totalApy);
const earnedAsset = accumulatedDebt || earnedInterest;
@@ -54,7 +53,6 @@ const ApyCell = ({
apy={apy}
currency={currency}
prices={prices}
- rewards={accruedRewards}
rewardsApy={rewardsApy}
isBorrow={isBorrow}
accumulatedDebt={accumulatedDebt}
diff --git a/src/components/LoanPositionsTable/LoanPositionsTable.tsx b/src/components/LoanPositionsTable/LoanPositionsTable.tsx
index ac3ad99d26..ed56422a75 100644
--- a/src/components/LoanPositionsTable/LoanPositionsTable.tsx
+++ b/src/components/LoanPositionsTable/LoanPositionsTable.tsx
@@ -7,7 +7,6 @@ import { convertMonetaryAmountToValueInUSD } from '@/common/utils/utils';
import { Switch } from '@/component-library';
import { LoanType } from '@/types/loans';
import { getTokenPrice } from '@/utils/helpers/prices';
-import { useGetAccountSubsidyRewards } from '@/utils/hooks/api/loans/use-get-account-subsidy-rewards';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
import { AssetCell, BalanceCell, Table, TableProps } from '../DataGrid';
@@ -53,7 +52,6 @@ const LoanPositionsTable = ({
const titleId = useId();
const { t } = useTranslation();
const prices = useGetPrices();
- const { data: subsidyRewards } = useGetAccountSubsidyRewards();
const isLending = variant === 'lend';
const showCollateral = !!onPressCollateralSwitch && isLending;
@@ -91,13 +89,11 @@ const LoanPositionsTable = ({
const apyCellProps = isLending
? {
apy: lendApy,
- rewardsPerYear: lendReward,
- accruedRewards: subsidyRewards ? subsidyRewards.perMarket[currency.ticker].lend : null
+ rewardsPerYear: lendReward
}
: {
apy: borrowApy,
rewardsPerYear: borrowReward,
- accruedRewards: subsidyRewards ? subsidyRewards.perMarket[currency.ticker].borrow : null,
accumulatedDebt: (position as BorrowPosition).accumulatedDebt,
isBorrow: true
};
@@ -140,7 +136,7 @@ const LoanPositionsTable = ({
collateral
};
}),
- [assets, isLending, onPressCollateralSwitch, onRowAction, positions, prices, showCollateral, subsidyRewards]
+ [assets, isLending, onPressCollateralSwitch, onRowAction, positions, prices, showCollateral]
);
return (
diff --git a/src/components/NotificationsPopover/NotificationsList.tsx b/src/components/NotificationsPopover/NotificationsList.tsx
new file mode 100644
index 0000000000..a84dc4091e
--- /dev/null
+++ b/src/components/NotificationsPopover/NotificationsList.tsx
@@ -0,0 +1,35 @@
+import { useTranslation } from 'react-i18next';
+
+import { Notification } from '@/common/types/util.types';
+import { Flex, P } from '@/component-library';
+
+import { NotificationListItem } from './NotificationsListItem';
+
+type NotificationsListProps = {
+ items: Notification[];
+};
+
+const NotificationsList = ({ items }: NotificationsListProps): JSX.Element => {
+ const { t } = useTranslation();
+
+ if (!items.length) {
+ return (
+
+ {t('transaction.no_recent_transactions')}
+
+ );
+ }
+
+ const latestTransactions = items.slice(-5).sort((a, b) => b.date.getTime() - a.date.getTime());
+
+ return (
+
+ {latestTransactions.map((item, index) => (
+
+ ))}
+
+ );
+};
+
+export { NotificationsList };
+export type { NotificationsListProps };
diff --git a/src/components/NotificationsPopover/NotificationsListItem.tsx b/src/components/NotificationsPopover/NotificationsListItem.tsx
new file mode 100644
index 0000000000..7c29cdfce8
--- /dev/null
+++ b/src/components/NotificationsPopover/NotificationsListItem.tsx
@@ -0,0 +1,42 @@
+import { useButton } from '@react-aria/button';
+import { formatDistanceToNowStrict } from 'date-fns';
+import { useRef } from 'react';
+
+import { CheckCircle, XCircle } from '@/assets/icons';
+import { Notification } from '@/common/types/util.types';
+import { Flex, P } from '@/component-library';
+import { TransactionStatus } from '@/utils/hooks/transaction/types';
+
+import { StyledListItem } from './NotificationsPopover.styles';
+
+type NotificationListItemProps = Notification;
+
+const NotificationListItem = ({ date, description, status, url }: NotificationListItemProps): JSX.Element => {
+ const ref = useRef(null);
+
+ const ariaLabel = url ? 'navigate to transaction subscan page' : undefined;
+
+ const handlePress = () => window.open(url, '_blank', 'noopener');
+
+ const { buttonProps } = useButton(
+ { 'aria-label': ariaLabel, isDisabled: !url, elementType: 'div', onPress: handlePress },
+ ref
+ );
+
+ return (
+
+
+
+ {status === TransactionStatus.SUCCESS ? : }
+ {description}
+
+
+ {formatDistanceToNowStrict(date)} ago
+
+
+
+ );
+};
+
+export { NotificationListItem };
+export type { NotificationListItemProps };
diff --git a/src/components/NotificationsPopover/NotificationsPopover.styles.tsx b/src/components/NotificationsPopover/NotificationsPopover.styles.tsx
new file mode 100644
index 0000000000..828c137438
--- /dev/null
+++ b/src/components/NotificationsPopover/NotificationsPopover.styles.tsx
@@ -0,0 +1,18 @@
+import styled from 'styled-components';
+
+import { CTA, theme } from '@/component-library';
+
+const StyledListItem = styled.div`
+ padding: ${theme.spacing.spacing3} ${theme.spacing.spacing2};
+
+ &:not(:last-of-type) {
+ border-bottom: ${theme.border.default};
+ }
+`;
+
+const StyledCTA = styled(CTA)`
+ padding: ${theme.spacing.spacing3};
+ border: ${theme.border.default};
+`;
+
+export { StyledCTA, StyledListItem };
diff --git a/src/components/NotificationsPopover/NotificationsPopover.tsx b/src/components/NotificationsPopover/NotificationsPopover.tsx
new file mode 100644
index 0000000000..334298a192
--- /dev/null
+++ b/src/components/NotificationsPopover/NotificationsPopover.tsx
@@ -0,0 +1,55 @@
+import { useTranslation } from 'react-i18next';
+
+import { ListBullet } from '@/assets/icons';
+import { Notification } from '@/common/types/util.types';
+import {
+ Popover,
+ PopoverBody,
+ PopoverContent,
+ PopoverFooter,
+ PopoverHeader,
+ PopoverTrigger,
+ TextLink
+} from '@/component-library';
+import { EXTERNAL_PAGES, EXTERNAL_URL_PARAMETERS } from '@/utils/constants/links';
+
+import { NotificationsList } from './NotificationsList';
+import { StyledCTA } from './NotificationsPopover.styles';
+
+type NotificationsPopoverProps = {
+ address?: string;
+ items: Notification[];
+};
+
+const NotificationsPopover = ({ address, items }: NotificationsPopoverProps): JSX.Element => {
+ const { t } = useTranslation();
+
+ const accountTransactionsUrl =
+ address && EXTERNAL_PAGES.SUBSCAN.ACCOUNT.replace(`:${EXTERNAL_URL_PARAMETERS.SUBSCAN.ACCOUNT.ADDRESS}`, address);
+
+ return (
+
+
+
+
+
+
+
+ {t('transaction.recent_transactions')}
+
+
+
+ {accountTransactionsUrl && (
+
+
+ View all transactions
+
+
+ )}
+
+
+ );
+};
+
+export { NotificationsPopover };
+export type { NotificationsPopoverProps };
diff --git a/src/components/NotificationsPopover/index.tsx b/src/components/NotificationsPopover/index.tsx
new file mode 100644
index 0000000000..9d68f4a5e0
--- /dev/null
+++ b/src/components/NotificationsPopover/index.tsx
@@ -0,0 +1,2 @@
+export type { NotificationsPopoverProps } from './NotificationsPopover';
+export { NotificationsPopover } from './NotificationsPopover';
diff --git a/src/components/ToastContainer/ToastContainer.styles.tsx b/src/components/ToastContainer/ToastContainer.styles.tsx
new file mode 100644
index 0000000000..0de455dab2
--- /dev/null
+++ b/src/components/ToastContainer/ToastContainer.styles.tsx
@@ -0,0 +1,36 @@
+import 'react-toastify/dist/ReactToastify.css';
+
+import { ToastContainer } from 'react-toastify';
+import styled from 'styled-components';
+
+import { theme } from '@/component-library';
+
+// &&& is used to override css styles
+const StyledToastContainer = styled(ToastContainer)`
+ &&&.Toastify__toast-container {
+ color: ${theme.colors.textPrimary};
+ padding: 0 ${theme.spacing.spacing4};
+ }
+
+ @media ${theme.breakpoints.up('sm')} {
+ &&&.Toastify__toast-container {
+ padding: 0;
+ }
+ }
+
+ .Toastify__toast {
+ margin-bottom: 1rem;
+ padding: 0;
+ border-radius: 12px;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ font-family: inherit;
+ background: ${theme.colors.bgPrimary};
+ border: ${theme.border.default};
+ }
+
+ .Toastify__toast-body {
+ padding: 0;
+ }
+`;
+
+export { StyledToastContainer };
diff --git a/src/components/ToastContainer/ToastContainer.tsx b/src/components/ToastContainer/ToastContainer.tsx
new file mode 100644
index 0000000000..8119b67efd
--- /dev/null
+++ b/src/components/ToastContainer/ToastContainer.tsx
@@ -0,0 +1,5 @@
+import { ToastContainerProps } from 'react-toastify';
+
+import { StyledToastContainer } from './ToastContainer.styles';
+export { StyledToastContainer as ToastContainer };
+export type { ToastContainerProps };
diff --git a/src/components/ToastContainer/index.tsx b/src/components/ToastContainer/index.tsx
new file mode 100644
index 0000000000..31f30105c2
--- /dev/null
+++ b/src/components/ToastContainer/index.tsx
@@ -0,0 +1,2 @@
+export type { ToastContainerProps } from './ToastContainer';
+export { ToastContainer } from './ToastContainer';
diff --git a/src/components/TransactionModal/TransactionModal.style.tsx b/src/components/TransactionModal/TransactionModal.style.tsx
new file mode 100644
index 0000000000..7819fa032b
--- /dev/null
+++ b/src/components/TransactionModal/TransactionModal.style.tsx
@@ -0,0 +1,21 @@
+import styled from 'styled-components';
+
+import { CheckCircle, XCircle } from '@/assets/icons';
+import { Card, theme } from '@/component-library';
+
+const StyledXCircle = styled(XCircle)`
+ width: 4rem;
+ height: 4rem;
+`;
+
+const StyledCheckCircle = styled(CheckCircle)`
+ width: 4rem;
+ height: 4rem;
+`;
+
+const StyledCard = styled(Card)`
+ border-radius: ${theme.rounded.rg};
+ padding: ${theme.spacing.spacing4};
+`;
+
+export { StyledCard, StyledCheckCircle, StyledXCircle };
diff --git a/src/components/TransactionModal/TransactionModal.tsx b/src/components/TransactionModal/TransactionModal.tsx
new file mode 100644
index 0000000000..6ab35ada22
--- /dev/null
+++ b/src/components/TransactionModal/TransactionModal.tsx
@@ -0,0 +1,112 @@
+import { TFunction } from 'i18next';
+import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { updateTransactionModal } from '@/common/actions/general.actions';
+import { StoreType } from '@/common/types/util.types';
+import {
+ CTA,
+ Flex,
+ H4,
+ H5,
+ LoadingSpinner,
+ Modal,
+ ModalBody,
+ ModalFooter,
+ ModalHeader,
+ P,
+ TextLink
+} from '@/component-library';
+import { NotificationToast, useNotifications } from '@/utils/context/Notifications';
+import { TransactionStatus } from '@/utils/hooks/transaction/types';
+
+import { StyledCard, StyledCheckCircle, StyledXCircle } from './TransactionModal.style';
+
+const loadingSpinner = ;
+
+const getData = (t: TFunction, variant: TransactionStatus) =>
+ ({
+ [TransactionStatus.CONFIRM]: {
+ title: t('transaction.confirm_transaction'),
+ subtitle: t('transaction.confirm_transaction_wallet'),
+ icon: loadingSpinner
+ },
+ [TransactionStatus.SUBMITTING]: {
+ title: t('transaction.transaction_processing'),
+ icon: loadingSpinner
+ },
+ [TransactionStatus.ERROR]: {
+ title: t('transaction.transaction_failed'),
+ icon:
+ },
+ [TransactionStatus.SUCCESS]: {
+ title: t('transaction.transaction_successful'),
+ icon:
+ }
+ }[variant]);
+
+const TransactionModal = (): JSX.Element => {
+ const { t } = useTranslation();
+
+ const notifications = useNotifications();
+
+ const { isOpen, data } = useSelector((state: StoreType) => state.general.transactionModal);
+ const { variant, description, url, timestamp, errorMessage } = data;
+ const dispatch = useDispatch();
+
+ const { title, subtitle, icon } = getData(t, variant);
+
+ const hasDismiss = variant !== TransactionStatus.CONFIRM;
+
+ const handleClose = () => {
+ // Only show toast if the current transaction variant is CONFIRM or SUBMITTING.
+ // No need to show toast if the transaction is SUCCESS or ERROR
+ if (timestamp && (variant === TransactionStatus.CONFIRM || variant === TransactionStatus.SUBMITTING)) {
+ notifications.show(timestamp, {
+ type: NotificationToast.TRANSACTION,
+ props: { variant: variant, url, description }
+ });
+ }
+
+ dispatch(updateTransactionModal(false, data));
+ };
+
+ return (
+
+ {title}
+
+ {icon}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+ {errorMessage && (
+
+
+ Message:
+
+
+ {errorMessage}
+
+
+ )}
+ {url && (
+
+ View transaction on Subscan
+
+ )}
+
+
+ {hasDismiss && {t('dismiss')}}
+
+ );
+};
+
+export { TransactionModal };
diff --git a/src/components/TransactionModal/index.tsx b/src/components/TransactionModal/index.tsx
new file mode 100644
index 0000000000..db2576f068
--- /dev/null
+++ b/src/components/TransactionModal/index.tsx
@@ -0,0 +1 @@
+export { TransactionModal } from './TransactionModal';
diff --git a/src/components/TransactionToast/TransactionToast.styles.tsx b/src/components/TransactionToast/TransactionToast.styles.tsx
new file mode 100644
index 0000000000..11a85da6e7
--- /dev/null
+++ b/src/components/TransactionToast/TransactionToast.styles.tsx
@@ -0,0 +1,13 @@
+import styled from 'styled-components';
+
+import { Flex, ProgressBar, theme } from '@/component-library';
+
+const StyledWrapper = styled(Flex)`
+ padding: ${theme.spacing.spacing4};
+`;
+
+const StyledProgressBar = styled(ProgressBar)`
+ margin-top: ${theme.spacing.spacing4};
+`;
+
+export { StyledProgressBar, StyledWrapper };
diff --git a/src/components/TransactionToast/TransactionToast.tsx b/src/components/TransactionToast/TransactionToast.tsx
new file mode 100644
index 0000000000..fc413baba1
--- /dev/null
+++ b/src/components/TransactionToast/TransactionToast.tsx
@@ -0,0 +1,132 @@
+import { useHover } from '@react-aria/interactions';
+import { mergeProps } from '@react-aria/utils';
+import { TFunction } from 'i18next';
+import { useTranslation } from 'react-i18next';
+import { useDispatch } from 'react-redux';
+
+import { CheckCircle, XCircle } from '@/assets/icons';
+import { updateTransactionModal } from '@/common/actions/general.actions';
+import { CTA, CTALink, Divider, Flex, FlexProps, LoadingSpinner, P } from '@/component-library';
+import { TransactionStatus } from '@/utils/hooks/transaction/types';
+import { useCountdown } from '@/utils/hooks/use-countdown';
+
+import { StyledProgressBar, StyledWrapper } from './TransactionToast.styles';
+
+const loadingSpinner = ;
+
+const getData = (t: TFunction, variant: TransactionStatus) =>
+ ({
+ [TransactionStatus.CONFIRM]: {
+ title: t('transaction.confirm_transaction'),
+ icon: loadingSpinner
+ },
+ [TransactionStatus.SUBMITTING]: {
+ title: t('transaction.transaction_processing'),
+ icon: loadingSpinner
+ },
+ [TransactionStatus.SUCCESS]: {
+ title: t('transaction.transaction_successful'),
+ icon:
+ },
+ [TransactionStatus.ERROR]: {
+ title: t('transaction.transaction_failed'),
+ icon:
+ }
+ }[variant]);
+
+type Props = {
+ variant?: TransactionStatus;
+ description?: string;
+ url?: string;
+ errorMessage?: string;
+ timeout?: number;
+ onDismiss?: () => void;
+};
+
+type InheritAttrs = Omit;
+
+type TransactionToastProps = Props & InheritAttrs;
+
+const TransactionToast = ({
+ variant = TransactionStatus.SUCCESS,
+ timeout = 8000,
+ url,
+ description,
+ onDismiss,
+ errorMessage,
+ ...props
+}: TransactionToastProps): JSX.Element => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ const showCountdown = variant === TransactionStatus.SUCCESS || variant === TransactionStatus.ERROR;
+
+ const { value: countdown, start, stop } = useCountdown({
+ timeout,
+ disabled: !showCountdown,
+ onEndCountdown: onDismiss
+ });
+
+ const { hoverProps } = useHover({
+ onHoverStart: stop,
+ onHoverEnd: start,
+ isDisabled: !showCountdown
+ });
+
+ const handleViewDetails = () => {
+ dispatch(updateTransactionModal(true, { variant: TransactionStatus.ERROR, description, errorMessage }));
+ onDismiss?.();
+ };
+
+ const { title, icon } = getData(t, variant);
+
+ return (
+
+
+
+ {icon}
+
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {showCountdown && (
+
+ )}
+
+ {(url || errorMessage) && (
+ <>
+ {url && (
+
+ View Subscan
+
+ )}
+ {errorMessage && !url && (
+
+ View Details
+
+ )}
+
+ >
+ )}
+
+ Dismiss
+
+
+
+ );
+};
+
+export { TransactionToast };
+export type { TransactionToastProps };
diff --git a/src/components/TransactionToast/index.tsx b/src/components/TransactionToast/index.tsx
new file mode 100644
index 0000000000..36ce2db462
--- /dev/null
+++ b/src/components/TransactionToast/index.tsx
@@ -0,0 +1,2 @@
+export type { TransactionToastProps } from './TransactionToast';
+export { TransactionToast } from './TransactionToast';
diff --git a/src/components/index.tsx b/src/components/index.tsx
index 83fc0ca6aa..bb20578fa9 100644
--- a/src/components/index.tsx
+++ b/src/components/index.tsx
@@ -10,6 +10,12 @@ export type { IsAuthenticatedProps } from './IsAuthenticated';
export { IsAuthenticated } from './IsAuthenticated';
export type { LoanPositionsTableProps } from './LoanPositionsTable';
export { LoanPositionsTable } from './LoanPositionsTable';
+export type { NotificationsPopoverProps } from './NotificationsPopover';
+export { NotificationsPopover } from './NotificationsPopover';
export type { PoolsTableProps } from './PoolsTable';
export { PoolsTable } from './PoolsTable';
export { ReceivableAssets } from './ReceivableAssets';
+export type { ToastContainerProps } from './ToastContainer';
+export { ToastContainer } from './ToastContainer';
+export type { TransactionToastProps } from './TransactionToast';
+export { TransactionToast } from './TransactionToast';
diff --git a/src/index.tsx b/src/index.tsx
index 4901f7d9a1..327b7658e6 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -21,6 +21,7 @@ import App from './App';
import { GeoblockingWrapper } from './components/Geoblock/Geoblock';
import reportWebVitals from './reportWebVitals';
import { store } from './store';
+import { NotificationsProvider } from './utils/context/Notifications';
configGlobalBig();
@@ -40,9 +41,11 @@ ReactDOM.render(
-
-
-
+
+
+
+
+
diff --git a/src/legacy-components/ErrorModal/index.tsx b/src/legacy-components/ErrorModal/index.tsx
deleted file mode 100644
index 8dc60f3f6e..0000000000
--- a/src/legacy-components/ErrorModal/index.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import clsx from 'clsx';
-import * as React from 'react';
-
-import CloseIconButton from '@/legacy-components/buttons/CloseIconButton';
-import InterlayModal, {
- InterlayModalInnerWrapper,
- InterlayModalTitle,
- Props as ModalProps
-} from '@/legacy-components/UI/InterlayModal';
-
-interface CustomProps {
- title: string;
- description: string;
-}
-
-const ErrorModal = ({ open, onClose, title, description }: Props): JSX.Element => {
- const focusRef = React.useRef(null);
-
- return (
-
-
-
- {title}
-
-
- {description}
-
-
- );
-};
-
-export type Props = Omit & CustomProps;
-
-export default ErrorModal;
diff --git a/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx b/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
index e76d52fe2d..3472455342 100644
--- a/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
+++ b/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
@@ -1,22 +1,14 @@
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
import { FaCheckCircle } from 'react-icons/fa';
-import { useQueryClient } from 'react-query';
-import { toast } from 'react-toastify';
import { BTC_EXPLORER_TRANSACTION_API } from '@/config/blockstream-explorer-links';
import { WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains';
import AddressWithCopyUI from '@/legacy-components/AddressWithCopyUI';
-import ErrorModal from '@/legacy-components/ErrorModal';
import ExternalLink from '@/legacy-components/ExternalLink';
import RequestWrapper from '@/pages/Bridge/RequestWrapper';
-import { ISSUES_FETCHER } from '@/services/fetchers/issues-fetcher';
-import { TABLE_PAGE_LIMIT } from '@/utils/constants/general';
-import { QUERY_PARAMETERS } from '@/utils/constants/links';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import { getColorShade } from '@/utils/helpers/colors';
-import { Transaction, useTransaction } from '@/utils/hooks/transaction';
-import useQueryParams from '@/utils/hooks/use-query-params';
import ManualIssueExecutionUI from '../ManualIssueExecutionUI';
@@ -28,21 +20,6 @@ interface Props {
const ConfirmedIssueRequest = ({ request }: Props): JSX.Element => {
const { t } = useTranslation();
- const queryParams = useQueryParams();
- const selectedPage = Number(queryParams.get(QUERY_PARAMETERS.PAGE)) || 1;
- const selectedPageIndex = selectedPage - 1;
-
- const queryClient = useQueryClient();
-
- // TODO: check if this transaction is necessary
- const transaction = useTransaction(Transaction.ISSUE_EXECUTE, {
- onSuccess: (_, variables) => {
- const [requestId] = variables.args;
- queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
- toast.success(t('issue_page.successfully_executed', { id: requestId }));
- }
- });
-
return (
<>
@@ -75,16 +52,6 @@ const ConfirmedIssueRequest = ({ request }: Props): JSX.Element => {
- {transaction.isError && transaction.error && (
- {
- transaction.reset();
- }}
- title='Error'
- description={typeof transaction.error === 'string' ? transaction.error : transaction.error.message}
- />
- )}
>
);
};
diff --git a/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx b/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
index c93aa9aae2..a111faff63 100644
--- a/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
+++ b/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
@@ -8,12 +8,10 @@ import {
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
import { useQuery, useQueryClient } from 'react-query';
-import { toast } from 'react-toastify';
import { displayMonetaryAmount } from '@/common/utils/utils';
import { WRAPPED_TOKEN, WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains';
import InterlayDenimOrKintsugiMidnightOutlinedButton from '@/legacy-components/buttons/InterlayDenimOrKintsugiMidnightOutlinedButton';
-import ErrorModal from '@/legacy-components/ErrorModal';
import { useSubstrateSecureState } from '@/lib/substrate';
import { ISSUES_FETCHER } from '@/services/fetchers/issues-fetcher';
import { TABLE_PAGE_LIMIT } from '@/utils/constants/general';
@@ -57,10 +55,8 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
const queryClient = useQueryClient();
const transaction = useTransaction(Transaction.ISSUE_EXECUTE, {
- onSuccess: (_, variables) => {
- const [requestId] = variables.args;
+ onSuccess: () => {
queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
- toast.success(t('issue_page.successfully_executed', { id: requestId }));
}
});
@@ -139,16 +135,6 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
wrappedTokenSymbol: WRAPPED_TOKEN_SYMBOL
})}
- {transaction.isError && transaction.error && (
- {
- transaction.reset();
- }}
- title='Error'
- description={typeof transaction.error === 'string' ? transaction.error : transaction.error.message}
- />
- )}
);
};
diff --git a/src/legacy-components/IssueUI/index.tsx b/src/legacy-components/IssueUI/index.tsx
index 2158916386..4edd9b6878 100644
--- a/src/legacy-components/IssueUI/index.tsx
+++ b/src/legacy-components/IssueUI/index.tsx
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { ReactComponent as BitcoinLogoIcon } from '@/assets/img/bitcoin-logo.svg';
import { displayMonetaryAmountInUSDFormat, formatNumber } from '@/common/utils/utils';
+import { Flex } from '@/component-library';
import { WRAPPED_TOKEN_SYMBOL, WrappedTokenAmount } from '@/config/relay-chains';
import AddressWithCopyUI from '@/legacy-components/AddressWithCopyUI';
import Hr2 from '@/legacy-components/hrs/Hr2';
@@ -52,7 +53,7 @@ const IssueUI = ({ issue }: Props): JSX.Element => {
const sentBackingTokenAmount = receivedWrappedTokenAmount.add(bridgeFee);
return (
-
+
{/* TODO: could componentize */}
@@ -184,7 +185,7 @@ const IssueUI = ({ issue }: Props): JSX.Element => {
<>{renderModalStatusPanel(issue)}>
-
+
);
};
diff --git a/src/legacy-components/RedeemUI/ReimburseStatusUI/index.tsx b/src/legacy-components/RedeemUI/ReimburseStatusUI/index.tsx
index 2e2cc3b19e..541f4ef875 100644
--- a/src/legacy-components/RedeemUI/ReimburseStatusUI/index.tsx
+++ b/src/legacy-components/RedeemUI/ReimburseStatusUI/index.tsx
@@ -1,14 +1,12 @@
import { newMonetaryAmount } from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
import Big from 'big.js';
import clsx from 'clsx';
import * as React from 'react';
import { useErrorHandler, withErrorBoundary } from 'react-error-boundary';
import { useTranslation } from 'react-i18next';
import { FaExclamationCircle } from 'react-icons/fa';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
-import { toast } from 'react-toastify';
import { StoreType } from '@/common/types/util.types';
import { displayMonetaryAmount, displayMonetaryAmountInUSDFormat } from '@/common/utils/utils';
@@ -22,10 +20,10 @@ import RequestWrapper from '@/pages/Bridge/RequestWrapper';
import { REDEEMS_FETCHER } from '@/services/fetchers/redeems-fetcher';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import { getColorShade } from '@/utils/helpers/colors';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
interface Props {
redeem: any; // TODO: should type properly (`Relay`)
@@ -45,6 +43,20 @@ const ReimburseStatusUI = ({ redeem, onClose }: Props): JSX.Element => {
);
const { t } = useTranslation();
const handleError = useErrorHandler();
+ const queryClient = useQueryClient();
+
+ const [cancelType, setCancelType] = React.useState<'reimburse' | 'retry'>();
+
+ const transaction = useTransaction(Transaction.REDEEM_CANCEL, {
+ onSuccess: () => {
+ queryClient.invalidateQueries([REDEEMS_FETCHER]);
+ setCancelType(undefined);
+ onClose();
+ },
+ onError: () => {
+ setCancelType(undefined);
+ }
+ });
React.useEffect(() => {
if (!bridgeLoaded) return;
@@ -67,48 +79,13 @@ const ReimburseStatusUI = ({ redeem, onClose }: Props): JSX.Element => {
})();
}, [redeem, bridgeLoaded, handleError]);
- const queryClient = useQueryClient();
- // TODO: should type properly (`Relay`)
- const retryMutation = useMutation
(
- (variables: any) => {
- return submitExtrinsic(window.bridge.redeem.cancel(variables.id, false));
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries([REDEEMS_FETCHER]);
- toast.success(t('redeem_page.successfully_cancelled_redeem'));
- onClose();
- },
- onError: (error) => {
- console.log('[useMutation] error => ', error);
- toast.error(t('redeem_page.error_cancelling_redeem'));
- }
- }
- );
- // TODO: should type properly (`Relay`)
- const reimburseMutation = useMutation(
- (variables: any) => {
- return submitExtrinsic(window.bridge.redeem.cancel(variables.id, true));
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries([REDEEMS_FETCHER]);
- toast.success(t('redeem_page.successfully_cancelled_redeem'));
- onClose();
- },
- onError: (error) => {
- console.log('[useMutation] error => ', error);
- toast.error(t('redeem_page.error_cancelling_redeem'));
- }
- }
- );
-
const handleRetry = () => {
if (!bridgeLoaded) {
throw new Error('Bridge is not loaded!');
}
- retryMutation.mutate(redeem);
+ setCancelType('retry');
+ transaction.execute(redeem.id, false);
};
const handleReimburse = () => {
@@ -116,7 +93,8 @@ const ReimburseStatusUI = ({ redeem, onClose }: Props): JSX.Element => {
throw new Error('Bridge is not loaded!');
}
- reimburseMutation.mutate(redeem);
+ setCancelType('reimburse');
+ transaction.execute(redeem.id, true);
};
const isOwner = selectedAccount?.address === redeem.userParachainAddress;
@@ -198,8 +176,8 @@ const ReimburseStatusUI = ({ redeem, onClose }: Props): JSX.Element => {
{t('retry')}
@@ -239,8 +217,8 @@ const ReimburseStatusUI = ({ redeem, onClose }: Props): JSX.Element => {
{t('redeem_page.reimburse')}
diff --git a/src/legacy-components/RedeemUI/index.tsx b/src/legacy-components/RedeemUI/index.tsx
index 34820878c5..229f1f3619 100644
--- a/src/legacy-components/RedeemUI/index.tsx
+++ b/src/legacy-components/RedeemUI/index.tsx
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { ReactComponent as BitcoinLogoIcon } from '@/assets/img/bitcoin-logo.svg';
import { displayMonetaryAmountInUSDFormat, formatNumber } from '@/common/utils/utils';
+import { Flex } from '@/component-library';
import { WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains';
import AddressWithCopyUI from '@/legacy-components/AddressWithCopyUI';
import Hr2 from '@/legacy-components/hrs/Hr2';
@@ -43,7 +44,7 @@ const RedeemUI = ({ redeem, onClose }: Props): JSX.Element => {
};
return (
-
+
@@ -160,7 +161,7 @@ const RedeemUI = ({ redeem, onClose }: Props): JSX.Element => {
<>{renderModalStatusPanel(redeem)}>
-
+
);
};
diff --git a/src/lib/substrate/components/SubstrateLoadingAndErrorHandlingWrapper/index.tsx b/src/lib/substrate/components/SubstrateLoadingAndErrorHandlingWrapper/index.tsx
index 1c378b87cf..0e6a048b99 100644
--- a/src/lib/substrate/components/SubstrateLoadingAndErrorHandlingWrapper/index.tsx
+++ b/src/lib/substrate/components/SubstrateLoadingAndErrorHandlingWrapper/index.tsx
@@ -1,5 +1,5 @@
import { useDispatch } from 'react-redux';
-import { toast, ToastContainer } from 'react-toastify';
+import { toast } from 'react-toastify';
import { isBridgeLoaded } from '@/common/actions/general.actions';
import FullLoadingSpinner from '@/legacy-components/FullLoadingSpinner';
@@ -66,7 +66,6 @@ const SubstrateLoadingAndErrorHandlingWrapper = ({
return (
<>
-
{children}
>
);
diff --git a/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx b/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
index 4baf947a1b..6b7387aeee 100644
--- a/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
+++ b/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
@@ -3,7 +3,6 @@ import { mergeProps } from '@react-aria/utils';
import Big from 'big.js';
import { ChangeEventHandler, RefObject, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { toast } from 'react-toastify';
import { displayMonetaryAmountInUSDFormat, newSafeMonetaryAmount } from '@/common/utils/utils';
import { Alert, Dd, DlGroup, Dt, Flex, TokenInput } from '@/component-library';
@@ -35,10 +34,11 @@ const isCustomAmountsMode = (form: ReturnType) =>
type DepositFormProps = {
pool: LiquidityPool;
slippageModalRef: RefObject;
- onDeposit?: () => void;
+ onSuccess?: () => void;
+ onSigning?: () => void;
};
-const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): JSX.Element => {
+const DepositForm = ({ pool, slippageModalRef, onSuccess, onSigning }: DepositFormProps): JSX.Element => {
const { pooledCurrencies } = pool;
const defaultValues = pooledCurrencies.reduce((acc, amount) => ({ ...acc, [amount.currency.ticker]: '' }), {});
@@ -52,13 +52,8 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
const governanceBalance = getBalance(GOVERNANCE_TOKEN.ticker)?.free || newMonetaryAmount(0, GOVERNANCE_TOKEN);
const transaction = useTransaction(Transaction.AMM_ADD_LIQUIDITY, {
- onSuccess: () => {
- onDeposit?.();
- toast.success('Deposit successful');
- },
- onError: (error) => {
- toast.error(error.message);
- }
+ onSuccess,
+ onSigning
});
const handleSubmit = async (data: DepositLiquidityPoolFormData) => {
@@ -72,8 +67,8 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
const deadline = await window.bridge.system.getFutureBlockNumber(AMM_DEADLINE_INTERVAL);
return transaction.execute(amounts, pool, slippage, deadline, accountId);
- } catch (err: any) {
- toast.error(err.toString());
+ } catch (error: any) {
+ transaction.reject(error);
}
};
@@ -91,8 +86,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
const form = useForm({
initialValues: defaultValues,
validationSchema: depositLiquidityPoolSchema({ transactionFee: TRANSACTION_FEE_AMOUNT, governanceBalance, tokens }),
- onSubmit: handleSubmit,
- disableValidation: transaction.isLoading
+ onSubmit: handleSubmit
});
const handleChange: ChangeEventHandler = (e) => {
@@ -189,7 +183,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
-
+
{t('amm.pools.add_liquidity')}
diff --git a/src/pages/AMM/Pools/components/DepositForm/DepositOutputAssets.tsx b/src/pages/AMM/Pools/components/DepositForm/DepositOutputAssets.tsx
index 5a17465ec9..f21e8f81af 100644
--- a/src/pages/AMM/Pools/components/DepositForm/DepositOutputAssets.tsx
+++ b/src/pages/AMM/Pools/components/DepositForm/DepositOutputAssets.tsx
@@ -56,7 +56,7 @@ const DepositOutputAssets = ({ pool, values, prices }: DepositOutputAssetsProps)
return (
- {t('amm.pools.receivable_assets')}
+ {t('receivable_assets')}
diff --git a/src/pages/AMM/Pools/components/PoolModal/PoolModal.tsx b/src/pages/AMM/Pools/components/PoolModal/PoolModal.tsx
index 2e1086a25b..b768873cff 100644
--- a/src/pages/AMM/Pools/components/PoolModal/PoolModal.tsx
+++ b/src/pages/AMM/Pools/components/PoolModal/PoolModal.tsx
@@ -26,11 +26,6 @@ const PoolModal = ({ pool, onClose, ...props }: PoolModalProps): JSX.Element | n
return null;
}
- const handleAction = () => {
- refetch();
- onClose?.();
- };
-
return (
-
+
-
+
diff --git a/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx b/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
index 1689c20a32..961db4e3c0 100644
--- a/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
+++ b/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
@@ -1,7 +1,6 @@
import { LiquidityPool } from '@interlay/interbtc-api';
import Big from 'big.js';
import { useTranslation } from 'react-i18next';
-import { toast } from 'react-toastify';
import { formatUSD } from '@/common/utils/utils';
import { Card, Dl, DlGroup } from '@/component-library';
@@ -49,13 +48,8 @@ const PoolsInsights = ({ pools, accountPoolsData, refetch }: PoolsInsightsProps)
const totalClaimableRewardUSD = calculateClaimableFarmingRewardUSD(accountPoolsData?.claimableRewards, prices);
- const handleSuccess = () => {
- toast.success(t('successfully_claimed_rewards'));
- refetch();
- };
-
const transaction = useTransaction(Transaction.AMM_CLAIM_REWARDS, {
- onSuccess: handleSuccess
+ onSuccess: refetch
});
const handleClickClaimRewards = () => accountPoolsData && transaction.execute(accountPoolsData.claimableRewards);
diff --git a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
index 2d74a356af..10d7f0fa85 100644
--- a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
+++ b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
@@ -2,7 +2,6 @@ import { LiquidityPool, newMonetaryAmount } from '@interlay/interbtc-api';
import Big from 'big.js';
import { RefObject, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { toast } from 'react-toastify';
import {
convertMonetaryAmountToValueInUSD,
@@ -28,10 +27,11 @@ import { StyledDl } from './WithdrawForm.styles';
type WithdrawFormProps = {
pool: LiquidityPool;
slippageModalRef: RefObject;
- onWithdraw?: () => void;
+ onSuccess?: () => void;
+ onSigning?: () => void;
};
-const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps): JSX.Element => {
+const WithdrawForm = ({ pool, slippageModalRef, onSuccess, onSigning }: WithdrawFormProps): JSX.Element => {
const [slippage, setSlippage] = useState(0.1);
const accountId = useAccountId();
@@ -40,13 +40,8 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
const { getBalance } = useGetBalances();
const transaction = useTransaction(Transaction.AMM_REMOVE_LIQUIDITY, {
- onSuccess: () => {
- onWithdraw?.();
- toast.success('Withdraw successful');
- },
- onError: (err) => {
- toast.error(err.message);
- }
+ onSuccess,
+ onSigning
});
const { lpToken } = pool;
@@ -70,8 +65,8 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
const deadline = await window.bridge.system.getFutureBlockNumber(AMM_DEADLINE_INTERVAL);
return transaction.execute(amount, pool, slippage, deadline, accountId);
- } catch (err: any) {
- toast.error(err.toString());
+ } catch (error: any) {
+ transaction.reject(error);
}
};
@@ -141,7 +136,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
-
+
{t('amm.pools.remove_liquidity')}
diff --git a/src/pages/AMM/Swap/components/SwapForm/SwapCTA.tsx b/src/pages/AMM/Swap/components/SwapForm/SwapCTA.tsx
index 3ef5503393..a817c120ff 100644
--- a/src/pages/AMM/Swap/components/SwapForm/SwapCTA.tsx
+++ b/src/pages/AMM/Swap/components/SwapForm/SwapCTA.tsx
@@ -45,8 +45,7 @@ const getProps = (
}
return {
- children: t('amm.swap'),
- disabled: false
+ children: t('amm.swap')
};
};
@@ -54,15 +53,14 @@ type SwapCTAProps = {
pair: SwapPair;
trade: Trade | null | undefined;
errors: FormErrors;
- loading: boolean;
};
-const SwapCTA = ({ pair, trade, errors, loading }: SwapCTAProps): JSX.Element | null => {
+const SwapCTA = ({ pair, trade, errors }: SwapCTAProps): JSX.Element | null => {
const { t } = useTranslation();
const otherProps = getProps(pair, trade, errors, t);
- return ;
+ return ;
};
export { SwapCTA };
diff --git a/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx b/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
index a4d60bbcf6..25753b06ca 100644
--- a/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
+++ b/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
@@ -4,8 +4,7 @@ import Big from 'big.js';
import { ChangeEventHandler, Key, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
-import { toast } from 'react-toastify';
-import { useDebounce } from 'react-use';
+import { useDebounce, useInterval } from 'react-use';
import { StoreType } from '@/common/types/util.types';
import { convertMonetaryAmountToValueInUSD, formatUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
@@ -21,6 +20,7 @@ import {
} from '@/lib/form';
import { SlippageManager } from '@/pages/AMM/shared/components';
import { SwapPair } from '@/types/swap';
+import { REFETCH_INTERVAL } from '@/utils/constants/api';
import { SWAP_PRICE_IMPACT_LIMIT } from '@/utils/constants/swap';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
@@ -113,31 +113,29 @@ const SwapForm = ({
const { data: currencies } = useGetCurrencies(bridgeLoaded);
const transaction = useTransaction(Transaction.AMM_SWAP, {
- onSuccess: () => {
- toast.success('Swap successful');
- setTrade(undefined);
+ onSigning: () => {
setInputAmount(undefined);
- onSwap();
+ form.setFieldValue(SWAP_INPUT_AMOUNT_FIELD, '', true);
+ setTrade(undefined);
},
- onError: (err) => {
- toast.error(err.message);
- }
+ onSuccess: onSwap
});
- useDebounce(
- () => {
- if (!pair.input || !pair.output || !inputAmount) {
- return setTrade(undefined);
- }
+ const handleChangeTrade = () => {
+ if (!pair.input || !pair.output || !inputAmount) {
+ return setTrade(undefined);
+ }
+
+ const inputMonetaryAmount = newMonetaryAmount(inputAmount, pair.input, true);
+ const trade = window.bridge.amm.getOptimalTrade(inputMonetaryAmount, pair.output, liquidityPools);
- const inputMonetaryAmount = newMonetaryAmount(inputAmount, pair.input, true);
- const trade = window.bridge.amm.getOptimalTrade(inputMonetaryAmount, pair.output, liquidityPools);
+ setTrade(trade);
+ };
- setTrade(trade);
- },
- 500,
- [inputAmount, pair]
- );
+ // attemp to update trade object on each new block
+ useInterval(handleChangeTrade, REFETCH_INTERVAL.BLOCK);
+
+ useDebounce(handleChangeTrade, 500, [inputAmount, pair]);
const inputBalance = pair.input && getAvailableBalance(pair.input.ticker);
const outputBalance = pair.output && getAvailableBalance(pair.output.ticker);
@@ -157,12 +155,11 @@ const SwapForm = ({
try {
const minimumAmountOut = trade.getMinimumOutputAmount(slippage);
-
const deadline = await window.bridge.system.getFutureBlockNumber(30 * 60);
return transaction.execute(trade, minimumAmountOut, accountId, deadline);
- } catch (err: any) {
- toast.error(err.toString());
+ } catch (error: any) {
+ transaction.reject(error);
}
};
@@ -193,7 +190,6 @@ const SwapForm = ({
initialValues,
validationSchema: swapSchema({ [SWAP_INPUT_AMOUNT_FIELD]: inputSchemaParams }),
onSubmit: handleSubmit,
- disableValidation: transaction.isLoading,
validateOnMount: true
});
@@ -216,16 +212,6 @@ const SwapForm = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pair]);
- // MEMO: amount field cleaned up after successful swap
- useEffect(() => {
- const isAmountFieldEmpty = form.values[SWAP_INPUT_AMOUNT_FIELD] === '';
-
- if (isAmountFieldEmpty || !transaction.isSuccess) return;
-
- form.setFieldValue(SWAP_INPUT_AMOUNT_FIELD, '');
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [transaction.isSuccess]);
-
const handleChangeInput: ChangeEventHandler = (e) => {
setInputAmount(e.target.value);
setTrade(undefined);
@@ -322,7 +308,7 @@ const SwapForm = ({
/>
{trade && }
-
+
diff --git a/src/pages/Bridge/BurnForm/index.tsx b/src/pages/Bridge/BurnForm/index.tsx
index 2063218a57..4699f19aa7 100644
--- a/src/pages/Bridge/BurnForm/index.tsx
+++ b/src/pages/Bridge/BurnForm/index.tsx
@@ -6,7 +6,6 @@ import { useErrorHandler, withErrorBoundary } from 'react-error-boundary';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
-import { toast } from 'react-toastify';
import { ParachainStatus, StoreType } from '@/common/types/util.types';
import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils';
@@ -15,7 +14,6 @@ import { AuthCTA } from '@/components';
import { WRAPPED_TOKEN, WRAPPED_TOKEN_SYMBOL, WrappedTokenLogoIcon } from '@/config/relay-chains';
import { BALANCE_MAX_INTEGER_LENGTH } from '@/constants';
import ErrorFallback from '@/legacy-components/ErrorFallback';
-import ErrorModal from '@/legacy-components/ErrorModal';
import FormTitle from '@/legacy-components/FormTitle';
import Hr2 from '@/legacy-components/hrs/Hr2';
import PriceInfo from '@/legacy-components/PriceInfo';
@@ -70,10 +68,12 @@ const BurnForm = (): JSX.Element | null => {
const [burnableCollateral, setBurnableCollateral] = React.useState();
const [selectedCollateral, setSelectedCollateral] = React.useState();
- const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
- const [submitError, setSubmitError] = React.useState(null);
-
- const transaction = useTransaction(Transaction.REDEEM_BURN);
+ const transaction = useTransaction(Transaction.REDEEM_BURN, {
+ onSuccess: () =>
+ reset({
+ [WRAPPED_TOKEN_AMOUNT]: ''
+ })
+ });
const handleUpdateCollateral = (collateral: TokenOption) => {
const selectedCollateral = burnableCollateral?.find(
@@ -128,18 +128,6 @@ const BurnForm = (): JSX.Element | null => {
})();
}, [bridgeLoaded, collateralCurrencies, handleError]);
- // This ensures that triggering the notification and clearing
- // the form happen at the same time.
- React.useEffect(() => {
- if (submitStatus !== STATUSES.RESOLVED) return;
-
- toast.success(t('burn_page.successfully_burned'));
-
- reset({
- [WRAPPED_TOKEN_AMOUNT]: ''
- });
- }, [submitStatus, reset, t]);
-
if (status === STATUSES.IDLE || status === STATUSES.PENDING) {
return ;
}
@@ -149,18 +137,8 @@ const BurnForm = (): JSX.Element | null => {
throw new Error('Something went wrong!');
}
- const onSubmit = async (data: BurnFormData) => {
- try {
- setSubmitStatus(STATUSES.PENDING);
-
- await transaction.executeAsync(new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT]), selectedCollateral.currency);
-
- setSubmitStatus(STATUSES.RESOLVED);
- } catch (error) {
- setSubmitStatus(STATUSES.REJECTED);
- setSubmitError(error);
- }
- };
+ const onSubmit = async (data: BurnFormData) =>
+ transaction.execute(new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT]), selectedCollateral.currency);
const validateForm = (value: string): string | undefined => {
// TODO: should use wrapped token amount type (e.g. InterBtcAmount or KBtcAmount)
@@ -305,23 +283,12 @@ const BurnForm = (): JSX.Element | null => {
fullWidth
size='large'
type='submit'
- loading={submitStatus === STATUSES.PENDING}
+ loading={transaction.isLoading}
disabled={parachainStatus === ParachainStatus.Loading || parachainStatus === ParachainStatus.Shutdown}
>
{t('burn')}
- {submitStatus === STATUSES.REJECTED && submitError && (
- {
- setSubmitStatus(STATUSES.IDLE);
- setSubmitError(null);
- }}
- title='Error'
- description={typeof submitError === 'string' ? submitError : submitError.message}
- />
- )}
>
);
}
diff --git a/src/pages/Bridge/IssueForm/SubmittedIssueRequestModal/index.tsx b/src/pages/Bridge/IssueForm/SubmittedIssueRequestModal/index.tsx
index b8569c7ee6..e5afdc0146 100644
--- a/src/pages/Bridge/IssueForm/SubmittedIssueRequestModal/index.tsx
+++ b/src/pages/Bridge/IssueForm/SubmittedIssueRequestModal/index.tsx
@@ -1,12 +1,11 @@
import { Issue } from '@interlay/interbtc-api';
import clsx from 'clsx';
-import * as React from 'react';
import { useTranslation } from 'react-i18next';
-import CloseIconButton from '@/legacy-components/buttons/CloseIconButton';
+import { Modal, ModalBody, ModalFooter } from '@/component-library';
import InterlayDefaultContainedButton from '@/legacy-components/buttons/InterlayDefaultContainedButton';
import BTCPaymentPendingStatusUI from '@/legacy-components/IssueUI/BTCPaymentPendingStatusUI';
-import InterlayModal, { InterlayModalInnerWrapper, Props as ModalProps } from '@/legacy-components/UI/InterlayModal';
+import { Props as ModalProps } from '@/legacy-components/UI/InterlayModal';
import InterlayRouterLink from '@/legacy-components/UI/InterlayRouterLink';
import { PAGES, QUERY_PARAMETERS } from '@/utils/constants/links';
import { getColorShade } from '@/utils/helpers/colors';
@@ -24,32 +23,31 @@ const SubmittedIssueRequestModal = ({
}: CustomProps & Omit): JSX.Element => {
const { t } = useTranslation();
- const focusRef = React.useRef(null);
-
return (
-
-
-
+
+
{t('issue_page.deposit')}
-
-
- {t('issue_page.i_have_made_the_payment')}
-
-
-
-
+
+
+
+
+ {t('issue_page.i_have_made_the_payment')}
+
+ {' '}
+
+
);
};
diff --git a/src/pages/Bridge/IssueForm/index.tsx b/src/pages/Bridge/IssueForm/index.tsx
index 2e3b83db45..fbffaaae14 100644
--- a/src/pages/Bridge/IssueForm/index.tsx
+++ b/src/pages/Bridge/IssueForm/index.tsx
@@ -42,7 +42,6 @@ import {
} from '@/config/relay-chains';
import AvailableBalanceUI from '@/legacy-components/AvailableBalanceUI';
import ErrorFallback from '@/legacy-components/ErrorFallback';
-import ErrorModal from '@/legacy-components/ErrorModal';
import FormTitle from '@/legacy-components/FormTitle';
import Hr2 from '@/legacy-components/hrs/Hr2';
import PriceInfo from '@/legacy-components/PriceInfo';
@@ -126,7 +125,6 @@ const IssueForm = (): JSX.Element | null => {
);
const [dustValue, setDustValue] = React.useState(new BitcoinAmount(DEFAULT_ISSUE_DUST_AMOUNT));
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
- const [submitError, setSubmitError] = React.useState(null);
const [submittedRequest, setSubmittedRequest] = React.useState();
const [selectVaultManually, setSelectVaultManually] = React.useState(false);
const [selectedVault, setSelectedVault] = React.useState();
@@ -142,7 +140,7 @@ const IssueForm = (): JSX.Element | null => {
});
useErrorHandler(requestLimitsError);
- const transaction = useTransaction(Transaction.ISSUE_REQUEST);
+ const transaction = useTransaction(Transaction.ISSUE_REQUEST, { showSuccessModal: false });
React.useEffect(() => {
if (!bridgeLoaded) return;
@@ -303,43 +301,38 @@ const IssueForm = (): JSX.Element | null => {
};
const onSubmit = async (data: IssueFormData) => {
- try {
- setSubmitStatus(STATUSES.PENDING);
- await requestLimitsRefetch();
- await trigger(BTC_AMOUNT);
-
- const monetaryBtcAmount = new BitcoinAmount(data[BTC_AMOUNT] || '0');
- const vaults = await window.bridge.vaults.getVaultsWithIssuableTokens();
-
- let vaultId: InterbtcPrimitivesVaultId;
- if (selectVaultManually) {
- if (!selectedVault) {
- throw new Error('Specific vault is not selected!');
- }
- vaultId = selectedVault[0];
- } else {
- vaultId = getRandomVaultIdWithCapacity(Array.from(vaults), monetaryBtcAmount);
- }
+ setSubmitStatus(STATUSES.PENDING);
+ await requestLimitsRefetch();
+ await trigger(BTC_AMOUNT);
- const collateralToken = await currencyIdToMonetaryCurrency(window.bridge.api, vaultId.currencies.collateral);
-
- const result = await transaction.executeAsync(
- monetaryBtcAmount,
- vaultId.accountId,
- collateralToken,
- false, // default
- vaults
- );
- const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, result);
-
- // TODO: handle issue aggregation
- const issueRequest = issueRequests[0];
- handleSubmittedRequestModalOpen(issueRequest);
- setSubmitStatus(STATUSES.RESOLVED);
- } catch (error) {
- setSubmitStatus(STATUSES.REJECTED);
- setSubmitError(error);
+ const monetaryBtcAmount = new BitcoinAmount(data[BTC_AMOUNT] || '0');
+ const vaults = await window.bridge.vaults.getVaultsWithIssuableTokens();
+
+ let vaultId: InterbtcPrimitivesVaultId;
+ if (selectVaultManually) {
+ if (!selectedVault) {
+ throw new Error('Specific vault is not selected!');
+ }
+ vaultId = selectedVault[0];
+ } else {
+ vaultId = getRandomVaultIdWithCapacity(Array.from(vaults), monetaryBtcAmount);
}
+
+ const collateralToken = await currencyIdToMonetaryCurrency(window.bridge.api, vaultId.currencies.collateral);
+
+ const result = await transaction.executeAsync(
+ monetaryBtcAmount,
+ vaultId.accountId,
+ collateralToken,
+ false, // default
+ vaults
+ );
+ const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, result.data);
+
+ // TODO: handle issue aggregation
+ const issueRequest = issueRequests[0];
+ handleSubmittedRequestModalOpen(issueRequest);
+ setSubmitStatus(STATUSES.RESOLVED);
};
const monetaryBtcAmount = new BitcoinAmount(btcAmount);
@@ -536,17 +529,6 @@ const IssueForm = (): JSX.Element | null => {
{t('confirm')}
- {submitStatus === STATUSES.REJECTED && submitError && (
- {
- setSubmitStatus(STATUSES.IDLE);
- setSubmitError(null);
- }}
- title='Error'
- description={typeof submitError === 'string' ? submitError : submitError.message}
- />
- )}
{submittedRequest && (
-
-
+
+
{t('redeem_page.redeem')}
@@ -114,8 +110,8 @@ const SubmittedRedeemRequestModal = ({
-
-
+
+
);
};
diff --git a/src/pages/Bridge/RedeemForm/index.tsx b/src/pages/Bridge/RedeemForm/index.tsx
index 357bfcf540..f934ae0a99 100644
--- a/src/pages/Bridge/RedeemForm/index.tsx
+++ b/src/pages/Bridge/RedeemForm/index.tsx
@@ -35,7 +35,6 @@ import {
import { BALANCE_MAX_INTEGER_LENGTH, BTC_ADDRESS_REGEX } from '@/constants';
import AvailableBalanceUI from '@/legacy-components/AvailableBalanceUI';
import ErrorFallback from '@/legacy-components/ErrorFallback';
-import ErrorModal from '@/legacy-components/ErrorModal';
import FormTitle from '@/legacy-components/FormTitle';
import Hr2 from '@/legacy-components/hrs/Hr2';
import PriceInfo from '@/legacy-components/PriceInfo';
@@ -112,14 +111,13 @@ const RedeemForm = (): JSX.Element | null => {
const [premiumRedeemFee, setPremiumRedeemFee] = React.useState(new Big(0));
const [currentInclusionFee, setCurrentInclusionFee] = React.useState(BitcoinAmount.zero());
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
- const [submitError, setSubmitError] = React.useState(null);
const [submittedRequest, setSubmittedRequest] = React.useState();
const [selectVaultManually, setSelectVaultManually] = React.useState(false);
const [selectedVault, setSelectedVault] = React.useState();
- const transaction = useTransaction(Transaction.REDEEM_REQUEST);
+ const transaction = useTransaction(Transaction.REDEEM_REQUEST, { showSuccessModal: false });
React.useEffect(() => {
if (!monetaryWrappedTokenAmount) return;
@@ -305,7 +303,7 @@ const RedeemForm = (): JSX.Element | null => {
const result = await transaction.executeAsync(monetaryWrappedTokenAmount, data[BTC_ADDRESS], vaultId);
- const redeemRequests = await getRedeemRequestsFromExtrinsicResult(window.bridge, result);
+ const redeemRequests = await getRedeemRequestsFromExtrinsicResult(window.bridge, result.data);
// TODO: handle redeem aggregator
const redeemRequest = redeemRequests[0];
@@ -313,7 +311,6 @@ const RedeemForm = (): JSX.Element | null => {
setSubmitStatus(STATUSES.RESOLVED);
} catch (error) {
setSubmitStatus(STATUSES.REJECTED);
- setSubmitError(error);
}
};
@@ -533,17 +530,6 @@ const RedeemForm = (): JSX.Element | null => {
{t('confirm')}
- {submitStatus === STATUSES.REJECTED && submitError && (
- {
- setSubmitStatus(STATUSES.IDLE);
- setSubmitError(null);
- }}
- title='Error'
- description={typeof submitError === 'string' ? submitError : submitError.message}
- />
- )}
{submittedRequest && (
Object.values(assets).map(({ borrowApy, currency, availableCapacity, totalBorrows, borrowReward }) => {
const asset = ;
- const accruedRewards = subsidyRewards ? subsidyRewards.perMarket[currency.ticker].borrow : null;
const apy = (
{
- toast.success('Successfully toggled collateral');
- onClose?.();
- refetch();
- }
+ onSigning: onClose,
+ onSuccess: refetch
});
if (!asset || !position) {
@@ -94,31 +89,21 @@ const CollateralModal = ({ asset, position, onClose, ...props }: CollateralModal
};
return (
- <>
-
- {content.title}
-
-
- {content.description}
-
- {variant !== 'disable-error' && }
-
-
-
-
- {content.buttonLabel}
-
-
-
- {transaction.isError && (
- transaction.reset()}
- title='Error'
- description={transaction.error?.message || ''}
- />
- )}
- >
+
+ {content.title}
+
+
+ {content.description}
+
+ {variant !== 'disable-error' && }
+
+
+
+
+ {content.buttonLabel}
+
+
+
);
};
diff --git a/src/pages/Loans/LoansOverview/components/LendAssetsTable/LendAssetsTable.tsx b/src/pages/Loans/LoansOverview/components/LendAssetsTable/LendAssetsTable.tsx
index ab5e125316..ea399226b5 100644
--- a/src/pages/Loans/LoansOverview/components/LendAssetsTable/LendAssetsTable.tsx
+++ b/src/pages/Loans/LoansOverview/components/LendAssetsTable/LendAssetsTable.tsx
@@ -8,7 +8,6 @@ import { AssetCell, BalanceCell, Cell, Table, TableProps } from '@/components';
import { ApyCell } from '@/components/LoanPositionsTable/ApyCell';
import { LoanTablePlaceholder } from '@/components/LoanPositionsTable/LoanTablePlaceholder';
import { getTokenPrice } from '@/utils/helpers/prices';
-import { useGetAccountSubsidyRewards } from '@/utils/hooks/api/loans/use-get-account-subsidy-rewards';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
@@ -48,20 +47,17 @@ const LendAssetsTable = ({ assets, onRowAction, ...props }: LendAssetsTableProps
const { t } = useTranslation();
const prices = useGetPrices();
const { data: balances } = useGetBalances();
- const { data: subsidyRewards } = useGetAccountSubsidyRewards();
const rows: LendAssetsTableRow[] = useMemo(
() =>
Object.values(assets).map(({ lendApy, currency, totalLiquidity, lendReward }) => {
const asset = ;
- const accruedRewards = subsidyRewards ? subsidyRewards.perMarket[currency.ticker].lend : null;
const apy = (
onRowAction?.(currency.ticker as Key)}
@@ -87,7 +83,7 @@ const LendAssetsTable = ({ assets, onRowAction, ...props }: LendAssetsTableProps
totalSupply
};
}),
- [assets, balances, onRowAction, prices, subsidyRewards]
+ [assets, balances, onRowAction, prices]
);
return (
diff --git a/src/pages/Loans/LoansOverview/components/LoanActionInfo/RewardsGroup.tsx b/src/pages/Loans/LoansOverview/components/LoanActionInfo/RewardsGroup.tsx
index 29164ddbc2..6dd8c5372b 100644
--- a/src/pages/Loans/LoansOverview/components/LoanActionInfo/RewardsGroup.tsx
+++ b/src/pages/Loans/LoansOverview/components/LoanActionInfo/RewardsGroup.tsx
@@ -26,7 +26,7 @@ const RewardsGroup = ({ isBorrow, apy, assetCurrency, rewards, prices }: Rewards
return (
<>
- Rewards APY {rewards.currency.ticker}
+ Rewards APR {rewards.currency.ticker}
{getApyLabel(subsidyRewardApy)}
diff --git a/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx b/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
index edc7763901..390be8cba5 100644
--- a/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
+++ b/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
@@ -3,7 +3,6 @@ import { MonetaryAmount } from '@interlay/monetary-js';
import { mergeProps } from '@react-aria/utils';
import { ChangeEventHandler, useState } from 'react';
import { TFunction, useTranslation } from 'react-i18next';
-import { toast } from 'react-toastify';
import { useDebounce } from 'react-use';
import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
@@ -116,42 +115,29 @@ const LoanForm = ({ asset, variant, position, onChangeLoan }: LoanFormProps): JS
[inputAmount]
);
- const transaction = useTransaction({
- onSuccess: () => {
- toast.success(`Successful ${content.title.toLowerCase()}`);
- onChangeLoan?.();
- refetch();
- },
- onError: (error: Error) => {
- toast.error(error.message);
- }
- });
+ const transaction = useTransaction({ onSigning: onChangeLoan, onSuccess: refetch });
const handleSubmit = (data: LoanFormData) => {
- try {
- const amount = data[variant] || 0;
- const monetaryAmount = newMonetaryAmount(amount, asset.currency, true);
-
- switch (variant) {
- case 'lend':
- return transaction.execute(Transaction.LOANS_LEND, monetaryAmount.currency, monetaryAmount);
- case 'withdraw':
- if (isMaxAmount) {
- return transaction.execute(Transaction.LOANS_WITHDRAW_ALL, monetaryAmount.currency);
- } else {
- return transaction.execute(Transaction.LOANS_WITHDRAW, monetaryAmount.currency, monetaryAmount);
- }
- case 'borrow':
- return transaction.execute(Transaction.LOANS_BORROW, monetaryAmount.currency, monetaryAmount);
- case 'repay':
- if (isMaxAmount) {
- return transaction.execute(Transaction.LOANS_REPAY_ALL, monetaryAmount.currency);
- } else {
- return transaction.execute(Transaction.LOANS_REPAY, monetaryAmount.currency, monetaryAmount);
- }
- }
- } catch (err: any) {
- toast.error(err.toString());
+ const amount = data[variant] || 0;
+ const monetaryAmount = newMonetaryAmount(amount, asset.currency, true);
+
+ switch (variant) {
+ case 'lend':
+ return transaction.execute(Transaction.LOANS_LEND, monetaryAmount.currency, monetaryAmount);
+ case 'withdraw':
+ if (isMaxAmount) {
+ return transaction.execute(Transaction.LOANS_WITHDRAW_ALL, monetaryAmount.currency);
+ } else {
+ return transaction.execute(Transaction.LOANS_WITHDRAW, monetaryAmount.currency, monetaryAmount);
+ }
+ case 'borrow':
+ return transaction.execute(Transaction.LOANS_BORROW, monetaryAmount.currency, monetaryAmount);
+ case 'repay':
+ if (isMaxAmount) {
+ return transaction.execute(Transaction.LOANS_REPAY_ALL, monetaryAmount.currency);
+ } else {
+ return transaction.execute(Transaction.LOANS_REPAY, monetaryAmount.currency, monetaryAmount);
+ }
}
};
@@ -216,7 +202,7 @@ const LoanForm = ({ asset, variant, position, onChangeLoan }: LoanFormProps): JS
-
+
{content.title}
diff --git a/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx b/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
index 6ad85f8d82..2ef53674f4 100644
--- a/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
+++ b/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
@@ -1,10 +1,6 @@
-import { useTranslation } from 'react-i18next';
-import { toast } from 'react-toastify';
-
import { formatNumber, formatPercentage, formatUSD } from '@/common/utils/utils';
import { Card, Dl, DlGroup } from '@/component-library';
import { AuthCTA } from '@/components';
-import ErrorModal from '@/legacy-components/ErrorModal';
import { AccountLendingStatistics } from '@/utils/hooks/api/loans/use-get-account-lending-statistics';
import { useGetAccountSubsidyRewards } from '@/utils/hooks/api/loans/use-get-account-subsidy-rewards';
import { Transaction, useTransaction } from '@/utils/hooks/transaction';
@@ -16,14 +12,10 @@ type LoansInsightsProps = {
};
const LoansInsights = ({ statistics }: LoansInsightsProps): JSX.Element => {
- const { t } = useTranslation();
const { data: subsidyRewards, refetch } = useGetAccountSubsidyRewards();
const transaction = useTransaction(Transaction.LOANS_CLAIM_REWARDS, {
- onSuccess: () => {
- toast.success(t('successfully_claimed_rewards'));
- refetch();
- }
+ onSuccess: refetch
});
const handleClickClaimRewards = () => transaction.execute();
@@ -76,14 +68,6 @@ const LoansInsights = ({ statistics }: LoansInsightsProps): JSX.Element => {
)}
- {transaction.isError && (
- transaction.reset()}
- title='Error'
- description={transaction.error?.message || ''}
- />
- )}
>
);
};
diff --git a/src/pages/Staking/ClaimRewardsButton/index.tsx b/src/pages/Staking/ClaimRewardsButton/index.tsx
index 442da162c0..e7ab257735 100644
--- a/src/pages/Staking/ClaimRewardsButton/index.tsx
+++ b/src/pages/Staking/ClaimRewardsButton/index.tsx
@@ -5,7 +5,6 @@ import { GOVERNANCE_TOKEN_SYMBOL } from '@/config/relay-chains';
import InterlayDenimOrKintsugiSupernovaContainedButton, {
Props as InterlayDenimOrKintsugiMidnightContainedButtonProps
} from '@/legacy-components/buttons/InterlayDenimOrKintsugiSupernovaContainedButton';
-import ErrorModal from '@/legacy-components/ErrorModal';
import { useSubstrateSecureState } from '@/lib/substrate';
import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
import { Transaction, useTransaction } from '@/utils/hooks/transaction';
@@ -35,26 +34,14 @@ const ClaimRewardsButton = ({
};
return (
- <>
-
- Claim {claimableRewardAmount} {GOVERNANCE_TOKEN_SYMBOL} Rewards
-
- {transaction.isError && (
- {
- transaction.reset();
- }}
- title='Error'
- description={transaction.error?.message || ''}
- />
- )}
- >
+
+ Claim {claimableRewardAmount} {GOVERNANCE_TOKEN_SYMBOL} Rewards
+
);
};
diff --git a/src/pages/Staking/WithdrawButton/index.tsx b/src/pages/Staking/WithdrawButton/index.tsx
index 7093017a52..190d2a628c 100644
--- a/src/pages/Staking/WithdrawButton/index.tsx
+++ b/src/pages/Staking/WithdrawButton/index.tsx
@@ -1,19 +1,17 @@
-import { ISubmittableResult } from '@polkadot/types/types';
import clsx from 'clsx';
import { add, format } from 'date-fns';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { BLOCK_TIME } from '@/config/parachain';
import { GOVERNANCE_TOKEN_SYMBOL } from '@/config/relay-chains';
import InterlayDenimOrKintsugiSupernovaContainedButton, {
Props as InterlayDenimOrKintsugiMidnightContainedButtonProps
} from '@/legacy-components/buttons/InterlayDenimOrKintsugiSupernovaContainedButton';
-import ErrorModal from '@/legacy-components/ErrorModal';
import InformationTooltip from '@/legacy-components/tooltips/InformationTooltip';
import { useSubstrateSecureState } from '@/lib/substrate';
import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
import { YEAR_MONTH_DAY_PATTERN } from '@/utils/constants/date-time';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const getFormattedUnlockDate = (remainingBlockNumbersToUnstake: number, formatPattern: string) => {
const unlockDate = add(new Date(), {
@@ -36,22 +34,15 @@ const WithdrawButton = ({
}: CustomProps & InterlayDenimOrKintsugiMidnightContainedButtonProps): JSX.Element => {
const { selectedAccount } = useSubstrateSecureState();
- const queryClient = useQueryClient();
-
- const withdrawMutation = useMutation(
- () => {
- return submitExtrinsic(window.bridge.escrow.withdraw());
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getStakedBalance', selectedAccount?.address]);
- }
+ const transaction = useTransaction(Transaction.ESCROW_WITHDRAW, {
+ onSuccess: () => {
+ queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getStakedBalance', selectedAccount?.address]);
}
- );
+ });
- const handleUnstake = () => {
- withdrawMutation.mutate();
- };
+ const queryClient = useQueryClient();
+
+ const handleUnstake = () => transaction.execute();
const disabled = remainingBlockNumbersToUnstake ? remainingBlockNumbersToUnstake > 0 : false;
@@ -79,22 +70,12 @@ const WithdrawButton = ({
/>
}
onClick={handleUnstake}
- pending={withdrawMutation.isLoading}
+ pending={transaction.isLoading}
disabled={disabled}
{...rest}
>
Withdraw Staked {GOVERNANCE_TOKEN_SYMBOL} {renderUnlockDateLabel()}
- {withdrawMutation.isError && (
- {
- withdrawMutation.reset();
- }}
- title='Error'
- description={withdrawMutation.error?.message || ''}
- />
- )}
>
);
};
diff --git a/src/pages/Staking/index.tsx b/src/pages/Staking/index.tsx
index 043d6b1185..df2f0b697f 100644
--- a/src/pages/Staking/index.tsx
+++ b/src/pages/Staking/index.tsx
@@ -29,7 +29,6 @@ import {
} from '@/config/relay-chains';
import AvailableBalanceUI from '@/legacy-components/AvailableBalanceUI';
import ErrorFallback from '@/legacy-components/ErrorFallback';
-import ErrorModal from '@/legacy-components/ErrorModal';
import Panel from '@/legacy-components/Panel';
import TitleWithUnderline from '@/legacy-components/TitleWithUnderline';
import TokenField from '@/legacy-components/TokenField';
@@ -837,17 +836,6 @@ const Staking = (): JSX.Element => {
- {(initialStakeTransaction.isError || existingStakeTransaction.isError) && (
- {
- initialStakeTransaction.reset();
- existingStakeTransaction.reset();
- }}
- title='Error'
- description={initialStakeTransaction.error?.message || existingStakeTransaction.error?.message || ''}
- />
- )}
>
);
};
diff --git a/src/pages/Transactions/IssueRequestsTable/IssueRequestModal/index.tsx b/src/pages/Transactions/IssueRequestsTable/IssueRequestModal/index.tsx
index c1457d5a8d..765659e2d9 100644
--- a/src/pages/Transactions/IssueRequestsTable/IssueRequestModal/index.tsx
+++ b/src/pages/Transactions/IssueRequestsTable/IssueRequestModal/index.tsx
@@ -1,13 +1,8 @@
-import clsx from 'clsx';
-import * as React from 'react';
import { useTranslation } from 'react-i18next';
-import CloseIconButton from '@/legacy-components/buttons/CloseIconButton';
-import Hr1 from '@/legacy-components/hrs/Hr1';
+import { Modal, ModalBody, ModalHeader } from '@/component-library';
import IssueUI from '@/legacy-components/IssueUI';
-import InterlayModal, { InterlayModalInnerWrapper, Props as ModalProps } from '@/legacy-components/UI/InterlayModal';
-
-import RequestModalTitle from '../../RequestModalTitle';
+import { Props as ModalProps } from '@/legacy-components/UI/InterlayModal';
interface CustomProps {
request: any; // TODO: should type properly (`Relay`)
@@ -16,17 +11,13 @@ interface CustomProps {
const IssueRequestModal = ({ open, onClose, request }: CustomProps & Omit): JSX.Element => {
const { t } = useTranslation();
- const focusRef = React.useRef(null);
-
return (
-
-
- {t('issue_page.request', { id: request.id })}
-
-
+
+ {t('issue_page.request', { id: request.id })}
+
-
-
+
+
);
};
diff --git a/src/pages/Transactions/RedeemRequestsTable/RedeemRequestModal/index.tsx b/src/pages/Transactions/RedeemRequestsTable/RedeemRequestModal/index.tsx
index ccc3fba223..fee187b468 100644
--- a/src/pages/Transactions/RedeemRequestsTable/RedeemRequestModal/index.tsx
+++ b/src/pages/Transactions/RedeemRequestsTable/RedeemRequestModal/index.tsx
@@ -1,13 +1,8 @@
-import clsx from 'clsx';
-import * as React from 'react';
import { useTranslation } from 'react-i18next';
-import CloseIconButton from '@/legacy-components/buttons/CloseIconButton';
-import Hr1 from '@/legacy-components/hrs/Hr1';
+import { Modal, ModalBody, ModalHeader } from '@/component-library';
import RedeemUI from '@/legacy-components/RedeemUI';
-import InterlayModal, { InterlayModalInnerWrapper, Props as ModalProps } from '@/legacy-components/UI/InterlayModal';
-
-import RequestModalTitle from '../../RequestModalTitle';
+import { Props as ModalProps } from '@/legacy-components/UI/InterlayModal';
interface CustomProps {
// TODO: should type properly (`Relay`)
@@ -21,17 +16,13 @@ const RedeemRequestModal = ({
}: CustomProps & Omit): JSX.Element | null => {
const { t } = useTranslation();
- const focusRef = React.useRef(null);
-
return (
-
-
- {t('issue_page.request', { id: request.id })}
-
-
+
+ {t('issue_page.request', { id: request.id })}
+
-
-
+
+
);
};
diff --git a/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
index 1e7a864185..fbd54a3cd8 100644
--- a/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
+++ b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
@@ -1,12 +1,9 @@
-import { FixedPointNumber } from '@acala-network/sdk-core';
-import { ChainName, CrossChainTransferParams } from '@interlay/bridge';
+import { ChainName } from '@interlay/bridge';
import { newMonetaryAmount } from '@interlay/interbtc-api';
import { web3FromAddress } from '@polkadot/extension-dapp';
import { mergeProps } from '@react-aria/utils';
import { ChangeEventHandler, Key, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
-import { toast } from 'react-toastify';
import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
import { Dd, DlGroup, Dt, Flex, LoadingSpinner, TokenInput } from '@/component-library';
@@ -25,11 +22,11 @@ import {
} from '@/lib/form';
import { useSubstrateSecureState } from '@/lib/substrate';
import { Chains } from '@/types/chains';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetCurrencies } from '@/utils/hooks/api/use-get-currencies';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
import { useXCMBridge, XCMTokenData } from '@/utils/hooks/api/xcm/use-xcm-bridge';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { ChainSelect } from './components';
@@ -65,37 +62,36 @@ const CrossChainTransferForm = (): JSX.Element => {
}
};
- const mutateXcmTransfer = async (formData: CrossChainTransferFormData) => {
+ const transaction = useTransaction(Transaction.XCM_TRANSFER, {
+ onSuccess: () => {
+ setTokenData(form.values[CROSS_CHAIN_TRANSFER_TO_FIELD] as ChainName);
+ form.setFieldValue(CROSS_CHAIN_TRANSFER_AMOUNT_FIELD, '');
+ }
+ });
+
+ const handleSubmit = async (formData: CrossChainTransferFormData) => {
if (!data || !formData || !currentToken) return;
- const { signer } = await web3FromAddress(formData[CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD] as string);
+ const address = formData[CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD] as string;
+
+ const { signer } = await web3FromAddress(address);
const adapter = data.bridge.findAdapter(formData[CROSS_CHAIN_TRANSFER_FROM_FIELD] as ChainName);
const apiPromise = data.provider.getApiPromise(formData[CROSS_CHAIN_TRANSFER_FROM_FIELD] as string);
apiPromise.setSigner(signer);
adapter.setApi(apiPromise);
+ const transferCurrency = getCurrencyFromTicker(currentToken.value);
const transferAmount = newMonetaryAmount(
form.values[CROSS_CHAIN_TRANSFER_AMOUNT_FIELD] || 0,
- getCurrencyFromTicker(currentToken.value),
+ transferCurrency,
true
);
- const transferAmountString = transferAmount.toString(true);
- const transferAmountDecimals = transferAmount.currency.decimals;
-
- const tx = adapter.createTx({
- amount: FixedPointNumber.fromInner(transferAmountString, transferAmountDecimals),
- to: formData[CROSS_CHAIN_TRANSFER_TO_FIELD],
- token: formData[CROSS_CHAIN_TRANSFER_TOKEN_FIELD],
- address: formData[CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD]
- } as CrossChainTransferParams);
-
- await submitExtrinsic({ extrinsic: tx });
- };
+ const fromChain = formData[CROSS_CHAIN_TRANSFER_FROM_FIELD] as ChainName;
+ const toChain = formData[CROSS_CHAIN_TRANSFER_TO_FIELD] as ChainName;
- const handleSubmit = (formData: CrossChainTransferFormData) => {
- xcmTransferMutation.mutate(formData);
+ transaction.execute(adapter, fromChain, toChain, address, transferAmount);
};
const form = useForm({
@@ -108,18 +104,6 @@ const CrossChainTransferForm = (): JSX.Element => {
validationSchema: crossChainTransferSchema(schema, t)
});
- const xcmTransferMutation = useMutation(mutateXcmTransfer, {
- onSuccess: async () => {
- toast.success('Transfer successful');
-
- setTokenData(form.values[CROSS_CHAIN_TRANSFER_TO_FIELD] as ChainName);
- form.setFieldValue(CROSS_CHAIN_TRANSFER_AMOUNT_FIELD, '');
- },
- onError: (err) => {
- toast.error(err.message);
- }
- });
-
const handleOriginatingChainChange = (chain: ChainName, name: string) => {
form.setFieldValue(name, chain);
@@ -238,9 +222,7 @@ const CrossChainTransferForm = (): JSX.Element => {
onSelectionChange={(chain: Key) =>
handleOriginatingChainChange(chain as ChainName, CROSS_CHAIN_TRANSFER_FROM_FIELD)
}
- {...mergeProps(form.getFieldProps(CROSS_CHAIN_TRANSFER_FROM_FIELD, false), {
- onChange: handleOriginatingChainChange
- })}
+ {...mergeProps(form.getFieldProps(CROSS_CHAIN_TRANSFER_FROM_FIELD, false))}
/>
{
onSelectionChange={(chain: Key) =>
handleDestinationChainChange(chain as ChainName, CROSS_CHAIN_TRANSFER_TO_FIELD)
}
- {...mergeProps(form.getFieldProps(CROSS_CHAIN_TRANSFER_TO_FIELD, false), {
- onChange: handleDestinationChainChange
- })}
+ {...mergeProps(form.getFieldProps(CROSS_CHAIN_TRANSFER_TO_FIELD, false))}
/>
@@ -290,7 +270,7 @@ const CrossChainTransferForm = (): JSX.Element => {
{`${currentToken?.destFee.toString()} ${currentToken?.value}`}
-
+
{isCTADisabled ? 'Enter transfer amount' : t('transfer')}
diff --git a/src/pages/Transfer/TransferForm/index.tsx b/src/pages/Transfer/TransferForm/index.tsx
index a488cd288f..2bc9ed3f19 100644
--- a/src/pages/Transfer/TransferForm/index.tsx
+++ b/src/pages/Transfer/TransferForm/index.tsx
@@ -5,19 +5,16 @@ import { withErrorBoundary } from 'react-error-boundary';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
-import { toast } from 'react-toastify';
import { ParachainStatus, StoreType } from '@/common/types/util.types';
import { formatNumber } from '@/common/utils/utils';
import { AuthCTA } from '@/components';
import ErrorFallback from '@/legacy-components/ErrorFallback';
-import ErrorModal from '@/legacy-components/ErrorModal';
import FormTitle from '@/legacy-components/FormTitle';
import TextField from '@/legacy-components/TextField';
import Tokens, { TokenOption } from '@/legacy-components/Tokens';
import InterlayButtonBase from '@/legacy-components/UI/InterlayButtonBase';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
-import STATUSES from '@/utils/constants/statuses';
import isValidPolkadotAddress from '@/utils/helpers/is-valid-polkadot-address';
import { Transaction, useTransaction } from '@/utils/hooks/transaction';
@@ -47,28 +44,21 @@ const TransferForm = (): JSX.Element => {
});
const [activeToken, setActiveToken] = React.useState(undefined);
- const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
- const [submitError, setSubmitError] = React.useState(null);
- const transaction = useTransaction(Transaction.TOKENS_TRANSFER);
+ const transaction = useTransaction(Transaction.TOKENS_TRANSFER, {
+ onSigning: () => {
+ reset({
+ [TRANSFER_AMOUNT]: '',
+ [RECIPIENT_ADDRESS]: ''
+ });
+ }
+ });
const onSubmit = async (data: TransferFormData) => {
if (!activeToken) return;
if (data[TRANSFER_AMOUNT] === undefined) return;
- try {
- setSubmitStatus(STATUSES.PENDING);
-
- await transaction.executeAsync(
- data[RECIPIENT_ADDRESS],
- newMonetaryAmount(data[TRANSFER_AMOUNT], activeToken.token, true)
- );
-
- setSubmitStatus(STATUSES.RESOLVED);
- } catch (error) {
- setSubmitStatus(STATUSES.REJECTED);
- setSubmitError(error);
- }
+ transaction.execute(data[RECIPIENT_ADDRESS], newMonetaryAmount(data[TRANSFER_AMOUNT], activeToken.token, true));
};
const validateTransferAmount = React.useCallback(
@@ -96,19 +86,6 @@ const TransferForm = (): JSX.Element => {
const handleClickBalance = () => setValue(TRANSFER_AMOUNT, activeToken?.transferableBalance || '');
- // This ensures that triggering the notification and clearing
- // the form happen at the same time.
- React.useEffect(() => {
- if (submitStatus !== STATUSES.RESOLVED) return;
-
- toast.success(t('transfer_page.successfully_transferred'));
-
- reset({
- [TRANSFER_AMOUNT]: '',
- [RECIPIENT_ADDRESS]: ''
- });
- }, [submitStatus, reset, t]);
-
return (
<>
- {submitStatus === STATUSES.REJECTED && submitError && (
-
{
- setSubmitStatus(STATUSES.IDLE);
- setSubmitError(null);
- }}
- title='Error'
- description={typeof submitError === 'string' ? submitError : submitError.message}
- />
- )}
>
);
};
diff --git a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
index 4e9215ce82..4eec9acdd1 100644
--- a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
@@ -16,6 +16,7 @@ import { useSelector } from 'react-redux';
import { ReactComponent as BitcoinLogoIcon } from '@/assets/img/bitcoin-logo.svg';
import { ParachainStatus, StoreType } from '@/common/types/util.types';
import { displayMonetaryAmount, displayMonetaryAmountInUSDFormat } from '@/common/utils/utils';
+import { Modal, ModalBody, ModalHeader } from '@/component-library';
import {
BLOCKS_BEHIND_LIMIT,
DEFAULT_ISSUE_BRIDGE_FEE_RATE,
@@ -30,15 +31,12 @@ import {
WRAPPED_TOKEN_SYMBOL,
WrappedTokenLogoIcon
} from '@/config/relay-chains';
-import CloseIconButton from '@/legacy-components/buttons/CloseIconButton';
-import ErrorModal from '@/legacy-components/ErrorModal';
import Hr2 from '@/legacy-components/hrs/Hr2';
import PriceInfo from '@/legacy-components/PriceInfo';
import SubmitButton from '@/legacy-components/SubmitButton';
import TokenField from '@/legacy-components/TokenField';
import InformationTooltip from '@/legacy-components/tooltips/InformationTooltip';
import InterlayButtonBase from '@/legacy-components/UI/InterlayButtonBase';
-import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
import { useSubstrateSecureState } from '@/lib/substrate';
import SubmittedIssueRequestModal from '@/pages/Bridge/IssueForm/SubmittedIssueRequestModal';
import { ForeignAssetIdLiteral } from '@/types/currency';
@@ -90,12 +88,10 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
);
const [dustValue, setDustValue] = React.useState(new BitcoinAmount(DEFAULT_ISSUE_DUST_AMOUNT));
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
- const [submitError, setSubmitError] = React.useState(null);
const [submittedRequest, setSubmittedRequest] = React.useState();
const { t } = useTranslation();
const prices = useGetPrices();
- const focusRef = React.useRef(null);
const handleError = useErrorHandler();
@@ -108,7 +104,7 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
const vaultAccountId = useAccountId(vaultAddress);
- const transaction = useTransaction(Transaction.ISSUE_REQUEST);
+ const transaction = useTransaction(Transaction.ISSUE_REQUEST, { showSuccessModal: false });
React.useEffect(() => {
if (!bridgeLoaded) return;
@@ -174,31 +170,29 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
}
const onSubmit = async (data: RequestIssueFormData) => {
- try {
- setSubmitStatus(STATUSES.PENDING);
- await trigger(WRAPPED_TOKEN_AMOUNT);
+ setSubmitStatus(STATUSES.PENDING);
- const wrappedTokenAmount = new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT] || '0');
+ await trigger(WRAPPED_TOKEN_AMOUNT);
- const vaults = await window.bridge.vaults.getVaultsWithIssuableTokens();
+ const wrappedTokenAmount = new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT] || '0');
- const extrinsicResult = await transaction.executeAsync(
- wrappedTokenAmount,
- vaultAccountId,
- collateralToken,
- false, // default
- vaults
- );
+ const vaults = await window.bridge.vaults.getVaultsWithIssuableTokens();
- const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, extrinsicResult);
+ const result = await transaction.executeAsync(
+ wrappedTokenAmount,
+ vaultAccountId,
+ collateralToken,
+ false, // default
+ vaults
+ );
- // TODO: handle issue aggregation
- const issueRequest = issueRequests[0];
- handleSubmittedRequestModalOpen(issueRequest);
- } catch (error) {
- setSubmitStatus(STATUSES.REJECTED);
- }
+ const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, result.data);
+
+ // TODO: handle issue aggregation
+ const issueRequest = issueRequests[0];
+ handleSubmittedRequestModalOpen(issueRequest);
setSubmitStatus(STATUSES.RESOLVED);
+ onClose();
};
const validateForm = (value: string): string | undefined => {
@@ -267,12 +261,9 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
return (
<>
-
-
-
- {t('vault.request_issue')}
-
-
+
+ {t('vault.request_issue')}
+
-
-
- {submitStatus === STATUSES.REJECTED && submitError && (
- {
- setSubmitStatus(STATUSES.IDLE);
- setSubmitError(null);
- }}
- title='Error'
- description={typeof submitError === 'string' ? submitError : submitError.message}
- />
- )}
+
+
{submittedRequest && (
();
const [isRequestPending, setRequestPending] = React.useState(false);
const { t } = useTranslation();
- const focusRef = React.useRef(null);
const transaction = useTransaction(Transaction.REDEEM_REQUEST);
const onSubmit = handleSubmit(async (data) => {
setRequestPending(true);
+
try {
// Represents being less than 1 Satoshi
if (new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT])._rawAmount.lt(1)) {
@@ -67,12 +65,11 @@ const RequestRedeemModal = ({ onClose, open, collateralToken, vaultAddress, lock
queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
- toast.success('Redeem request submitted');
onClose();
- } catch (error) {
- toast.error(error.toString());
+ setRequestPending(false);
+ } catch (error: any) {
+ transaction.reject(error);
}
- setRequestPending(false);
});
const validateAmount = (value: string): string | undefined => {
@@ -89,12 +86,9 @@ const RequestRedeemModal = ({ onClose, open, collateralToken, vaultAddress, lock
};
return (
-
-
-
- {t('vault.request_redeem')}
-
-
+
+ {t('vault.request_redeem')}
+
-
-
+
+
);
};
diff --git a/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx b/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
index a92acc73b2..3001474981 100644
--- a/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
@@ -9,20 +9,18 @@ import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
-import { toast } from 'react-toastify';
import { StoreType } from '@/common/types/util.types';
import { displayMonetaryAmount } from '@/common/utils/utils';
+import { Modal, ModalBody, ModalHeader } from '@/component-library';
import { ACCOUNT_ID_TYPE_NAME } from '@/config/general';
import { DEFAULT_REDEEM_DUST_AMOUNT } from '@/config/parachain';
import { GOVERNANCE_TOKEN, GOVERNANCE_TOKEN_SYMBOL, TRANSACTION_FEE_AMOUNT } from '@/config/relay-chains';
-import CloseIconButton from '@/legacy-components/buttons/CloseIconButton';
import InterlayCinnabarOutlinedButton from '@/legacy-components/buttons/InterlayCinnabarOutlinedButton';
import InterlayMulberryOutlinedButton from '@/legacy-components/buttons/InterlayMulberryOutlinedButton';
import ErrorMessage from '@/legacy-components/ErrorMessage';
import NumberInput from '@/legacy-components/NumberInput';
import PrimaryColorEllipsisLoader from '@/legacy-components/PrimaryColorEllipsisLoader';
-import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
import STATUSES from '@/utils/constants/statuses';
import { getExchangeRate } from '@/utils/helpers/oracle';
@@ -66,8 +64,6 @@ const RequestReplacementModal = ({
const handleError = useErrorHandler();
const { isLoading: isBalancesLoading, data: balances } = useGetBalances();
- const focusRef = React.useRef(null);
-
const { bridgeLoaded } = useSelector((state: StoreType) => state.general);
const [status, setStatus] = React.useState(STATUSES.IDLE);
@@ -112,10 +108,10 @@ const RequestReplacementModal = ({
const vaultId = window.bridge.api.createType(ACCOUNT_ID_TYPE_NAME, vaultAddress);
queryClient.invalidateQueries([GENERIC_FETCHER, 'mapReplaceRequests', vaultId]);
- toast.success('Replacement request is submitted');
setSubmitStatus(STATUSES.RESOLVED);
onClose();
- } catch (error) {
+ } catch (error: any) {
+ transaction.reject(error);
setSubmitStatus(STATUSES.REJECTED);
}
});
@@ -158,12 +154,9 @@ const RequestReplacementModal = ({
const securityDeposit = btcToGovernanceTokenRate.toCounter(wrappedTokenAmount).mul(griefingRate);
return (
-
-
-
- {t('vault.request_replacement')}
-
-
+
+ {t('vault.request_replacement')}
+
-
-
+
+
);
}
return null;
diff --git a/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx b/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
index dad669da97..c01420c02c 100644
--- a/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
+++ b/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
@@ -9,16 +9,14 @@ import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useQuery, useQueryClient } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
-import { toast } from 'react-toastify';
import { updateCollateralAction, updateCollateralizationAction } from '@/common/actions/vault.actions';
import { StoreType } from '@/common/types/util.types';
import { displayMonetaryAmount, displayMonetaryAmountInUSDFormat, formatPercentage } from '@/common/utils/utils';
+import { Modal, ModalBody, ModalHeader } from '@/component-library';
import { ACCOUNT_ID_TYPE_NAME } from '@/config/general';
-import CloseIconButton from '@/legacy-components/buttons/CloseIconButton';
import InterlayDefaultContainedButton from '@/legacy-components/buttons/InterlayDefaultContainedButton';
import TokenField from '@/legacy-components/TokenField';
-import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
import genericFetcher, { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
import STATUSES from '@/utils/constants/statuses';
import { getTokenPrice } from '@/utils/helpers/prices';
@@ -73,7 +71,6 @@ const UpdateCollateralModal = ({
const dispatch = useDispatch();
const { t } = useTranslation();
- const focusRef = React.useRef(null);
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
const handleError = useErrorHandler();
@@ -164,11 +161,10 @@ const UpdateCollateralModal = ({
dispatch(updateCollateralizationAction(strVaultCollateralizationPercentage));
}
- toast.success(t('vault.successfully_updated_collateral'));
setSubmitStatus(STATUSES.RESOLVED);
handleClose();
- } catch (error) {
- toast.error(error.message);
+ } catch (error: any) {
+ transaction.reject(error);
handleError(error);
setSubmitStatus(STATUSES.REJECTED);
}
@@ -271,12 +267,9 @@ const UpdateCollateralModal = ({
};
return (
-
-
-
- {collateralUpdateStatusText}
-
-
+
+ {collateralUpdateStatusText}
+
-
-
+
+
);
};
diff --git a/src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.styles.tsx b/src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.styles.tsx
deleted file mode 100644
index c0591711d6..0000000000
--- a/src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.styles.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import styled from 'styled-components';
-
-import { H2, theme } from '@/component-library';
-
-const StyledDl = styled.dl`
- display: flex;
- flex-direction: column;
- gap: ${theme.spacing.spacing2};
-`;
-
-const StyledDItem = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: ${theme.spacing.spacing2};
-`;
-
-const StyledDt = styled.dt`
- font-size: ${theme.text.xs};
- line-height: ${theme.lineHeight.base};
- color: ${theme.colors.textTertiary};
-`;
-
-const StyledDd = styled.dd`
- font-size: ${theme.text.xs};
- line-height: ${theme.lineHeight.base};
-`;
-
-const StyledTitle = styled(H2)`
- font-size: ${theme.text.base};
- line-height: ${theme.lineHeight.base};
- color: #d57b33;
- padding: ${theme.spacing.spacing3};
- border-bottom: 2px solid #feca2f;
- text-align: center;
-`;
-
-const StyledHr = styled.hr`
- border: 0;
- border-bottom: ${theme.border.default};
- margin: ${theme.spacing.spacing4} 0;
-`;
-
-export { StyledDd, StyledDItem, StyledDl, StyledDt, StyledHr, StyledTitle };
diff --git a/src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.tsx b/src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.tsx
deleted file mode 100644
index 7f00b8be13..0000000000
--- a/src/pages/Vaults/Vault/components/CollateralForm/CollateralForm.tsx
+++ /dev/null
@@ -1,312 +0,0 @@
-import { CollateralCurrencyExt, CurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { useId } from '@react-aria/utils';
-import Big from 'big.js';
-import { FormHTMLAttributes, useEffect, useState } from 'react';
-import { useErrorHandler } from 'react-error-boundary';
-import { useForm } from 'react-hook-form';
-import { useTranslation } from 'react-i18next';
-import { useQuery } from 'react-query';
-import { useSelector } from 'react-redux';
-import { useParams } from 'react-router';
-
-import { StoreType } from '@/common/types/util.types';
-import {
- convertMonetaryAmountToValueInUSD,
- displayMonetaryAmount,
- displayMonetaryAmountInUSDFormat,
- formatNumber,
- formatUSD
-} from '@/common/utils/utils';
-import { CTA, Span, Stack, TokenInput } from '@/component-library';
-import genericFetcher, { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
-import { URL_PARAMETERS } from '@/utils/constants/links';
-import { submitExtrinsic, submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
-import { getTokenPrice } from '@/utils/helpers/prices';
-import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
-import { VaultData } from '@/utils/hooks/api/vaults/get-vault-data';
-
-import { CollateralActions, CollateralStatusRanges } from '../../types';
-import { StyledDd, StyledDItem, StyledDl, StyledDt, StyledHr, StyledTitle } from './CollateralForm.styles';
-
-// const getCollateralStatusLabel = (status: CollateralStatus) => {
-// switch (status) {
-// case 'error':
-// return '(High Risk)';
-// case 'warning':
-// return '(Medium Risk)';
-// case 'success':
-// return '(Low Risk)';
-// }
-// };
-
-const getCollateralTokenAmount = (
- vaultCollateral: Big,
- inputCollateral: MonetaryAmount,
- token: CurrencyExt,
- collateralAction: CollateralActions
-) => {
- let amount = newMonetaryAmount(vaultCollateral, token, true) as MonetaryAmount;
-
- switch (collateralAction) {
- case 'deposit': {
- amount = amount.add(inputCollateral);
- break;
- }
- case 'withdraw': {
- amount = amount.sub(inputCollateral);
- break;
- }
- }
-
- return amount;
-};
-
-const DEPOSIT_COLLATERAL_AMOUNT = 'deposit-collateral-amount';
-const WITHDRAW_COLLATERAL_AMOUNT = 'withdraw-collateral-amount';
-
-type CollateralFormData = {
- [DEPOSIT_COLLATERAL_AMOUNT]?: string;
- [WITHDRAW_COLLATERAL_AMOUNT]?: string;
-};
-
-const collateralInputId: Record = {
- deposit: DEPOSIT_COLLATERAL_AMOUNT,
- withdraw: WITHDRAW_COLLATERAL_AMOUNT
-};
-
-type Props = {
- collateral: VaultData['collateral'];
- collateralToken: CurrencyExt;
- variant?: CollateralActions;
- onSubmit?: () => void;
- ranges: CollateralStatusRanges;
-};
-
-type NativeAttrs = Omit, keyof Props | 'children'>;
-
-type CollateralFormProps = Props & NativeAttrs;
-
-const CollateralForm = ({
- variant = 'deposit',
- onSubmit,
- collateral,
- collateralToken,
- ...props
-}: CollateralFormProps): JSX.Element => {
- const { t } = useTranslation();
- const { bridgeLoaded } = useSelector((state: StoreType) => state.general);
- const { [URL_PARAMETERS.VAULT.ACCOUNT]: vaultAddress } = useParams>();
- const [isSubmitting, setIsSubmitting] = useState(false);
- const prices = useGetPrices();
- const { register, handleSubmit: h, watch } = useForm({
- mode: 'onChange'
- });
- // const [score, setScore] = useState(0);
-
- const tokenInputId = collateralInputId[variant];
- const inputCollateral = watch(tokenInputId) || '0';
- const inputCollateralAmount = newMonetaryAmount(
- inputCollateral,
- collateralToken,
- true
- ) as MonetaryAmount;
-
- const {
- isIdle: requiredCollateralTokenAmountIdle,
- isLoading: requiredCollateralTokenAmountLoading,
- data: requiredCollateralTokenAmount,
- error: requiredCollateralTokenAmountError
- } = useQuery, Error>(
- [GENERIC_FETCHER, 'vaults', 'getRequiredCollateralForVault', vaultAddress, collateralToken],
- genericFetcher>(),
- {
- enabled: !!bridgeLoaded
- }
- );
- useErrorHandler(requiredCollateralTokenAmountError);
-
- const collateralTokenAmount = getCollateralTokenAmount(
- collateral.amount,
- inputCollateralAmount,
- collateralToken,
- variant
- );
-
- const { isLoading: isGetCollateralizationLoading, data: unparsedScore, error } = useQuery(
- [GENERIC_FETCHER, 'vaults', 'getVaultCollateralization', vaultAddress, collateralToken, collateralTokenAmount],
- genericFetcher(),
- {
- enabled: bridgeLoaded
- // TODO: add hasLockedBTC
- // && hasLockedBTC
- }
- );
- useErrorHandler(error);
-
- useEffect(() => {
- if (!isGetCollateralizationLoading) {
- // setScore(unparsedScore?.toNumber() ?? 0);
- }
- }, [isGetCollateralizationLoading, unparsedScore]);
-
- const handleSubmit = async (data: CollateralFormData) => {
- if (!bridgeLoaded) return;
- onSubmit?.();
- setIsSubmitting(true);
-
- try {
- const collateralTokenAmount = newMonetaryAmount(
- data[tokenInputId] || '0',
- collateralToken,
- true
- ) as MonetaryAmount;
-
- switch (variant) {
- case 'deposit': {
- await submitExtrinsic(window.bridge.vaults.depositCollateral(collateralTokenAmount));
- break;
- }
- case 'withdraw': {
- await submitExtrinsicPromise(window.bridge.vaults.withdrawCollateral(collateralTokenAmount));
- break;
- }
- }
-
- // TODO: state changes
-
- // const balanceLockedCollateral = (await window.bridge.tokens.balance(collateralToken, vaultAddress)).reserved;
- // dispatch(updateCollateralAction(balanceLockedCollateral as MonetaryAmount));
-
- // if (vaultCollateralization === undefined) {
- // dispatch(updateCollateralizationAction('∞'));
- // } else {
- // // The vault API returns collateralization as a regular number rather than a percentage
- // const strVaultCollateralizationPercentage = vaultCollateralization.mul(100).toString();
- // dispatch(updateCollateralizationAction(strVaultCollateralizationPercentage));
- // }
-
- // toast.success(t('vault.successfully_updated_collateral'));
- // setSubmitStatus(STATUSES.RESOLVED);
- // onClose();
- } catch (error) {
- // toast.error(error.message);
- // handleError(error);
- setIsSubmitting(false);
- }
- };
-
- const validateCollateralTokenAmount = (value?: string): string | undefined => {
- const collateralTokenAmount = newMonetaryAmount(value || '0', collateralToken, true);
-
- // Collateral update only allowed if above required collateral
- if (variant === 'withdraw' && requiredCollateralTokenAmount) {
- const maxWithdrawableCollateralTokenAmount = collateralTokenAmount.sub(requiredCollateralTokenAmount);
-
- return collateralTokenAmount.gt(maxWithdrawableCollateralTokenAmount)
- ? t('vault.collateral_below_threshold')
- : undefined;
- }
-
- if (collateralTokenAmount.lte(newMonetaryAmount(0, collateralToken, true))) {
- return t('vault.collateral_higher_than_0');
- }
-
- // Represents being less than 1 Planck
- if (collateralTokenAmount.toBig(0).lte(1)) {
- return 'Please enter an amount greater than 1 Planck';
- }
-
- // if (collateralBalance && collateralTokenAmount.gt(collateralBalance.transferable)) {
- // return t(`Must be less than ${collateralToken.ticker} balance!`);
- // }
-
- if (!bridgeLoaded) {
- return 'Bridge must be loaded!';
- }
-
- return undefined;
- };
-
- const collateralUSDAmount = getTokenPrice(prices, collateralToken.ticker)?.usd;
- const isMinCollateralLoading = requiredCollateralTokenAmountIdle || requiredCollateralTokenAmountLoading;
-
- const titleId = useId();
- const title = variant === 'deposit' ? 'Deposit Collateral' : 'Withdraw Collateral';
-
- // TODO: handle infinity collateralization in form
- // const collateralStatus = getCollateralStatus(score, ranges, false);
-
- return (
-
- );
-};
-
-export { CollateralForm };
-export type { CollateralFormProps };
diff --git a/src/pages/Vaults/Vault/components/CollateralForm/index.tsx b/src/pages/Vaults/Vault/components/CollateralForm/index.tsx
deleted file mode 100644
index 1e29b6d0c5..0000000000
--- a/src/pages/Vaults/Vault/components/CollateralForm/index.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export type { CollateralFormProps } from './CollateralForm';
-export { CollateralForm } from './CollateralForm';
diff --git a/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx b/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
index 2b8cedbe6b..d372470202 100644
--- a/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
+++ b/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
@@ -1,13 +1,11 @@
import { CollateralCurrencyExt, newVaultId, WrappedCurrency, WrappedIdLiteral } from '@interlay/interbtc-api';
import Big from 'big.js';
import { useQueryClient } from 'react-query';
-import { toast } from 'react-toastify';
import { formatNumber, formatUSD } from '@/common/utils/utils';
import { CardProps } from '@/component-library';
import { LoadingSpinner } from '@/component-library/LoadingSpinner';
import { GOVERNANCE_TOKEN_SYMBOL, WRAPPED_TOKEN } from '@/config/relay-chains';
-import ErrorModal from '@/legacy-components/ErrorModal';
import { ZERO_GOVERNANCE_TOKEN_AMOUNT } from '@/utils/constants/currency';
import { VaultData } from '@/utils/hooks/api/vaults/get-vault-data';
import { Transaction, useTransaction } from '@/utils/hooks/transaction';
@@ -51,7 +49,6 @@ const Rewards = ({
const transaction = useTransaction(Transaction.REWARDS_WITHDRAW, {
onSuccess: () => {
queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
- toast.success('Your rewards were successfully withdrawn.');
}
});
@@ -91,14 +88,6 @@ const Rewards = ({
Withdraw all rewards
)}
- {transaction.isError && (
- transaction.reset()}
- title='Error'
- description={transaction.error?.message || ''}
- />
- )}
);
diff --git a/src/pages/Vaults/Vault/components/index.tsx b/src/pages/Vaults/Vault/components/index.tsx
index e85ac5e4b4..eefb92e3b4 100644
--- a/src/pages/Vaults/Vault/components/index.tsx
+++ b/src/pages/Vaults/Vault/components/index.tsx
@@ -1,4 +1,3 @@
-import { CollateralForm, CollateralFormProps } from './CollateralForm';
import { InsightListItem, InsightsList, InsightsListProps } from './InsightsList';
import { PageTitle, PageTitleProps } from './PageTitle';
import { Rewards, RewardsProps } from './Rewards';
@@ -6,9 +5,8 @@ import { TransactionHistory, TransactionHistoryProps } from './TransactionHistor
import { VaultCollateral, VaultCollateralProps } from './VaultCollateral';
import { VaultInfo, VaultInfoProps } from './VaultInfo';
-export { CollateralForm, InsightsList, PageTitle, Rewards, TransactionHistory, VaultCollateral, VaultInfo };
+export { InsightsList, PageTitle, Rewards, TransactionHistory, VaultCollateral, VaultInfo };
export type {
- CollateralFormProps,
InsightListItem,
InsightsListProps,
PageTitleProps,
diff --git a/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx b/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
index 4fbe4efd89..62a3bc21fe 100644
--- a/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
+++ b/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
@@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
import { CTA, ModalBody, ModalDivider, ModalFooter, ModalHeader, Span, Stack, TokenInput } from '@/component-library';
import { GOVERNANCE_TOKEN } from '@/config/relay-chains';
-import ErrorModal from '@/legacy-components/ErrorModal';
import {
CREATE_VAULT_DEPOSIT_FIELD,
CreateVaultFormData,
@@ -38,7 +37,8 @@ const DepositCollateralStep = ({
const { collateral, fee, governance } = useDepositCollateral(collateralCurrency, minCollateralAmount);
const transaction = useTransaction(Transaction.VAULTS_REGISTER_NEW_COLLATERAL, {
- onSuccess: onSuccessfulDeposit
+ onSuccess: onSuccessfulDeposit,
+ showSuccessModal: false
});
const validationParams = {
@@ -108,14 +108,6 @@ const DepositCollateralStep = ({
- {transaction.isError && (
- transaction.reset()}
- title='Error'
- description={transaction.error?.message || ''}
- />
- )}
>
);
};
diff --git a/src/pages/Wallet/WalletOverview/components/AvailableAssetsTable/ActionsCell.tsx b/src/pages/Wallet/WalletOverview/components/AvailableAssetsTable/ActionsCell.tsx
index ca103cb82d..4eeb9f33c4 100644
--- a/src/pages/Wallet/WalletOverview/components/AvailableAssetsTable/ActionsCell.tsx
+++ b/src/pages/Wallet/WalletOverview/components/AvailableAssetsTable/ActionsCell.tsx
@@ -1,21 +1,16 @@
import { CurrencyExt } from '@interlay/interbtc-api';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { useDispatch } from 'react-redux';
-import { toast } from 'react-toastify';
import { showBuyModal } from '@/common/actions/general.actions';
import { CTA, CTALink, CTAProps, Divider, Flex, theme } from '@/component-library';
import { useMediaQuery } from '@/component-library/utils/use-media-query';
import { WRAPPED_TOKEN } from '@/config/relay-chains';
import { PAGES, QUERY_PARAMETERS } from '@/utils/constants/links';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const queryString = require('query-string');
-const claimVesting = async () => {
- await window.bridge.api.tx.vesting.claim();
-};
-
type ActionsCellProps = {
currency: CurrencyExt;
isWrappedToken: boolean;
@@ -39,20 +34,9 @@ const ActionsCell = ({
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const isSmallMobile = useMediaQuery(theme.breakpoints.down('sm'));
- const handleClaimVestingSuccess = () => {
- toast.success('Successfully claimed vesting');
- };
-
- const handleClaimVestingError = (error: Error) => {
- toast.success(error);
- };
-
- const claimVestingMutation = useMutation(claimVesting, {
- onSuccess: handleClaimVestingSuccess,
- onError: handleClaimVestingError
- });
+ const vestingClaimTransaction = useTransaction(Transaction.VESTING_CLAIM);
- const handlePressClaimVesting = () => claimVestingMutation.mutate();
+ const handlePressClaimVesting = () => vestingClaimTransaction.execute();
const handlePressBuyGovernance = () => dispatch(showBuyModal(true));
diff --git a/src/parts/Topbar/index.tsx b/src/parts/Topbar/index.tsx
index b287a4420d..63098f3442 100644
--- a/src/parts/Topbar/index.tsx
+++ b/src/parts/Topbar/index.tsx
@@ -9,7 +9,7 @@ import { toast } from 'react-toastify';
import { showAccountModalAction, showSignTermsModalAction } from '@/common/actions/general.actions';
import { StoreType } from '@/common/types/util.types';
-import { FundWallet } from '@/components';
+import { FundWallet, NotificationsPopover } from '@/components';
import { AuthModal, SignTermsModal } from '@/components/AuthModal';
import { ACCOUNT_ID_TYPE_NAME } from '@/config/general';
import { GOVERNANCE_TOKEN } from '@/config/relay-chains';
@@ -21,6 +21,7 @@ import Tokens from '@/legacy-components/Tokens';
import InterlayLink from '@/legacy-components/UI/InterlayLink';
import { KeyringPair, useSubstrate, useSubstrateSecureState } from '@/lib/substrate';
import { BitcoinNetwork } from '@/types/bitcoin';
+import { useNotifications } from '@/utils/context/Notifications';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { FeatureFlags, useFeatureFlag } from '@/utils/hooks/use-feature-flag';
import { useSignMessage } from '@/utils/hooks/use-sign-message';
@@ -38,8 +39,9 @@ const Topbar = (): JSX.Element => {
const isBanxaEnabled = useFeatureFlag(FeatureFlags.BANXA);
const { setSelectedAccount, removeSelectedAccount } = useSubstrate();
const { selectProps } = useSignMessage();
+ const { list } = useNotifications();
- const kintBalanceIsZero = getAvailableBalance('KINT')?.isZero();
+ const governanceTokenBalanceIsZero = getAvailableBalance(GOVERNANCE_TOKEN.ticker)?.isZero();
const handleRequestFromFaucet = async (): Promise => {
if (!selectedAccount) return;
@@ -47,6 +49,7 @@ const Topbar = (): JSX.Element => {
try {
const receiverId = window.bridge.api.createType(ACCOUNT_ID_TYPE_NAME, selectedAccount.address);
await window.faucet.fundAccount(receiverId, GOVERNANCE_TOKEN);
+ // TODO: show new notification
toast.success('Your account has been funded.');
} catch (error) {
toast.error(`Funding failed. ${error.message}`);
@@ -103,7 +106,7 @@ const Topbar = (): JSX.Element => {
{isBanxaEnabled ? : }
{selectedAccount !== undefined && (
<>
- {process.env.REACT_APP_FAUCET_URL && kintBalanceIsZero && (
+ {process.env.REACT_APP_FAUCET_URL && governanceTokenBalanceIsZero && (
<>
{
>
)}
+
{accountLabel}
diff --git a/src/utils/constants/links.ts b/src/utils/constants/links.ts
index c6cd038371..7a62ca51f9 100644
--- a/src/utils/constants/links.ts
+++ b/src/utils/constants/links.ts
@@ -1,4 +1,5 @@
import { BANXA_LINK } from '@/config/links';
+import { SUBSCAN_LINK } from '@/config/relay-chains';
const URL_PARAMETERS = Object.freeze({
VAULT: {
@@ -35,8 +36,24 @@ const PAGES = Object.freeze({
WALLET: '/wallet'
});
+const EXTERNAL_URL_PARAMETERS = Object.freeze({
+ SUBSCAN: {
+ BLOCK: {
+ HASH: 'hash'
+ },
+ ACCOUNT: {
+ ADDRESS: 'address'
+ }
+ }
+});
+
const EXTERNAL_PAGES = Object.freeze({
- BANXA: `${BANXA_LINK}`
+ BANXA: `${BANXA_LINK}`,
+ SUBSCAN: {
+ BLOCKS: `${SUBSCAN_LINK}/block`,
+ BLOCK: `${SUBSCAN_LINK}/block/:${EXTERNAL_URL_PARAMETERS.SUBSCAN.BLOCK.HASH}`,
+ ACCOUNT: `${SUBSCAN_LINK}/account/:${EXTERNAL_URL_PARAMETERS.SUBSCAN.ACCOUNT.ADDRESS}`
+ }
});
const QUERY_PARAMETERS = Object.freeze({
@@ -60,4 +77,4 @@ const EXTERNAL_QUERY_PARAMETERS = Object.freeze({
}
});
-export { EXTERNAL_PAGES, EXTERNAL_QUERY_PARAMETERS, PAGES, QUERY_PARAMETERS, URL_PARAMETERS };
+export { EXTERNAL_PAGES, EXTERNAL_QUERY_PARAMETERS, EXTERNAL_URL_PARAMETERS, PAGES, QUERY_PARAMETERS, URL_PARAMETERS };
diff --git a/src/utils/context/Notifications.tsx b/src/utils/context/Notifications.tsx
new file mode 100644
index 0000000000..3dd7f48752
--- /dev/null
+++ b/src/utils/context/Notifications.tsx
@@ -0,0 +1,141 @@
+import { Overlay } from '@react-aria/overlays';
+import { mergeProps } from '@react-aria/utils';
+import React, { useEffect, useRef } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { Id as NotificationId, toast, ToastOptions } from 'react-toastify';
+
+import { addNotification } from '@/common/actions/general.actions';
+import { Notification, StoreType } from '@/common/types/util.types';
+import { ToastContainer, TransactionToast, TransactionToastProps } from '@/components';
+
+import { useWallet } from '../hooks/use-wallet';
+
+// Allows the introduction of diferent
+// notifications toast beyond transactions
+// i.e. claiming faucet funds or sign T&Cs
+enum NotificationToast {
+ TRANSACTION
+}
+
+type NotificationToastAction = { type: NotificationToast.TRANSACTION; props: TransactionToastProps };
+
+const toastComponentMap = { [NotificationToast.TRANSACTION]: TransactionToast };
+
+type ToastMap = Record;
+
+type NotifcationInfo = {
+ // NotificationId - toast is on the screen
+ // null - toast has been dismissed
+ // undefined - toast never existed
+ id: NotificationId | null | undefined;
+ hasRendered: boolean;
+ isOnScreen: boolean;
+};
+
+type NotificationOptions = ToastOptions;
+
+const toastConfig: NotificationOptions = {
+ closeButton: false,
+ autoClose: false,
+ closeOnClick: false,
+ draggable: false,
+ icon: false
+};
+
+type NotificationsConfig = {
+ list: Notification[];
+ // gets notification meta data
+ get: (id: number | string) => NotifcationInfo;
+ // adds to the redux notifications list
+ add: (notification: Omit) => void;
+ // renders toast
+ show: (id: number | string, action: NotificationToastAction) => void;
+ // removes toast from the screen
+ dismiss: (id: number | string) => void;
+};
+
+const defaultContext: NotificationsConfig = {} as NotificationsConfig;
+
+const NotificationsContext = React.createContext(defaultContext);
+
+const useNotifications = (): NotificationsConfig => React.useContext(NotificationsContext);
+
+const NotificationsProvider: React.FC = ({ children }) => {
+ const toastContainerRef = useRef(null);
+
+ const dispatch = useDispatch();
+
+ const { account } = useWallet();
+ const { notifications } = useSelector((state: StoreType) => state.general);
+
+ const idsMap = useRef({});
+
+ const get = (id: number | string) => {
+ const toastId = idsMap.current[id];
+
+ return {
+ id: toastId,
+ hasRendered: toastId === null,
+ isOnScreen: !!toastId
+ };
+ };
+
+ const add = (notification: Omit) =>
+ dispatch(addNotification(account?.toString() as string, { ...notification, date: new Date() }));
+
+ const show = (id: number | string, action: NotificationToastAction) => {
+ const toastInfo = get(id);
+
+ const ToastComponent = toastComponentMap[action.type];
+
+ const onDismiss = () => dismiss(id);
+
+ const render = ;
+
+ if (toastInfo.id) {
+ return toast.update(toastInfo.id, { render, ...toastConfig });
+ }
+
+ const newToastId = toast(render, toastConfig);
+ idsMap.current[id] = newToastId;
+ };
+
+ const dismiss = (id: number | string) => {
+ const toasInfo = get(id);
+
+ if (!toasInfo.id) return;
+
+ toast.dismiss(toasInfo.id);
+ // Set to null, meaning that this toast should never appear again, even if updated
+ idsMap.current[id] = null;
+ };
+
+ // Applying data-react-aria-top-layer="true" makes react-aria overlay consider the element as a visible element.
+ // Non-visible elements get forced with aria-hidden=true.
+ // Check: https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/overlays/src/ariaHideOutside.ts#L32
+ useEffect(() => {
+ if (!toastContainerRef.current) return;
+
+ toastContainerRef.current.setAttribute('data-react-aria-top-layer', 'true');
+ }, [toastContainerRef]);
+
+ return (
+
+ {children}
+
+
+
+
+ );
+};
+
+export { NotificationsContext, NotificationsProvider, NotificationToast, useNotifications };
+export type { NotificationToastAction };
diff --git a/src/utils/helpers/loans.ts b/src/utils/helpers/loans.ts
index c7241abb5e..35ceb31d07 100644
--- a/src/utils/helpers/loans.ts
+++ b/src/utils/helpers/loans.ts
@@ -11,6 +11,9 @@ const MIN_DECIMAL_NUMBER = 0.01;
// MEMO: returns formatted apy or better representation of a very small apy
const getApyLabel = (apy: Big): string => {
+ if (apy.eq(0)) {
+ return formatPercentage(0);
+ }
const isPositive = apy.gt(0);
const isTinyApy = isPositive ? apy.lt(MIN_DECIMAL_NUMBER) : apy.gt(-MIN_DECIMAL_NUMBER);
@@ -41,7 +44,7 @@ const getSubsidyRewardApy = (
}
const exchangeRate = rewardCurrencyPriceUSD / positionCurrencyPriceUSD;
- const apy = reward.toBig().mul(exchangeRate);
+ const apy = reward.toBig().mul(exchangeRate).mul(100);
return apy;
};
diff --git a/src/utils/hooks/api/loans/use-get-account-lending-statistics.tsx b/src/utils/hooks/api/loans/use-get-account-lending-statistics.tsx
index 0aa6d77d05..3facffc723 100644
--- a/src/utils/hooks/api/loans/use-get-account-lending-statistics.tsx
+++ b/src/utils/hooks/api/loans/use-get-account-lending-statistics.tsx
@@ -60,6 +60,7 @@ const getNetAPY = (
const totalBorrowApy = borrowPositions.reduce((total, position) => {
const { currency } = position.amount;
const { borrowApy, borrowReward } = assets[currency.ticker];
+
const rewardsApy = getSubsidyRewardApy(currency, borrowReward, prices);
const positionApy = borrowApy.sub(rewardsApy || 0);
const positionUSDValue = convertMonetaryAmountToValueInUSD(
diff --git a/src/utils/hooks/api/use-get-vesting-data.tsx b/src/utils/hooks/api/use-get-vesting-data.tsx
index 48972a88f1..5c011ef2cc 100644
--- a/src/utils/hooks/api/use-get-vesting-data.tsx
+++ b/src/utils/hooks/api/use-get-vesting-data.tsx
@@ -23,7 +23,7 @@ const getVestingData = async (accountId: AccountId): Promise => {
const schedules = await window.bridge.api.query.vesting.vestingSchedules(accountId);
const schedule = schedules[0];
- const isClaimable = !!schedule && currentBlockNumber > schedule.start + schedule.period;
+ const isClaimable = !!schedule && currentBlockNumber > schedule.start.toNumber() + schedule.period.toNumber();
return {
schedules,
diff --git a/src/utils/hooks/transaction/extrinsics/extrinsics.ts b/src/utils/hooks/transaction/extrinsics/extrinsics.ts
new file mode 100644
index 0000000000..cf63d868c9
--- /dev/null
+++ b/src/utils/hooks/transaction/extrinsics/extrinsics.ts
@@ -0,0 +1,46 @@
+import { ExtrinsicData } from '@interlay/interbtc-api';
+import { ExtrinsicStatus } from '@polkadot/types/interfaces';
+
+import { Transaction, TransactionActions } from '../types';
+import { getLibExtrinsic } from './lib';
+import { getXCMExtrinsic } from './xcm';
+
+/**
+ * SUMMARY: Maps each transaction to the correct lib call,
+ * while maintaining a safe-type check.
+ * HOW TO ADD NEW TRANSACTION: find the correct module to add the transaction
+ * in the types folder. In case you are adding a new type to the loans modules, go
+ * to types/loans and add your new transaction as an action. This actions needs to also be added to the
+ * types/index TransactionActions type. After that, you should be able to add it to the function.
+ * @param {TransactionActions} params contains the type of transaction and
+ * the related args to call the mapped lib call
+ * @return {Promise} every transaction return an extrinsic
+ */
+const getExtrinsic = async (params: TransactionActions): Promise => {
+ switch (params.type) {
+ case Transaction.XCM_TRANSFER:
+ return getXCMExtrinsic(params);
+ default:
+ return getLibExtrinsic(params);
+ }
+};
+
+/**
+ * The status where we want to be notified on the transaction completion
+ * @param {Transaction} type type of transaction
+ * @return {ExtrinsicStatus.type} transaction status
+ */
+const getStatus = (type: Transaction): ExtrinsicStatus['type'] => {
+ switch (type) {
+ // When requesting a replace, wait for the finalized event because we cannot revert BTC transactions.
+ // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
+ case Transaction.ISSUE_REQUEST:
+ case Transaction.REDEEM_REQUEST:
+ case Transaction.REPLACE_REQUEST:
+ return 'Finalized';
+ default:
+ return 'InBlock';
+ }
+};
+
+export { getExtrinsic, getStatus };
diff --git a/src/utils/hooks/transaction/extrinsics/index.ts b/src/utils/hooks/transaction/extrinsics/index.ts
new file mode 100644
index 0000000000..ff986fb28c
--- /dev/null
+++ b/src/utils/hooks/transaction/extrinsics/index.ts
@@ -0,0 +1 @@
+export { getExtrinsic, getStatus } from './extrinsics';
diff --git a/src/utils/hooks/transaction/utils/extrinsic.ts b/src/utils/hooks/transaction/extrinsics/lib.ts
similarity index 70%
rename from src/utils/hooks/transaction/utils/extrinsic.ts
rename to src/utils/hooks/transaction/extrinsics/lib.ts
index 23346819db..0d2b90a727 100644
--- a/src/utils/hooks/transaction/utils/extrinsic.ts
+++ b/src/utils/hooks/transaction/extrinsics/lib.ts
@@ -1,20 +1,8 @@
import { ExtrinsicData } from '@interlay/interbtc-api';
-import { ExtrinsicStatus } from '@polkadot/types/interfaces';
-import { Transaction, TransactionActions } from '../types';
+import { LibActions, Transaction } from '../types';
-/**
- * SUMMARY: Maps each transaction to the correct lib call,
- * while maintaining a safe-type check.
- * HOW TO ADD NEW TRANSACTION: find the correct module to add the transaction
- * in the types folder. In case you are adding a new type to the loans modules, go
- * to types/loans and add your new transaction as an action. This actions needs to also be added to the
- * types/index TransactionActions type. After that, you should be able to add it to the function.
- * @param {TransactionActions} params contains the type of transaction and
- * the related args to call the mapped lib call
- * @return {Promise} every transaction return an extrinsic
- */
-const getExtrinsic = async (params: TransactionActions): Promise => {
+const getLibExtrinsic = async (params: LibActions): Promise => {
switch (params.type) {
/* START - AMM */
case Transaction.AMM_SWAP:
@@ -74,18 +62,19 @@ const getExtrinsic = async (params: TransactionActions): Promise
return window.bridge.loans.enableAsCollateral(...params.args);
/* END - LOANS */
- /* START - LOANS */
+ /* START - VAULTS */
case Transaction.VAULTS_DEPOSIT_COLLATERAL:
return window.bridge.vaults.depositCollateral(...params.args);
case Transaction.VAULTS_WITHDRAW_COLLATERAL:
return window.bridge.vaults.withdrawCollateral(...params.args);
case Transaction.VAULTS_REGISTER_NEW_COLLATERAL:
return window.bridge.vaults.registerNewCollateralVault(...params.args);
+ /* END - VAULTS */
+
/* START - REWARDS */
case Transaction.REWARDS_WITHDRAW:
return window.bridge.rewards.withdrawRewards(...params.args);
/* START - REWARDS */
- /* END - LOANS */
/* START - ESCROW */
case Transaction.ESCROW_CREATE_LOCK:
@@ -109,25 +98,12 @@ const getExtrinsic = async (params: TransactionActions): Promise
return { extrinsic: batch };
}
/* END - ESCROW */
- }
-};
-/**
- * The status where we want to be notified on the transaction completion
- * @param {Transaction} type type of transaction
- * @return {ExtrinsicStatus.type} transaction status
- */
-const getStatus = (type: Transaction): ExtrinsicStatus['type'] => {
- switch (type) {
- // When requesting a replace, wait for the finalized event because we cannot revert BTC transactions.
- // For more details see: https://github.com/interlay/interbtc-api/pull/373#issuecomment-1058949000
- case Transaction.ISSUE_REQUEST:
- case Transaction.REDEEM_REQUEST:
- case Transaction.REPLACE_REQUEST:
- return 'Finalized';
- default:
- return 'InBlock';
+ /* START - VESTING */
+ case Transaction.VESTING_CLAIM:
+ return { extrinsic: window.bridge.api.tx.vesting.claim() };
+ /* END - VESTING */
}
};
-export { getExtrinsic, getStatus };
+export { getLibExtrinsic };
diff --git a/src/utils/hooks/transaction/extrinsics/xcm.ts b/src/utils/hooks/transaction/extrinsics/xcm.ts
new file mode 100644
index 0000000000..785369df31
--- /dev/null
+++ b/src/utils/hooks/transaction/extrinsics/xcm.ts
@@ -0,0 +1,27 @@
+import { FixedPointNumber } from '@acala-network/sdk-core';
+import { CrossChainTransferParams } from '@interlay/bridge';
+import { ExtrinsicData } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { XCMActions } from '../types/xcm';
+
+const getXCMExtrinsic = async (params: XCMActions): Promise => {
+ switch (params.type) {
+ case Transaction.XCM_TRANSFER: {
+ const [adapter, , toChain, address, transferAmount] = params.args;
+
+ const transferAmountString = transferAmount.toString(true);
+ const transferAmountDecimals = transferAmount.currency.decimals;
+ const tx = adapter.createTx({
+ amount: FixedPointNumber.fromInner(transferAmountString, transferAmountDecimals),
+ to: toChain,
+ token: transferAmount.currency.ticker,
+ address
+ } as CrossChainTransferParams);
+
+ return { extrinsic: tx };
+ }
+ }
+};
+
+export { getXCMExtrinsic };
diff --git a/src/utils/hooks/transaction/types/index.ts b/src/utils/hooks/transaction/types/index.ts
index 538f820678..81d43097a0 100644
--- a/src/utils/hooks/transaction/types/index.ts
+++ b/src/utils/hooks/transaction/types/index.ts
@@ -9,6 +9,8 @@ import { ReplaceActions } from './replace';
import { RewardsActions } from './rewards';
import { TokensActions } from './tokens';
import { VaultsActions } from './vaults';
+import { VestingActions } from './vesting';
+import { XCMActions } from './xcm';
enum Transaction {
// Issue
@@ -29,6 +31,8 @@ enum Transaction {
ESCROW_WITHDRAW = 'ESCROW_WITHDRAW',
// Tokens
TOKENS_TRANSFER = 'TOKENS_TRANSFER',
+ // XCM
+ XCM_TRANSFER = 'XCM_TRANSFER',
// Vaults
VAULTS_DEPOSIT_COLLATERAL = 'VAULTS_DEPOSIT_COLLATERAL',
VAULTS_WITHDRAW_COLLATERAL = 'VAULTS_WITHDRAW_COLLATERAL',
@@ -49,7 +53,11 @@ enum Transaction {
AMM_SWAP = 'AMM_SWAP',
AMM_ADD_LIQUIDITY = 'AMM_ADD_LIQUIDITY',
AMM_REMOVE_LIQUIDITY = 'AMM_REMOVE_LIQUIDITY',
- AMM_CLAIM_REWARDS = 'AMM_CLAIM_REWARDS'
+ AMM_CLAIM_REWARDS = 'AMM_CLAIM_REWARDS',
+ // Vesting
+ VESTING_CLAIM = 'VESTING_CLAIM',
+ // Faucet
+ FAUCET_FUND_WALLET = 'FAUCET_FUND_WALLET'
}
type TransactionEvents = {
@@ -59,10 +67,11 @@ type TransactionEvents = {
interface TransactionAction {
accountAddress: string;
events: TransactionEvents;
+ timestamp: number;
customStatus?: ExtrinsicStatus['type'];
}
-type TransactionActions =
+type LibActions =
| EscrowActions
| IssueActions
| RedeemActions
@@ -71,9 +80,19 @@ type TransactionActions =
| LoansActions
| AMMActions
| VaultsActions
- | RewardsActions;
+ | RewardsActions
+ | VestingActions;
+
+type TransactionActions = XCMActions | LibActions;
type TransactionArgs = Extract['args'];
-export { Transaction };
-export type { TransactionAction, TransactionActions, TransactionArgs, TransactionEvents };
+enum TransactionStatus {
+ CONFIRM,
+ SUBMITTING,
+ SUCCESS,
+ ERROR
+}
+
+export { Transaction, TransactionStatus };
+export type { LibActions, TransactionAction, TransactionActions, TransactionArgs, TransactionEvents, XCMActions };
diff --git a/src/utils/hooks/transaction/types/vesting.ts b/src/utils/hooks/transaction/types/vesting.ts
new file mode 100644
index 0000000000..ab4ce9a00e
--- /dev/null
+++ b/src/utils/hooks/transaction/types/vesting.ts
@@ -0,0 +1,13 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '.';
+import { TransactionAction } from '.';
+
+interface VestingClaimAction extends TransactionAction {
+ type: Transaction.VESTING_CLAIM;
+ args: Parameters;
+}
+
+type VestingActions = VestingClaimAction;
+
+export type { VestingActions };
diff --git a/src/utils/hooks/transaction/types/xcm.ts b/src/utils/hooks/transaction/types/xcm.ts
new file mode 100644
index 0000000000..71b0276c11
--- /dev/null
+++ b/src/utils/hooks/transaction/types/xcm.ts
@@ -0,0 +1,21 @@
+import { ChainName } from '@interlay/bridge';
+import { BaseCrossChainAdapter } from '@interlay/bridge/build/base-chain-adapter';
+import { CurrencyExt } from '@interlay/interbtc-api';
+import { MonetaryAmount } from '@interlay/monetary-js';
+
+import { Transaction, TransactionAction } from '.';
+
+interface XCMTransferAction extends TransactionAction {
+ type: Transaction.XCM_TRANSFER;
+ args: [
+ adapter: BaseCrossChainAdapter,
+ fromChain: ChainName,
+ toChain: ChainName,
+ destinatary: string,
+ transferAmount: MonetaryAmount
+ ];
+}
+
+type XCMActions = XCMTransferAction;
+
+export type { XCMActions };
diff --git a/src/utils/hooks/transaction/use-transaction-notifications.tsx b/src/utils/hooks/transaction/use-transaction-notifications.tsx
new file mode 100644
index 0000000000..abcb7fda2e
--- /dev/null
+++ b/src/utils/hooks/transaction/use-transaction-notifications.tsx
@@ -0,0 +1,107 @@
+import { ISubmittableResult } from '@polkadot/types/types';
+import { useTranslation } from 'react-i18next';
+import { useDispatch } from 'react-redux';
+
+import { updateTransactionModal } from '@/common/actions/general.actions';
+import { TransactionModalData } from '@/common/types/util.types';
+import { EXTERNAL_PAGES, EXTERNAL_URL_PARAMETERS } from '@/utils/constants/links';
+import { NotificationToast, NotificationToastAction, useNotifications } from '@/utils/context/Notifications';
+
+import { TransactionActions, TransactionStatus } from './types';
+import { TransactionResult } from './use-transaction';
+import { getTransactionDescription } from './utils/description';
+
+type TransactionNotificationsOptions = {
+ showSuccessModal?: boolean;
+};
+
+type UseTransactionNotificationsResult = {
+ onReject: (error?: Error) => void;
+ mutationProps: {
+ onMutate: (variables: TransactionActions) => void;
+ onSigning: (variables: TransactionActions) => void;
+ onSuccess: (data: TransactionResult, variables: TransactionActions) => void;
+ onError: (error: Error, variables: TransactionActions, context: unknown) => void;
+ };
+};
+
+// Handles both transactions notifications and modal
+const useTransactionNotifications = ({
+ showSuccessModal = true
+}: TransactionNotificationsOptions): UseTransactionNotificationsResult => {
+ const { t } = useTranslation();
+
+ const notifications = useNotifications();
+
+ const dispatch = useDispatch();
+
+ const handleModalOrToast = (
+ status: TransactionStatus,
+ variables: TransactionActions,
+ data?: ISubmittableResult,
+ error?: Error
+ ) => {
+ const toastInfo = notifications.get(variables.timestamp);
+
+ const url =
+ data?.txHash &&
+ EXTERNAL_PAGES.SUBSCAN.BLOCK.replace(`:${EXTERNAL_URL_PARAMETERS.SUBSCAN.BLOCK.HASH}`, data.txHash.toString());
+
+ const description = getTransactionDescription(variables, status, t);
+
+ // Add notification to history if status is SUCCESS or ERROR
+ if (description && (status === TransactionStatus.SUCCESS || status === TransactionStatus.ERROR)) {
+ notifications.add({ description, status, url });
+ }
+
+ // If toast already rendered, it means that the user did already dismiss the transaction modal and the toast
+ if (toastInfo.hasRendered) return;
+
+ // creating or updating notification
+ if (toastInfo.isOnScreen) {
+ const toastAction: NotificationToastAction = {
+ type: NotificationToast.TRANSACTION,
+ props: {
+ variant: status,
+ url,
+ errorMessage: error?.message,
+ description
+ }
+ };
+
+ return notifications.show(variables.timestamp, toastAction);
+ }
+
+ // only reach here if the modal has not been dismissed
+ const modalData: TransactionModalData = {
+ url,
+ description,
+ variant: status,
+ errorMessage: error?.message,
+ timestamp: variables?.timestamp
+ };
+
+ const isModalOpen = status === TransactionStatus.SUCCESS ? showSuccessModal : true;
+
+ return dispatch(updateTransactionModal(isModalOpen, modalData));
+ };
+
+ const handleSuccess = (result: TransactionResult, variables: TransactionActions) => {
+ const status = result.status === 'error' ? TransactionStatus.ERROR : TransactionStatus.SUCCESS;
+
+ handleModalOrToast(status, variables, result.data, result.error);
+ };
+
+ return {
+ onReject: (error) =>
+ dispatch(updateTransactionModal(true, { variant: TransactionStatus.ERROR, errorMessage: error?.message })),
+ mutationProps: {
+ onMutate: (variables) => handleModalOrToast(TransactionStatus.CONFIRM, variables),
+ onSigning: (variables) => handleModalOrToast(TransactionStatus.SUBMITTING, variables),
+ onSuccess: (result, variables) => handleSuccess(result, variables),
+ onError: (error, variables) => handleModalOrToast(TransactionStatus.ERROR, variables, undefined, error)
+ }
+ };
+};
+
+export { useTransactionNotifications };
diff --git a/src/utils/hooks/transaction/use-transaction.ts b/src/utils/hooks/transaction/use-transaction.ts
index d18291f94c..3fa2cda32e 100644
--- a/src/utils/hooks/transaction/use-transaction.ts
+++ b/src/utils/hooks/transaction/use-transaction.ts
@@ -1,51 +1,62 @@
import { ExtrinsicStatus } from '@polkadot/types/interfaces';
import { ISubmittableResult } from '@polkadot/types/types';
-import { useCallback } from 'react';
+import { mergeProps } from '@react-aria/utils';
+import { useCallback, useState } from 'react';
import { MutationFunction, useMutation, UseMutationOptions, UseMutationResult } from 'react-query';
import { useSubstrate } from '@/lib/substrate';
+import { getExtrinsic, getStatus } from './extrinsics';
import { Transaction, TransactionActions, TransactionArgs } from './types';
-import { getExtrinsic, getStatus } from './utils/extrinsic';
+import { useTransactionNotifications } from './use-transaction-notifications';
import { submitTransaction } from './utils/submit';
-type UseTransactionOptions = Omit<
- UseMutationOptions,
- 'mutationFn'
-> & {
- customStatus?: ExtrinsicStatus['type'];
-};
+type TransactionResult = { status: 'success' | 'error'; data: ISubmittableResult; error?: Error };
// TODO: add feeEstimate and feeEstimateAsync
type ExecuteArgs = {
// Executes the transaction
execute(...args: TransactionArgs): void;
// Similar to execute but returns a promise which can be awaited.
- executeAsync(...args: TransactionArgs): Promise;
+ executeAsync(...args: TransactionArgs): Promise;
};
// TODO: add feeEstimate and feeEstimateAsync
type ExecuteTypeArgs = {
execute(type: D, ...args: TransactionArgs): void;
- executeAsync(type: D, ...args: TransactionArgs): Promise;
+ executeAsync(type: D, ...args: TransactionArgs): Promise;
};
-type InheritAttrs = Omit<
- UseMutationResult,
+type ExecuteFunctions = ExecuteArgs | ExecuteTypeArgs;
+
+type ReactQueryUseMutationResult = Omit<
+ UseMutationResult,
'mutate' | 'mutateAsync'
>;
-type UseTransactionResult = InheritAttrs & (ExecuteArgs | ExecuteTypeArgs);
+type UseTransactionResult = {
+ reject: (error?: Error) => void;
+ isSigned: boolean;
+} & ReactQueryUseMutationResult &
+ ExecuteFunctions;
-const mutateTransaction: MutationFunction = async (params) => {
+const mutateTransaction: MutationFunction = async (params) => {
const extrinsics = await getExtrinsic(params);
const expectedStatus = params.customStatus || getStatus(params.type);
return submitTransaction(window.bridge.api, params.accountAddress, extrinsics, expectedStatus, params.events);
};
+type UseTransactionOptions = Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> & {
+ customStatus?: ExtrinsicStatus['type'];
+ onSigning?: (variables: TransactionActions) => void;
+ showSuccessModal?: boolean;
+};
+
// The three declared functions are use to infer types on diferent implementations
-// TODO: missing xcm transaction
function useTransaction(
type: T,
options?: UseTransactionOptions
@@ -59,13 +70,31 @@ function useTransaction(
): UseTransactionResult {
const { state } = useSubstrate();
- const hasOnlyOptions = typeof typeOrOptions !== 'string';
+ const [isSigned, setSigned] = useState(false);
+
+ const { showSuccessModal, customStatus, ...mutateOptions } =
+ (typeof typeOrOptions === 'string' ? options : typeOrOptions) || {};
- const { mutate, mutateAsync, ...transactionMutation } = useMutation(
- mutateTransaction,
- (hasOnlyOptions ? typeOrOptions : options) as UseTransactionOptions
+ const notifications = useTransactionNotifications({ showSuccessModal });
+
+ const handleMutate = () => setSigned(false);
+
+ const handleSigning = () => setSigned(true);
+
+ const handleError = (error: Error) => console.error(error.message);
+
+ const { onSigning, ...optionsProp } = mergeProps(
+ mutateOptions,
+ {
+ onMutate: handleMutate,
+ onSigning: handleSigning,
+ onError: handleError
+ },
+ notifications.mutationProps
);
+ const { mutate, mutateAsync, ...transactionMutation } = useMutation(mutateTransaction, optionsProp);
+
// Handles params for both type of implementations
const getParams = useCallback(
(args: Parameters['execute']>) => {
@@ -83,14 +112,21 @@ function useTransaction(
// Execution should only ran when authenticated
const accountAddress = state.selectedAccount?.address;
- // TODO: add event `onReady`
- return {
+ const variables = {
...params,
accountAddress,
- customStatus: options?.customStatus
+ timestamp: new Date().getTime(),
+ customStatus
} as TransactionActions;
+
+ return {
+ ...variables,
+ events: {
+ onReady: () => onSigning(variables)
+ }
+ };
},
- [options?.customStatus, state.selectedAccount?.address, typeOrOptions]
+ [onSigning, customStatus, state.selectedAccount?.address, typeOrOptions]
);
const handleExecute = useCallback(
@@ -111,12 +147,23 @@ function useTransaction(
[getParams, mutateAsync]
);
+ const handleReject = (error?: Error) => {
+ notifications.onReject(error);
+ setSigned(false);
+
+ if (error) {
+ console.error(error.message);
+ }
+ };
+
return {
...transactionMutation,
+ isSigned,
+ reject: handleReject,
execute: handleExecute,
executeAsync: handleExecuteAsync
};
}
export { useTransaction };
-export type { UseTransactionResult };
+export type { TransactionResult, UseTransactionResult };
diff --git a/src/utils/hooks/transaction/utils/description.ts b/src/utils/hooks/transaction/utils/description.ts
new file mode 100644
index 0000000000..f79c121332
--- /dev/null
+++ b/src/utils/hooks/transaction/utils/description.ts
@@ -0,0 +1,363 @@
+import { StringMap, TOptions } from 'i18next';
+import { TFunction } from 'react-i18next';
+
+import { shortAddress } from '@/common/utils/utils';
+
+import { Transaction, TransactionActions, TransactionStatus } from '../types';
+
+const getTranslationArgs = (
+ params: TransactionActions,
+ status: TransactionStatus
+): { key: string; args?: TOptions } | undefined => {
+ const isPast = status === TransactionStatus.SUCCESS;
+
+ switch (params.type) {
+ /* START - AMM */
+ case Transaction.AMM_SWAP: {
+ const [trade] = params.args;
+
+ return {
+ key: isPast ? 'transaction.swapped_to' : 'transaction.swapping_to',
+ args: {
+ fromAmount: trade.inputAmount.toHuman(),
+ fromCurrency: trade.inputAmount.currency.ticker,
+ toAmount: trade.outputAmount.toHuman(),
+ toCurrency: trade.outputAmount.currency.ticker
+ }
+ };
+ }
+ case Transaction.AMM_ADD_LIQUIDITY: {
+ const [, pool] = params.args;
+
+ return {
+ key: isPast ? 'transaction.added_liquidity_to_pool' : 'transaction.adding_liquidity_to_pool',
+ args: {
+ poolName: pool.lpToken.ticker
+ }
+ };
+ }
+ case Transaction.AMM_REMOVE_LIQUIDITY: {
+ const [, pool] = params.args;
+
+ return {
+ key: isPast ? 'transaction.removed_liquidity_from_pool' : 'transaction.removing_liquidity_from_pool',
+ args: {
+ poolName: pool.lpToken.ticker
+ }
+ };
+ }
+ case Transaction.AMM_CLAIM_REWARDS: {
+ return {
+ key: isPast ? 'transaction.claimed_pool_rewards' : 'transaction.claiming_pool_rewards'
+ };
+ }
+ /* END - AMM */
+
+ /* START - ISSUE */
+ case Transaction.ISSUE_REQUEST: {
+ const [amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.issued_amount' : 'transaction.issuing_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker
+ }
+ };
+ }
+ case Transaction.ISSUE_EXECUTE: {
+ return {
+ key: isPast ? 'transaction.executed_issue' : 'transaction.executing_issue'
+ };
+ }
+ /* END - ISSUE */
+
+ /* START - REDEEM */
+ case Transaction.REDEEM_CANCEL: {
+ const [redeemId, isReimburse] = params.args;
+
+ const args = {
+ requestId: shortAddress(redeemId)
+ };
+
+ if (isReimburse) {
+ return {
+ key: isPast ? 'transaction.reimbersed_redeem_id' : 'transaction.reimbursing_redeem_id',
+ args
+ };
+ }
+
+ return {
+ key: isPast ? 'transaction.retried_redeem_id' : 'transaction.retrying_redeem_id',
+ args
+ };
+ }
+ case Transaction.REDEEM_BURN: {
+ const [amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.burned_amount' : 'transaction.burning_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker
+ }
+ };
+ }
+ case Transaction.REDEEM_REQUEST: {
+ const [amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.redeemed_amount' : 'transaction.redeeming_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker
+ }
+ };
+ }
+ /* END - REDEEM */
+
+ /* START - REPLACE */
+ case Transaction.REPLACE_REQUEST: {
+ return {
+ key: isPast ? 'transaction.requested_vault_replacement' : 'transaction.requesting_vault_replacement'
+ };
+ }
+ /* END - REPLACE */
+
+ /* START - TOKENS */
+ case Transaction.TOKENS_TRANSFER: {
+ const [destination, amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.transfered_amount_to_address' : 'transaction.transfering_amount_to_address',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker,
+ address: shortAddress(destination)
+ }
+ };
+ }
+ /* END - TOKENS */
+
+ /* START - XCM */
+ case Transaction.XCM_TRANSFER: {
+ const [, fromChain, toChain, , transferAmount] = params.args;
+
+ return {
+ key: isPast
+ ? 'transaction.transfered_amount_from_chain_to_chain'
+ : 'transaction.transfering_amount_from_chain_to_chain',
+ args: {
+ amount: transferAmount.toHuman(),
+ currency: transferAmount.currency.ticker,
+ fromChain: fromChain.toUpperCase(),
+ toChain: toChain.toUpperCase()
+ }
+ };
+ }
+ /* END - XCM */
+
+ /* START - LOANS */
+ case Transaction.LOANS_CLAIM_REWARDS: {
+ return {
+ key: isPast ? 'transaction.claimed_lending_rewards' : 'transaction.claiming_lending_rewards'
+ };
+ }
+ case Transaction.LOANS_BORROW: {
+ const [currency, amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.borrowed_amount' : 'transaction.borrowing_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: currency.ticker
+ }
+ };
+ }
+ case Transaction.LOANS_LEND: {
+ const [currency, amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.lent_amount' : 'transaction.lending_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: currency.ticker
+ }
+ };
+ }
+ case Transaction.LOANS_REPAY: {
+ const [currency, amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.repaid_amount' : 'transaction.repaying_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: currency.ticker
+ }
+ };
+ }
+ case Transaction.LOANS_REPAY_ALL: {
+ const [currency] = params.args;
+
+ return {
+ key: isPast ? 'transaction.repaid' : 'transaction.repaying',
+ args: {
+ currency: currency.ticker
+ }
+ };
+ }
+ case Transaction.LOANS_WITHDRAW: {
+ const [currency, amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.withdrew_amount' : 'transaction.withdrawing_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: currency.ticker
+ }
+ };
+ }
+ case Transaction.LOANS_WITHDRAW_ALL: {
+ const [currency] = params.args;
+
+ return {
+ key: isPast ? 'transaction.withdrew' : 'transaction.withdrawing',
+ args: {
+ currency: currency.ticker
+ }
+ };
+ }
+ case Transaction.LOANS_DISABLE_COLLATERAL: {
+ const [currency] = params.args;
+
+ return {
+ key: isPast ? 'transaction.disabled_loan_as_collateral' : 'transaction.disabling_loan_as_collateral',
+ args: {
+ currency: currency.ticker
+ }
+ };
+ }
+ case Transaction.LOANS_ENABLE_COLLATERAL: {
+ const [currency] = params.args;
+
+ return {
+ key: isPast ? 'transaction.enabled_loan_as_collateral' : 'transaction.enabling_loan_as_collateral',
+ args: {
+ currency: currency.ticker
+ }
+ };
+ }
+ /* END - LOANS */
+
+ /* START - VAULTS */
+ case Transaction.VAULTS_DEPOSIT_COLLATERAL: {
+ const [amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.deposited_amount_to_vault' : 'transaction.depositing_amount_to_vault',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker
+ }
+ };
+ }
+ case Transaction.VAULTS_WITHDRAW_COLLATERAL: {
+ const [amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.withdrew_amount_from_vault' : 'transaction.withdrawing_amount_from_vault',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker
+ }
+ };
+ }
+ case Transaction.VAULTS_REGISTER_NEW_COLLATERAL: {
+ const [collateralAmount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.created_currency_vault' : 'transaction.creating_currency_vault',
+ args: {
+ currency: collateralAmount.currency.ticker
+ }
+ };
+ }
+ /* END - VAULTS */
+
+ /* START - REWARDS */
+ case Transaction.REWARDS_WITHDRAW: {
+ return {
+ key: isPast ? 'transaction.claimed_vault_rewards' : 'transaction.claiming_vault_rewards'
+ };
+ }
+ /* START - REWARDS */
+
+ /* START - ESCROW */
+ case Transaction.ESCROW_CREATE_LOCK: {
+ const [amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.staked_amount' : 'transaction.staking_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker
+ }
+ };
+ }
+ case Transaction.ESCROW_INCREASE_LOCKED_AMOUNT: {
+ const [amount] = params.args;
+
+ return {
+ key: isPast ? 'transaction.added_amount_to_staked_amount' : 'transaction.adding_amount_to_staked_amount',
+ args: {
+ amount: amount.toHuman(),
+ currency: amount.currency.ticker
+ }
+ };
+ }
+ case Transaction.ESCROW_INCREASE_LOCKED_TIME: {
+ return {
+ key: isPast ? 'transaction.increased_stake_lock_time' : 'transaction.increasing_stake_lock_time'
+ };
+ }
+ case Transaction.ESCROW_WITHDRAW: {
+ return {
+ key: isPast ? 'transaction.withdrew_stake' : 'transaction.withdrawing_stake'
+ };
+ }
+ case Transaction.ESCROW_WITHDRAW_REWARDS: {
+ return {
+ key: isPast ? 'transaction.claimed_staking_rewards' : 'transaction.claiming_staking_rewards'
+ };
+ }
+ case Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT: {
+ return {
+ key: isPast
+ ? 'transaction.increased_stake_locked_time_amount'
+ : 'transaction.increasing_stake_locked_time_amount'
+ };
+ }
+ /* END - ESCROW */
+ /* START - VESTING */
+ case Transaction.VESTING_CLAIM: {
+ return {
+ key: isPast ? 'transaction.claimed_vesting' : 'transaction.claiming_vesting'
+ };
+ }
+ /* END - VESTING */
+ }
+};
+
+const getTransactionDescription = (
+ params: TransactionActions,
+ status: TransactionStatus,
+ t: TFunction
+): string | undefined => {
+ const translation = getTranslationArgs(params, status);
+
+ if (!translation) return;
+
+ return t(translation.key, translation.args);
+};
+
+export { getTransactionDescription };
diff --git a/src/utils/hooks/transaction/utils/submit.ts b/src/utils/hooks/transaction/utils/submit.ts
index d1c832b023..ceb930d7db 100644
--- a/src/utils/hooks/transaction/utils/submit.ts
+++ b/src/utils/hooks/transaction/utils/submit.ts
@@ -6,12 +6,10 @@ import { ExtrinsicStatus } from '@polkadot/types/interfaces/author';
import { ISubmittableResult } from '@polkadot/types/types';
import { TransactionEvents } from '../types';
+import { TransactionResult } from '../use-transaction';
type HandleTransactionResult = { result: ISubmittableResult; unsubscribe: () => void };
-// When passing { nonce: -1 } to signAndSend the API will use system.accountNextIndex to determine the nonce
-const transactionOptions = { nonce: -1 };
-
const handleTransaction = async (
account: AddressOrPair,
extrinsicData: ExtrinsicData,
@@ -27,7 +25,7 @@ const handleTransaction = async (
let unsubscribe: () => void;
(extrinsicData.extrinsic as SubmittableExtrinsic<'promise'>)
- .signAndSend(account, transactionOptions, callback)
+ .signAndSend(account, { nonce: -1 }, callback)
.then((unsub) => (unsubscribe = unsub))
.catch((error) => reject(error));
@@ -43,7 +41,7 @@ const handleTransaction = async (
isComplete = expectedStatus === result.status.type;
}
- if (isComplete) {
+ if (isComplete || result.status.isUsurped) {
resolve({ unsubscribe, result });
}
}
@@ -53,25 +51,20 @@ const handleTransaction = async (
const getErrorMessage = (api: ApiPromise, dispatchError: DispatchError) => {
const { isModule, asModule, isBadOrigin } = dispatchError;
- // Construct error message
- const message = 'The transaction failed.';
-
// Runtime error in one of the parachain modules
if (isModule) {
// for module errors, we have the section indexed, lookup
const decoded = api.registry.findMetaError(asModule);
const { docs, name, section } = decoded;
- return message.concat(` The error code is ${section}.${name}. ${docs.join(' ')}`);
+ return `The error code is ${section}.${name}. ${docs.join(' ')}.`;
}
// Bad origin
if (isBadOrigin) {
- return message.concat(
- ` The error is caused by using an incorrect account. The error code is BadOrigin ${dispatchError}.`
- );
+ return `The error is caused by using an incorrect account. The error code is BadOrigin ${dispatchError}.`;
}
- return message.concat(` The error is ${dispatchError}.`);
+ return `The error is ${dispatchError}.`;
};
/**
@@ -89,19 +82,29 @@ const submitTransaction = async (
extrinsicData: ExtrinsicData,
expectedStatus?: ExtrinsicStatus['type'],
callbacks?: TransactionEvents
-): Promise => {
+): Promise => {
const { result, unsubscribe } = await handleTransaction(account, extrinsicData, expectedStatus, callbacks);
unsubscribe();
+ let error: Error | undefined;
+
const { dispatchError } = result;
if (dispatchError) {
- const message = getErrorMessage(api, dispatchError);
- throw new Error(message);
+ error = new Error(getErrorMessage(api, dispatchError));
+ }
+
+ // TODO: determine a description to when transaction ends up usurped
+ if (result.status.isUsurped) {
+ error = new Error();
}
- return result;
+ return {
+ status: error ? 'error' : 'success',
+ data: result,
+ error
+ };
};
export { submitTransaction };
diff --git a/src/utils/hooks/use-copy-tooltip.tsx b/src/utils/hooks/use-copy-tooltip.tsx
index 36ec13ccd4..42ce3c1fcc 100644
--- a/src/utils/hooks/use-copy-tooltip.tsx
+++ b/src/utils/hooks/use-copy-tooltip.tsx
@@ -15,6 +15,7 @@ type CopyTooltipResult = {
};
};
+// FIX: is openning tooltip too fast
const useCopyTooltip = (props?: CopyTooltipProp): CopyTooltipResult => {
const { t } = useTranslation();
diff --git a/src/utils/hooks/use-countdown.ts b/src/utils/hooks/use-countdown.ts
new file mode 100644
index 0000000000..49e74aa05d
--- /dev/null
+++ b/src/utils/hooks/use-countdown.ts
@@ -0,0 +1,67 @@
+import { useCallback, useEffect, useState } from 'react';
+import { useInterval } from 'react-use';
+
+import { theme } from '@/component-library';
+import { useWindowFocus } from '@/utils/hooks/use-window-focus';
+
+type UseCountdownProps = {
+ value?: number;
+ timeout?: number;
+ disabled?: boolean;
+ onEndCountdown?: () => void;
+};
+
+type UseCountdownResult = {
+ value: number;
+ start: () => void;
+ stop: () => void;
+};
+
+const useCountdown = ({
+ value = 100,
+ timeout = 8000,
+ disabled,
+ onEndCountdown
+}: UseCountdownProps): UseCountdownResult => {
+ const windowFocused = useWindowFocus();
+
+ const [countdown, setProgress] = useState(value);
+ const [isRunning, setRunning] = useState(disabled);
+
+ // handles the countdown
+ useInterval(
+ () => setProgress((prev) => prev - 1),
+ isRunning ? timeout / theme.transition.duration.duration100 : null
+ );
+
+ const handleStartCountdown = useCallback(() => {
+ const shouldRun = !disabled && countdown > 0;
+ setRunning(shouldRun);
+ }, [countdown, disabled]);
+
+ const handleStopCountdown = () => setRunning(false);
+
+ useEffect(() => {
+ if (isRunning && countdown === 0) {
+ onEndCountdown?.();
+ handleStopCountdown();
+ }
+ }, [isRunning, countdown, onEndCountdown]);
+
+ useEffect(() => {
+ if (windowFocused && !disabled) {
+ handleStartCountdown();
+ } else {
+ handleStopCountdown();
+ }
+ }, [windowFocused, handleStartCountdown, disabled]);
+
+ return {
+ value: countdown,
+ start: handleStartCountdown,
+ stop: handleStopCountdown
+ };
+};
+
+export { useCountdown };
+export type { UseCountdownProps, UseCountdownResult };
diff --git a/src/utils/hooks/use-sign-message.ts b/src/utils/hooks/use-sign-message.ts
index 78ef745257..ef57dbdfd0 100644
--- a/src/utils/hooks/use-sign-message.ts
+++ b/src/utils/hooks/use-sign-message.ts
@@ -96,6 +96,7 @@ const useSignMessage = (): UseSignMessageResult => {
queryFn: () => selectedAccount && getSignature(selectedAccount)
});
+ // TODO: add new notification
const signMessageMutation = useMutation((account: KeyringPair) => postSignature(account), {
onError: (_, variables) => {
setSignature(variables.address, false);
diff --git a/src/utils/hooks/use-update-query-parameters.ts b/src/utils/hooks/use-update-query-parameters.ts
index 80ba8ff7ed..9970299c52 100644
--- a/src/utils/hooks/use-update-query-parameters.ts
+++ b/src/utils/hooks/use-update-query-parameters.ts
@@ -13,7 +13,7 @@ const useUpdateQueryParameters = (): ((newQueryParameters: QueryParameters) => v
...newQueryParameters
};
- history.push({
+ history.replace({
...location,
search: queryString.stringify(queryParameters)
});
diff --git a/src/utils/hooks/use-window-focus.ts b/src/utils/hooks/use-window-focus.ts
new file mode 100644
index 0000000000..27d63b7c54
--- /dev/null
+++ b/src/utils/hooks/use-window-focus.ts
@@ -0,0 +1,26 @@
+import { useEffect, useState } from 'react';
+
+const hasFocus = () => typeof document !== 'undefined' && document.hasFocus();
+
+const useWindowFocus = (): boolean => {
+ const [focused, setFocused] = useState(hasFocus); // Focus for first render
+
+ useEffect(() => {
+ setFocused(hasFocus()); // Focus for additional renders
+
+ const onFocus = () => setFocused(true);
+ const onBlur = () => setFocused(false);
+
+ window.addEventListener('focus', onFocus);
+ window.addEventListener('blur', onBlur);
+
+ return () => {
+ window.removeEventListener('focus', onFocus);
+ window.removeEventListener('blur', onBlur);
+ };
+ }, []);
+
+ return focused;
+};
+
+export { useWindowFocus };
diff --git a/yarn.lock b/yarn.lock
index f662a8bcb9..8b0e526b36 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2099,10 +2099,10 @@
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
-"@interlay/bridge@^0.3.11":
- version "0.3.11"
- resolved "https://registry.yarnpkg.com/@interlay/bridge/-/bridge-0.3.11.tgz#45b2f3bb44d5e7eb1777ba82cfdf1a2f5dbf2b1d"
- integrity sha512-HMgUlSFw5wOR7Qi+JxrDeY8TqoybRd7MWdXUqswDpiCgc0WZGTSDK+2NmuKRgDjRYoly0xIpzpkb8oek6v/JQw==
+"@interlay/bridge@^0.3.13":
+ version "0.3.13"
+ resolved "https://registry.yarnpkg.com/@interlay/bridge/-/bridge-0.3.13.tgz#8add2a9d8a811ea3bbe73498bf3ebc19cd279ec6"
+ integrity sha512-LXXomxfI2n1h2MHeN8woRaQgh+gLKKlHfH1oTBAMyKPpSI7tTvtrE2XwIKt+Qg1TvmukRngtmwWtEXh760Dtkw==
dependencies:
"@acala-network/api" "4.1.8-13"
"@acala-network/sdk" "4.1.8-13"
@@ -2120,15 +2120,16 @@
dependencies:
axios "^0.21.1"
-"@interlay/interbtc-api@2.2.4":
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/@interlay/interbtc-api/-/interbtc-api-2.2.4.tgz#28b429d066d35f77fdc72f4cf57e2452507c37f7"
- integrity sha512-cJxSE7J41JPE8QhV0YiLCJEfvpv9JcSWmieITTSOWQCW8GFFXnSTU0iPA2Tgw6s9ea3uxoM2DLGhlDQL8c0ktw==
+"@interlay/interbtc-api@2.3.3":
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/@interlay/interbtc-api/-/interbtc-api-2.3.3.tgz#e75f0aa64ae6db604d4314cadf307fe09d128741"
+ integrity sha512-q5uDFejEJoy4ZC5sc2YSmksILDA14qR/A+oQonMJGIh2F8k58YHdC8Zpp+6ayYUjp13rwkeQQwoBS1kwBFFdqg==
dependencies:
"@interlay/esplora-btc-api" "0.4.0"
"@interlay/interbtc-types" "1.12.0"
"@interlay/monetary-js" "0.7.3"
"@polkadot/api" "9.14.2"
+ "@types/bitcoinjs-lib" "^5.0.0"
big.js "6.1.1"
bitcoin-core "^3.0.0"
bitcoinjs-lib "^5.2.0"
@@ -2156,15 +2157,6 @@
big.js "6.1.1"
typescript "^4.3.2"
-"@interlay/monetary-js@0.7.3":
- version "0.7.3"
- resolved "https://registry.yarnpkg.com/@interlay/monetary-js/-/monetary-js-0.7.3.tgz#0bf4c56b15fde2fd0573e6cac185b0703f368133"
- integrity sha512-LbCtLRNjl1/LO8R1ay6lJwKgOC/J40YywF+qSuQ7hEjLIkAslY5dLH11heQgQW9hOmqCSS5fTUQWXhmYQr6Ksg==
- dependencies:
- "@types/big.js" "6.1.2"
- big.js "6.1.1"
- typescript "^4.3.2"
-
"@internationalized/date@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.0.1.tgz#66332e9ca8f59b7be010ca65d946bca430ba4b66"
@@ -2574,6 +2566,11 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
+"@noble/hashes@^1.2.0":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9"
+ integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==
+
"@noble/secp256k1@1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
@@ -6004,6 +6001,13 @@
resolved "https://registry.yarnpkg.com/@types/big.js/-/big.js-6.1.2.tgz#68a952b629a6aaa2b5855a2f63363d1e77f6dd91"
integrity sha512-h24JIZ52rvSvi2jkpYDk2yLH99VzZoCJiSfDWwjst7TwJVuXN61XVCUlPCzRl7mxKEMsGf8z42Q+J4TZwU3z2w==
+"@types/bitcoinjs-lib@^5.0.0":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@types/bitcoinjs-lib/-/bitcoinjs-lib-5.0.0.tgz#f2905d673d1c4b42a91d64d95f1c464f1a48cb56"
+ integrity sha512-9zXjgmH2E8qEZ9gQ9GH+I6Cze3bweQbyXtR/X4RD3SdR5I4jdRPvmBrKmjegV3HZG03KNricjEoq+lQUtIXCKQ==
+ dependencies:
+ bitcoinjs-lib "*"
+
"@types/bn.js@^5.1.1":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682"
@@ -7727,6 +7731,11 @@ base-x@^3.0.2:
dependencies:
safe-buffer "^5.0.1"
+base-x@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a"
+ integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==
+
base64-js@^1.0.2, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -7762,6 +7771,11 @@ bech32@1.1.4, bech32@^1.1.2:
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==
+bech32@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355"
+ integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==
+
before-after-hook@^2.2.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c"
@@ -7831,6 +7845,11 @@ bip174@^2.0.1:
resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.0.1.tgz#39cf8ca99e50ce538fb762589832f4481d07c254"
integrity sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ==
+bip174@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.0.tgz#cd3402581feaa5116f0f00a0eaee87a5843a2d30"
+ integrity sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==
+
bip32@^2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134"
@@ -7869,6 +7888,18 @@ bitcoin-ops@^1.3.0, bitcoin-ops@^1.4.0:
resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278"
integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==
+bitcoinjs-lib@*:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.1.tgz#3950c29fd96f07131e41a36a265b17ebd02b4a11"
+ integrity sha512-FYihfgTk29lt1eK2y48OtuarEDUnTprNBW3ctT8yHiOhvmeS3DzAVG6gI0VCvMkydz6UdlXlYNWIPqGD0SUYRQ==
+ dependencies:
+ "@noble/hashes" "^1.2.0"
+ bech32 "^2.0.0"
+ bip174 "^2.1.0"
+ bs58check "^3.0.1"
+ typeforce "^1.11.3"
+ varuint-bitcoin "^1.1.2"
+
bitcoinjs-lib@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-5.2.0.tgz#caf8b5efb04274ded1b67e0706960b93afb9d332"
@@ -8121,6 +8152,13 @@ bs58@^4.0.0:
dependencies:
base-x "^3.0.2"
+bs58@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279"
+ integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==
+ dependencies:
+ base-x "^4.0.0"
+
bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1, bs58check@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc"
@@ -8130,6 +8168,14 @@ bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1, bs58check@^2.1.2:
create-hash "^1.1.0"
safe-buffer "^5.1.2"
+bs58check@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-3.0.1.tgz#2094d13720a28593de1cba1d8c4e48602fdd841c"
+ integrity sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==
+ dependencies:
+ "@noble/hashes" "^1.2.0"
+ bs58 "^5.0.0"
+
bser@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
@@ -17669,16 +17715,14 @@ react-table@^7.6.3:
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==
-react-toastify@^6.0.5:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-6.2.0.tgz#f2d76747c70b9de91f71f253d9feae6b53dc836c"
- integrity sha512-XpjFrcBhQ0/nBOL4syqgP/TywFnOyxmstYLWgSQWcj39qpp+WU4vPt3C/ayIDx7RFyxRWfzWTdR2qOcDGo7G0w==
+react-toastify@^9.1.2:
+ version "9.1.2"
+ resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.1.2.tgz#293aa1f952240129fe485ae5cb2f8d09c652cf3f"
+ integrity sha512-PBfzXO5jMGEtdYR5jxrORlNZZe/EuOkwvwKijMatsZZm8IZwLj01YvobeJYNjFcA6uy6CVrx2fzL9GWbhWPTDA==
dependencies:
clsx "^1.1.1"
- prop-types "^15.7.2"
- react-transition-group "^4.4.1"
-react-transition-group@^4.4.1, react-transition-group@^4.4.5:
+react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
@@ -20553,7 +20597,7 @@ value-equal@^1.0.1:
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
-varuint-bitcoin@^1.0.4:
+varuint-bitcoin@^1.0.4, varuint-bitcoin@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92"
integrity sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==