From 182fba62d4ab69435c90ed84838be9d47f9338bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peter=20Slan=C3=BD?=
<47864599+peterslany@users.noreply.github.com>
Date: Fri, 19 May 2023 11:15:10 +0200
Subject: [PATCH 001/220] feat: redirect when access from forbidden country is
detected (#1209)
---
src/components/Geoblock/Geoblock.tsx | 14 +++++++++
src/config/links.ts | 5 ++++
src/index.tsx | 43 +++++++++++++++-------------
src/utils/hooks/use-geoblocking.ts | 22 ++++++++++++++
4 files changed, 64 insertions(+), 20 deletions(-)
create mode 100644 src/components/Geoblock/Geoblock.tsx
create mode 100644 src/utils/hooks/use-geoblocking.ts
diff --git a/src/components/Geoblock/Geoblock.tsx b/src/components/Geoblock/Geoblock.tsx
new file mode 100644
index 0000000000..43493562d3
--- /dev/null
+++ b/src/components/Geoblock/Geoblock.tsx
@@ -0,0 +1,14 @@
+import React, { ReactNode } from 'react';
+
+import { useGeoblocking } from '@/utils/hooks/use-geoblocking';
+
+type Props = {
+ children: ReactNode;
+};
+
+const GeoblockingWrapper = ({ children }: Props): JSX.Element => {
+ useGeoblocking();
+ return <>{children}>;
+};
+
+export { GeoblockingWrapper };
diff --git a/src/config/links.ts b/src/config/links.ts
index 8f1d7557b1..b2d84b6a02 100644
--- a/src/config/links.ts
+++ b/src/config/links.ts
@@ -21,8 +21,13 @@ const INTERLAY_DOS_AND_DONTS_DOCS_LINK = 'https://docs.interlay.io/#/vault/insta
const BANXA_LINK = 'http://talisman.banxa.com/';
+const GEOBLOCK_API_ENDPOINT = '/check_access';
+const GEOBLOCK_REDIRECTION_LINK = 'https://www.interlay.io/geoblock';
+
export {
BANXA_LINK,
+ GEOBLOCK_API_ENDPOINT,
+ GEOBLOCK_REDIRECTION_LINK,
INTERLAY_COMPANY_LINK,
INTERLAY_CROWDLOAN_LINK,
INTERLAY_DISCORD_LINK,
diff --git a/src/index.tsx b/src/index.tsx
index 4a8ef1ba46..4901f7d9a1 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -18,6 +18,7 @@ import ThemeWrapper from '@/parts/ThemeWrapper';
import { Subscriptions } from '@/utils/hooks/api/tokens/use-balances-subscription';
import App from './App';
+import { GeoblockingWrapper } from './components/Geoblock/Geoblock';
import reportWebVitals from './reportWebVitals';
import { store } from './store';
@@ -30,26 +31,28 @@ const queryClient = new QueryClient();
// MEMO: temporarily removed React.StrictMode. We should add back when react-spectrum handles
// it across their library. (Issue: https://github.com/adobe/react-spectrum/issues/779#issuecomment-1353734729)
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
document.getElementById('root')
);
diff --git a/src/utils/hooks/use-geoblocking.ts b/src/utils/hooks/use-geoblocking.ts
new file mode 100644
index 0000000000..6ca37d3a02
--- /dev/null
+++ b/src/utils/hooks/use-geoblocking.ts
@@ -0,0 +1,22 @@
+import { useEffect } from 'react';
+
+import { GEOBLOCK_API_ENDPOINT, GEOBLOCK_REDIRECTION_LINK } from '@/config/links';
+
+const useGeoblocking = (): void => {
+ useEffect(() => {
+ const checkCountry = async () => {
+ try {
+ const response = await fetch(GEOBLOCK_API_ENDPOINT);
+ if (response.status === 403) {
+ console.log('Access from forbidden country detected, user will be redirected.');
+ window.location.replace(GEOBLOCK_REDIRECTION_LINK);
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ };
+ checkCountry();
+ }, []);
+};
+
+export { useGeoblocking };
From ab6a5549984e92641bfe0701027dd25edeb4a10b Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Mon, 22 May 2023 09:15:34 +0100
Subject: [PATCH 002/220] Feature/updated transfer UI (#876)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 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
---
package.json | 3 +-
src/assets/icons/ArrowRightCircle.tsx | 13 +
src/assets/icons/index.ts | 1 +
src/assets/locales/en/translation.json | 1 +
src/component-library/Label/Label.style.tsx | 1 +
.../Select/SelectTrigger.tsx | 2 +-
src/component-library/index.tsx | 2 +
src/component-library/theme/theme.ts | 10 +-
src/components/AccountSelect/AccountLabel.tsx | 34 ++
src/components/AccountSelect/AccountList.tsx | 58 +++
.../AccountSelect/AccountListModal.tsx | 34 ++
.../AccountSelect/AccountSelect.style.tsx | 68 +++
.../AccountSelect/AccountSelect.tsx | 99 +++++
src/components/AccountSelect/index.tsx | 2 +
src/components/index.tsx | 2 +
src/config/relay-chains.tsx | 14 +-
.../Chains/ChainSelector/index.tsx | 64 ---
src/legacy-components/Chains/index.tsx | 24 --
src/lib/form/index.tsx | 6 +
src/lib/form/schemas/index.ts | 9 +
src/lib/form/schemas/transfers.ts | 54 +++
src/lib/form/use-form.tsx | 4 +-
.../CrossChainTransferForm.styles.tsx | 42 ++
.../CrossChainTransferForm.tsx | 293 +++++++++++++
.../components/ChainIcon/ChainIcon.tsx | 6 +-
.../components/ChainIcon/icons/Bifrost.tsx | 38 ++
.../components/ChainIcon/icons/Heiko.tsx | 47 ++
.../components/ChainIcon/icons/Hydra.tsx | 32 ++
.../components/ChainIcon/icons/Karura.tsx | 51 +++
.../components/ChainIcon/icons/index.ts | 4 +
.../ChainSelect/ChainSelect.style.tsx | 29 ++
.../components/ChainSelect/ChainSelect.tsx | 53 +++
.../components/ChainSelect/index.tsx | 2 +
.../components/index.tsx | 6 +-
.../Transfer/CrossChainTransferForm/index.tsx | 378 +---------------
src/pages/Transfer/Transfer.style.tsx | 9 +
src/pages/Transfer/index.tsx | 112 +----
src/types/chains.d.ts | 10 +
src/types/chains.types.ts | 3 -
src/utils/hooks/api/xcm/use-xcm-bridge.ts | 182 +++++---
src/utils/hooks/api/xcm/xcm-endpoints.ts | 33 ++
yarn.lock | 402 ++++++++++++------
42 files changed, 1478 insertions(+), 759 deletions(-)
create mode 100644 src/assets/icons/ArrowRightCircle.tsx
create mode 100644 src/components/AccountSelect/AccountLabel.tsx
create mode 100644 src/components/AccountSelect/AccountList.tsx
create mode 100644 src/components/AccountSelect/AccountListModal.tsx
create mode 100644 src/components/AccountSelect/AccountSelect.style.tsx
create mode 100644 src/components/AccountSelect/AccountSelect.tsx
create mode 100644 src/components/AccountSelect/index.tsx
delete mode 100644 src/legacy-components/Chains/ChainSelector/index.tsx
delete mode 100644 src/legacy-components/Chains/index.tsx
create mode 100644 src/lib/form/schemas/transfers.ts
create mode 100644 src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.styles.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Bifrost.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Heiko.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Hydra.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Karura.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.style.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/index.tsx
create mode 100644 src/pages/Transfer/Transfer.style.tsx
create mode 100644 src/types/chains.d.ts
delete mode 100644 src/types/chains.types.ts
create mode 100644 src/utils/hooks/api/xcm/xcm-endpoints.ts
diff --git a/package.json b/package.json
index 973c99347b..1362974961 100644
--- a/package.json
+++ b/package.json
@@ -6,11 +6,12 @@
"@craco/craco": "^6.1.1",
"@headlessui/react": "^1.1.1",
"@heroicons/react": "^2.0.0",
- "@interlay/bridge": "^0.2.4",
+ "@interlay/bridge": "^0.3.9",
"@interlay/interbtc-api": "2.2.2",
"@interlay/monetary-js": "0.7.2",
"@polkadot/api": "9.14.2",
"@polkadot/extension-dapp": "0.44.1",
+ "@polkadot/react-identicon": "^2.11.1",
"@polkadot/ui-keyring": "^2.9.7",
"@reach/tooltip": "^0.16.0",
"@react-aria/accordion": "^3.0.0-alpha.14",
diff --git a/src/assets/icons/ArrowRightCircle.tsx b/src/assets/icons/ArrowRightCircle.tsx
new file mode 100644
index 0000000000..8cef0e7841
--- /dev/null
+++ b/src/assets/icons/ArrowRightCircle.tsx
@@ -0,0 +1,13 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const ArrowRightCircle = forwardRef((props, ref) => (
+
+
+
+));
+
+ArrowRightCircle.displayName = 'ArrowRightCircle';
+
+export { ArrowRightCircle };
diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts
index 6393c7b057..bf537097cc 100644
--- a/src/assets/icons/index.ts
+++ b/src/assets/icons/index.ts
@@ -1,4 +1,5 @@
export { ArrowRight } from './ArrowRight';
+export { ArrowRightCircle } from './ArrowRightCircle';
export { ArrowsUpDown } from './ArrowsUpDown';
export { ArrowTopRightOnSquare } from './ArrowTopRightOnSquare';
export { ChevronDown } from './ChevronDown';
diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json
index 439791e0dd..2959b1bd8d 100644
--- a/src/assets/locales/en/translation.json
+++ b/src/assets/locales/en/translation.json
@@ -602,6 +602,7 @@
},
"forms": {
"please_enter_your_field": "Please enter your {{field}}",
+ "please_select_your_field": "Please select your {{field}}",
"please_enter_the_amount_to": "Please enter the amount to {{field}}",
"amount_must_be_at_least": "Amount to {{action}} must be at least {{amount}} {{token}}",
"amount_must_be_at_most": "Amount to {{action}} must be at most {{amount}}",
diff --git a/src/component-library/Label/Label.style.tsx b/src/component-library/Label/Label.style.tsx
index 55c1a22cb1..d3209b0dee 100644
--- a/src/component-library/Label/Label.style.tsx
+++ b/src/component-library/Label/Label.style.tsx
@@ -8,6 +8,7 @@ const StyledLabel = styled.label`
font-size: ${theme.text.xs};
color: ${theme.colors.textTertiary};
padding: ${theme.spacing.spacing1} 0;
+ align-self: flex-start;
`;
export { StyledLabel };
diff --git a/src/component-library/Select/SelectTrigger.tsx b/src/component-library/Select/SelectTrigger.tsx
index 2229fd8f1c..8c2b52e27e 100644
--- a/src/component-library/Select/SelectTrigger.tsx
+++ b/src/component-library/Select/SelectTrigger.tsx
@@ -8,7 +8,7 @@ import { Sizes } from '../utils/prop-types';
import { StyledChevronDown, StyledTrigger, StyledTriggerValue } from './Select.style';
type Props = {
- as: any;
+ as?: any;
size?: Sizes;
isOpen?: boolean;
hasError?: boolean;
diff --git a/src/component-library/index.tsx b/src/component-library/index.tsx
index b91ec169da..d385b075d1 100644
--- a/src/component-library/index.tsx
+++ b/src/component-library/index.tsx
@@ -32,6 +32,8 @@ export type { ModalBodyProps, ModalDividerProps, ModalFooterProps, ModalHeaderPr
export { Modal, ModalBody, ModalDivider, ModalFooter, ModalHeader } from './Modal';
export type { NumberInputProps } from './NumberInput';
export { NumberInput } from './NumberInput';
+export type { SelectProps } from './Select';
+export { Item, Select } from './Select';
export type { StackProps } from './Stack';
export { Stack } from './Stack';
export type { SwitchProps } from './Switch';
diff --git a/src/component-library/theme/theme.ts b/src/component-library/theme/theme.ts
index 86e1295e36..c7a21c8791 100644
--- a/src/component-library/theme/theme.ts
+++ b/src/component-library/theme/theme.ts
@@ -510,15 +510,19 @@ const theme = {
size: {
small: {
padding: 'var(--spacing-1)',
- text: 'var(--text-s)'
+ text: 'var(--text-s)',
+ // TODO: to be determined
+ maxHeight: 'calc(var(--spacing-6) - 1px)'
},
medium: {
padding: 'var(--spacing-2)',
- text: 'var(--text-base)'
+ text: 'var(--text-base)',
+ maxHeight: 'calc(var(--spacing-10) - 1px)'
},
large: {
padding: 'var(--spacing-5) var(--spacing-2)',
- text: 'var(--text-lg)'
+ text: 'var(--text-lg)',
+ maxHeight: 'calc(var(--spacing-16) - 1px)'
}
}
}
diff --git a/src/components/AccountSelect/AccountLabel.tsx b/src/components/AccountSelect/AccountLabel.tsx
new file mode 100644
index 0000000000..965d1128c2
--- /dev/null
+++ b/src/components/AccountSelect/AccountLabel.tsx
@@ -0,0 +1,34 @@
+import Identicon from '@polkadot/react-identicon';
+
+import { FlexProps } from '@/component-library/Flex';
+
+import { StyledAccountLabelAddress, StyledAccountLabelName, StyledAccountLabelWrapper } from './AccountSelect.style';
+
+type Props = {
+ isSelected?: boolean;
+ address: string;
+ name?: string;
+};
+
+type InheritAttrs = Omit;
+
+type AccountLabelProps = Props & InheritAttrs;
+
+const AccountLabel = ({ isSelected, address, name, ...props }: AccountLabelProps): JSX.Element => (
+
+
+
+ {name && (
+
+ {name}
+
+ )}
+
+ {address}
+
+
+
+);
+
+export { AccountLabel };
+export type { AccountLabelProps };
diff --git a/src/components/AccountSelect/AccountList.tsx b/src/components/AccountSelect/AccountList.tsx
new file mode 100644
index 0000000000..ca12d8b20e
--- /dev/null
+++ b/src/components/AccountSelect/AccountList.tsx
@@ -0,0 +1,58 @@
+import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';
+
+import { ListItem, ListProps } from '@/component-library/List';
+
+import { AccountLabel } from './AccountLabel';
+import { StyledList } from './AccountSelect.style';
+
+type Props = {
+ items: InjectedAccountWithMeta[];
+ selectedAccount?: string;
+ onSelectionChange?: (account: string) => void;
+};
+
+type InheritAttrs = Omit;
+
+type AccountListProps = Props & InheritAttrs;
+
+const AccountList = ({ items, selectedAccount, onSelectionChange, ...props }: AccountListProps): JSX.Element => {
+ const handleSelectionChange: ListProps['onSelectionChange'] = (key) => {
+ const [selectedKey] = [...key];
+
+ if (!selectedKey) return;
+
+ onSelectionChange?.(selectedKey as string);
+ };
+
+ return (
+
+ {items.map((item) => {
+ const accountText = item.address;
+
+ const isSelected = selectedAccount === accountText;
+
+ return (
+
+
+
+ );
+ })}
+
+ );
+};
+
+export { AccountList };
+export type { AccountListProps };
diff --git a/src/components/AccountSelect/AccountListModal.tsx b/src/components/AccountSelect/AccountListModal.tsx
new file mode 100644
index 0000000000..07b211e2a9
--- /dev/null
+++ b/src/components/AccountSelect/AccountListModal.tsx
@@ -0,0 +1,34 @@
+import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';
+
+import { Modal, ModalBody, ModalHeader, ModalProps } from '@/component-library/Modal';
+
+import { AccountList } from './AccountList';
+
+type Props = {
+ accounts: InjectedAccountWithMeta[];
+ onSelectionChange?: (account: string) => void;
+ selectedAccount?: string;
+};
+
+type InheritAttrs = Omit;
+
+type AccountListModalProps = Props & InheritAttrs;
+
+const AccountListModal = ({
+ selectedAccount,
+ accounts,
+ onSelectionChange,
+ ...props
+}: AccountListModalProps): JSX.Element => (
+
+
+ Select Account
+
+
+
+
+
+);
+
+export { AccountListModal };
+export type { AccountListModalProps };
diff --git a/src/components/AccountSelect/AccountSelect.style.tsx b/src/components/AccountSelect/AccountSelect.style.tsx
new file mode 100644
index 0000000000..850c26e0fc
--- /dev/null
+++ b/src/components/AccountSelect/AccountSelect.style.tsx
@@ -0,0 +1,68 @@
+import styled from 'styled-components';
+
+import { ChevronDown } from '@/assets/icons';
+import { Flex } from '@/component-library/Flex';
+import { List } from '@/component-library/List';
+import { Span } from '@/component-library/Text';
+import { theme } from '@/component-library/theme';
+
+type StyledClickableProps = {
+ $isClickable: boolean;
+};
+
+type StyledListItemSelectedLabelProps = {
+ $isSelected: boolean;
+};
+
+const StyledAccount = styled.span`
+ font-size: ${theme.text.s};
+ color: ${theme.colors.textPrimary};
+ overflow: hidden;
+ text-overflow: ellipsis;
+`;
+
+const StyledAccountSelect = styled(Flex)`
+ background-color: ${theme.tokenInput.endAdornment.bg};
+ border-radius: ${theme.rounded.md};
+ font-size: ${theme.text.xl2};
+ padding: ${theme.spacing.spacing3};
+ cursor: ${({ $isClickable }) => $isClickable && 'pointer'};
+ height: 3rem;
+ width: auto;
+ overflow: hidden;
+`;
+
+const StyledChevronDown = styled(ChevronDown)`
+ margin-left: ${theme.spacing.spacing1};
+`;
+
+const StyledAccountLabelAddress = styled(Span)`
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+`;
+
+const StyledAccountLabelName = styled(StyledAccountLabelAddress)`
+ color: ${({ $isSelected }) =>
+ $isSelected ? theme.tokenInput.list.item.selected.text : theme.tokenInput.list.item.default.text};
+`;
+
+const StyledList = styled(List)`
+ overflow: auto;
+ padding: 0 ${theme.modal.body.paddingX} ${theme.modal.body.paddingY} ${theme.modal.body.paddingX};
+`;
+
+const StyledAccountLabelWrapper = styled(Flex)`
+ flex-grow: 1;
+ overflow: hidden;
+`;
+
+export {
+ StyledAccount,
+ StyledAccountLabelAddress,
+ StyledAccountLabelName,
+ StyledAccountLabelWrapper,
+ StyledAccountSelect,
+ StyledChevronDown,
+ StyledList
+};
diff --git a/src/components/AccountSelect/AccountSelect.tsx b/src/components/AccountSelect/AccountSelect.tsx
new file mode 100644
index 0000000000..b9712491b5
--- /dev/null
+++ b/src/components/AccountSelect/AccountSelect.tsx
@@ -0,0 +1,99 @@
+import { InjectedAccountWithMeta } from '@polkadot/extension-inject/types';
+import { useLabel } from '@react-aria/label';
+import { chain, mergeProps } from '@react-aria/utils';
+import { VisuallyHidden } from '@react-aria/visually-hidden';
+import { forwardRef, InputHTMLAttributes, ReactNode, useEffect, useState } from 'react';
+
+import { Flex, Label } from '@/component-library';
+import { SelectTrigger } from '@/component-library/Select';
+import { useDOMRef } from '@/component-library/utils/dom';
+import { triggerChangeEvent } from '@/component-library/utils/input';
+
+import { AccountLabel } from './AccountLabel';
+import { AccountListModal } from './AccountListModal';
+
+const getAccount = (accountValue?: string, accounts?: InjectedAccountWithMeta[]) =>
+ accounts?.find((account) => account.address === accountValue);
+
+type Props = {
+ value: string;
+ defaultValue?: string;
+ icons?: string[];
+ isDisabled?: boolean;
+ label?: ReactNode;
+ accounts?: InjectedAccountWithMeta[];
+};
+
+type NativeAttrs = Omit & { ref?: any }, keyof Props>;
+
+type AccountSelectProps = Props & NativeAttrs;
+
+const AccountSelect = forwardRef(
+ ({ value: valueProp, defaultValue = '', accounts, disabled, label, className, ...props }, ref): JSX.Element => {
+ const inputRef = useDOMRef(ref);
+
+ const [isOpen, setOpen] = useState(false);
+ const [value, setValue] = useState(defaultValue);
+
+ const { fieldProps, labelProps } = useLabel({ ...props, label });
+
+ useEffect(() => {
+ if (valueProp === undefined) return;
+
+ setValue(valueProp);
+ }, [valueProp]);
+
+ const handleAccount = (account: string) => {
+ triggerChangeEvent(inputRef, account);
+ setValue(account);
+ };
+
+ const handleClose = () => setOpen(false);
+
+ const isDisabled = !accounts?.length || disabled;
+
+ const selectedAccount = getAccount(value, accounts);
+
+ return (
+ <>
+
+ {label && {label} }
+ setOpen(true)}
+ disabled={isDisabled}
+ {...mergeProps(fieldProps, {
+ // MEMO: when the button is blurred, a focus and blur is executed on the input
+ // so that validation gets triggered.
+ onBlur: () => {
+ if (!isOpen) {
+ inputRef.current?.focus();
+ inputRef.current?.blur();
+ }
+ }
+ })}
+ >
+ {selectedAccount && }
+
+
+
+
+
+ {accounts && (
+
+ )}
+ >
+ );
+ }
+);
+
+AccountSelect.displayName = 'AccountSelect';
+
+export { AccountSelect };
+export type { AccountSelectProps };
diff --git a/src/components/AccountSelect/index.tsx b/src/components/AccountSelect/index.tsx
new file mode 100644
index 0000000000..83aa80ccbd
--- /dev/null
+++ b/src/components/AccountSelect/index.tsx
@@ -0,0 +1,2 @@
+export type { AccountSelectProps } from './AccountSelect';
+export { AccountSelect } from './AccountSelect';
diff --git a/src/components/index.tsx b/src/components/index.tsx
index 51545514ef..5149bf3220 100644
--- a/src/components/index.tsx
+++ b/src/components/index.tsx
@@ -1,3 +1,5 @@
+export type { AccountSelectProps } from './AccountSelect';
+export { AccountSelect } from './AccountSelect';
export type { AuthCTAProps } from './AuthCTA';
export { AuthCTA } from './AuthCTA';
export type { AssetCellProps, BalanceCellProps, CellProps, TableProps } from './DataGrid';
diff --git a/src/config/relay-chains.tsx b/src/config/relay-chains.tsx
index d358c50be6..de9302d73f 100644
--- a/src/config/relay-chains.tsx
+++ b/src/config/relay-chains.tsx
@@ -1,4 +1,9 @@
+import { AcalaAdapter, KaruraAdapter } from '@interlay/bridge/build/adapters/acala';
+import { AstarAdapter } from '@interlay/bridge/build/adapters/astar';
+import { BifrostAdapter } from '@interlay/bridge/build/adapters/bifrost';
+import { HydraAdapter } from '@interlay/bridge/build/adapters/hydradx';
import { InterlayAdapter, KintsugiAdapter } from '@interlay/bridge/build/adapters/interlay';
+import { HeikoAdapter, ParallelAdapter } from '@interlay/bridge/build/adapters/parallel';
import { KusamaAdapter, PolkadotAdapter } from '@interlay/bridge/build/adapters/polkadot';
import { StatemineAdapter, StatemintAdapter } from '@interlay/bridge/build/adapters/statemint';
import { BaseCrossChainAdapter } from '@interlay/bridge/build/base-chain-adapter';
@@ -156,6 +161,10 @@ switch (process.env.REACT_APP_RELAY_CHAIN_NAME) {
TRANSACTION_FEE_AMOUNT = newMonetaryAmount(0.2, GOVERNANCE_TOKEN, true);
XCM_ADAPTERS = {
interlay: new InterlayAdapter(),
+ acala: new AcalaAdapter(),
+ astar: new AstarAdapter(),
+ hydra: new HydraAdapter(),
+ parallel: new ParallelAdapter(),
polkadot: new PolkadotAdapter(),
statemint: new StatemintAdapter()
};
@@ -199,7 +208,10 @@ switch (process.env.REACT_APP_RELAY_CHAIN_NAME) {
XCM_ADAPTERS = {
kintsugi: new KintsugiAdapter(),
kusama: new KusamaAdapter(),
- statemine: new StatemineAdapter()
+ karura: new KaruraAdapter(),
+ statemine: new StatemineAdapter(),
+ bifrost: new BifrostAdapter(),
+ heiko: new HeikoAdapter()
};
SS58_PREFIX = 2;
break;
diff --git a/src/legacy-components/Chains/ChainSelector/index.tsx b/src/legacy-components/Chains/ChainSelector/index.tsx
deleted file mode 100644
index aa3c8d0e7c..0000000000
--- a/src/legacy-components/Chains/ChainSelector/index.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import clsx from 'clsx';
-
-import Select, {
- SELECT_VARIANTS,
- SelectBody,
- SelectButton,
- SelectCheck,
- SelectLabel,
- SelectOption,
- SelectOptions,
- SelectText
-} from '@/legacy-components/Select';
-import { XCMChains } from '@/types/chains.types';
-
-interface ChainOption {
- type: XCMChains;
- name: string;
- icon: JSX.Element;
-}
-
-interface Props {
- chainOptions: Array;
- selectedChain: ChainOption | undefined;
- label: string;
- onChange?: (chain: ChainOption) => void;
-}
-
-const ChainSelector = ({ chainOptions, selectedChain, label, onChange }: Props): JSX.Element => (
-
- {({ open }) => (
- <>
- {label}
-
-
-
- {selectedChain?.icon}
- {selectedChain?.name}
-
-
-
- {chainOptions.map((chainOption: ChainOption) => {
- return (
-
- {({ selected, active }) => (
- <>
-
- {chainOption.icon}
- {chainOption.name}
-
- {selected ? : null}
- >
- )}
-
- );
- })}
-
-
- >
- )}
-
-);
-
-export type { ChainOption };
-export default ChainSelector;
diff --git a/src/legacy-components/Chains/index.tsx b/src/legacy-components/Chains/index.tsx
deleted file mode 100644
index 16873ecdbf..0000000000
--- a/src/legacy-components/Chains/index.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import ChainSelector, { ChainOption } from './ChainSelector';
-
-interface Props {
- label: string;
- chainOptions: Array | undefined;
- onChange?: (chain: ChainOption) => void;
- selectedChain: ChainOption | undefined;
-}
-
-const Chains = ({ onChange, chainOptions, label, selectedChain }: Props): JSX.Element | null => {
- if (!selectedChain || !chainOptions) {
- return null;
- }
-
- return (
-
-
-
- );
-};
-
-export type { ChainOption };
-
-export default Chains;
diff --git a/src/lib/form/index.tsx b/src/lib/form/index.tsx
index 60fbf3b63b..af76026d2e 100644
--- a/src/lib/form/index.tsx
+++ b/src/lib/form/index.tsx
@@ -1,3 +1,9 @@
+export type {
+ CreateVaultFormData,
+ CrossChainTransferFormData,
+ DepositLiquidityPoolFormData,
+ LoanFormData
+} from './schemas';
export * from './schemas';
export type { FormErrors } from './use-form';
export { useForm } from './use-form';
diff --git a/src/lib/form/schemas/index.ts b/src/lib/form/schemas/index.ts
index 1d584fcd5d..a792ef0fbe 100644
--- a/src/lib/form/schemas/index.ts
+++ b/src/lib/form/schemas/index.ts
@@ -15,5 +15,14 @@ export {
SwapErrorMessage,
swapSchema
} from './swap';
+export type { CrossChainTransferFormData, CrossChainTransferValidationParams } from './transfers';
+export {
+ CROSS_CHAIN_TRANSFER_AMOUNT_FIELD,
+ CROSS_CHAIN_TRANSFER_FROM_FIELD,
+ CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD,
+ CROSS_CHAIN_TRANSFER_TO_FIELD,
+ CROSS_CHAIN_TRANSFER_TOKEN_FIELD,
+ crossChainTransferSchema
+} from './transfers';
export type { CreateVaultFormData } from './vaults';
export { CREATE_VAULT_DEPOSIT_FIELD, createVaultSchema } from './vaults';
diff --git a/src/lib/form/schemas/transfers.ts b/src/lib/form/schemas/transfers.ts
new file mode 100644
index 0000000000..a6fe5c5f47
--- /dev/null
+++ b/src/lib/form/schemas/transfers.ts
@@ -0,0 +1,54 @@
+import { ChainName } from '@interlay/bridge';
+import { TFunction } from 'react-i18next';
+
+import yup, { MaxAmountValidationParams, MinAmountValidationParams } from '../yup.custom';
+
+const CROSS_CHAIN_TRANSFER_FROM_FIELD = 'transfer-from';
+const CROSS_CHAIN_TRANSFER_TO_FIELD = 'transfer-to';
+const CROSS_CHAIN_TRANSFER_AMOUNT_FIELD = 'transfer-amount';
+const CROSS_CHAIN_TRANSFER_TOKEN_FIELD = 'transfer-token';
+const CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD = 'transfer-account';
+
+type CrossChainTransferFormData = {
+ [CROSS_CHAIN_TRANSFER_FROM_FIELD]?: ChainName;
+ [CROSS_CHAIN_TRANSFER_TO_FIELD]?: ChainName;
+ [CROSS_CHAIN_TRANSFER_AMOUNT_FIELD]?: string;
+ [CROSS_CHAIN_TRANSFER_TOKEN_FIELD]?: string;
+ [CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD]?: string;
+};
+
+type CrossChainTransferValidationParams = {
+ [CROSS_CHAIN_TRANSFER_AMOUNT_FIELD]: Partial & Partial;
+};
+
+// MEMO: until now, only CROSS_CHAIN_TRANSFER_AMOUNT_FIELD needs validation
+const crossChainTransferSchema = (params: CrossChainTransferValidationParams, t: TFunction): yup.ObjectSchema =>
+ yup.object().shape({
+ [CROSS_CHAIN_TRANSFER_AMOUNT_FIELD]: yup
+ .string()
+ .requiredAmount('transfer')
+ .maxAmount(params[CROSS_CHAIN_TRANSFER_AMOUNT_FIELD] as MaxAmountValidationParams)
+ .minAmount(params[CROSS_CHAIN_TRANSFER_AMOUNT_FIELD] as MinAmountValidationParams, 'transfer'),
+ [CROSS_CHAIN_TRANSFER_FROM_FIELD]: yup
+ .string()
+ .required(t('forms.please_enter_your_field', { field: 'source chain' })),
+ [CROSS_CHAIN_TRANSFER_TO_FIELD]: yup
+ .string()
+ .required(t('forms.please_enter_your_field', { field: 'destination chain' })),
+ [CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD]: yup
+ .string()
+ .required(t('forms.please_enter_your_field', { field: 'destination' })),
+ [CROSS_CHAIN_TRANSFER_TOKEN_FIELD]: yup
+ .string()
+ .required(t('forms.please_select_your_field', { field: 'transfer token' }))
+ });
+
+export {
+ CROSS_CHAIN_TRANSFER_AMOUNT_FIELD,
+ CROSS_CHAIN_TRANSFER_FROM_FIELD,
+ CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD,
+ CROSS_CHAIN_TRANSFER_TO_FIELD,
+ CROSS_CHAIN_TRANSFER_TOKEN_FIELD,
+ crossChainTransferSchema
+};
+export type { CrossChainTransferFormData, CrossChainTransferValidationParams };
diff --git a/src/lib/form/use-form.tsx b/src/lib/form/use-form.tsx
index 48c668f13e..7e62e97bd9 100644
--- a/src/lib/form/use-form.tsx
+++ b/src/lib/form/use-form.tsx
@@ -15,7 +15,7 @@ type GetFieldProps = (
withErrorMessage?: boolean
) => FieldInputProps & { errorMessage?: string | string[] };
-type UseFormAgrs = FormikConfig & {
+type UseFormArgs = FormikConfig & {
disableValidation?: boolean;
getFieldProps?: GetFieldProps;
};
@@ -25,7 +25,7 @@ const useForm = ({
validationSchema,
disableValidation,
...args
-}: UseFormAgrs) => {
+}: UseFormArgs) => {
const { t } = useTranslation();
const { validateForm, values, getFieldProps: getFormikFieldProps, ...formik } = useFormik({
...args,
diff --git a/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.styles.tsx b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.styles.tsx
new file mode 100644
index 0000000000..1c1ee33b0b
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.styles.tsx
@@ -0,0 +1,42 @@
+import styled from 'styled-components';
+
+import { ArrowRightCircle } from '@/assets/icons';
+import { Dl, Flex, theme } from '@/component-library';
+
+import { ChainSelect } from './components';
+
+const StyledDl = styled(Dl)`
+ background-color: ${theme.card.bg.secondary};
+ padding: ${theme.spacing.spacing4};
+ font-size: ${theme.text.xs};
+ border-radius: ${theme.rounded.rg};
+`;
+
+const StyledArrowRightCircle = styled(ArrowRightCircle)`
+ transform: rotate(90deg);
+ align-self: center;
+
+ @media (min-width: 30em) {
+ transform: rotate(0deg);
+ margin-top: 1.75rem;
+ }
+`;
+
+const ChainSelectSection = styled(Flex)`
+ flex-direction: column;
+
+ @media (min-width: 30em) {
+ flex-direction: row;
+ gap: ${theme.spacing.spacing4};
+ }
+`;
+
+const StyledSourceChainSelect = styled(ChainSelect)`
+ margin-bottom: ${theme.spacing.spacing3};
+
+ @media (min-width: 30em) {
+ margin-bottom: 0;
+ }
+`;
+
+export { ChainSelectSection, StyledArrowRightCircle, StyledDl, StyledSourceChainSelect };
diff --git a/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
new file mode 100644
index 0000000000..85b5cd0eb1
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
@@ -0,0 +1,293 @@
+import { FixedPointNumber } from '@acala-network/sdk-core';
+import { ChainName, CrossChainTransferParams } 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';
+import { AccountSelect, AuthCTA } from '@/components';
+import {
+ CROSS_CHAIN_TRANSFER_AMOUNT_FIELD,
+ CROSS_CHAIN_TRANSFER_FROM_FIELD,
+ CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD,
+ CROSS_CHAIN_TRANSFER_TO_FIELD,
+ CROSS_CHAIN_TRANSFER_TOKEN_FIELD,
+ CrossChainTransferFormData,
+ crossChainTransferSchema,
+ CrossChainTransferValidationParams,
+ isFormDisabled,
+ useForm
+} 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 useAccountId from '@/utils/hooks/use-account-id';
+
+import { ChainSelect } from './components';
+import {
+ ChainSelectSection,
+ StyledArrowRightCircle,
+ StyledDl,
+ StyledSourceChainSelect
+} from './CrossChainTransferForm.styles';
+
+const CrossChainTransferForm = (): JSX.Element => {
+ const [destinationChains, setDestinationChains] = useState([]);
+ const [transferableTokens, setTransferableTokens] = useState([]);
+ const [currentToken, setCurrentToken] = useState();
+
+ const prices = useGetPrices();
+ const { t } = useTranslation();
+ const { getCurrencyFromTicker } = useGetCurrencies(true);
+
+ const accountId = useAccountId();
+ const { accounts } = useSubstrateSecureState();
+
+ const { data, getDestinationChains, originatingChains, getAvailableTokens } = useXCMBridge();
+
+ const schema: CrossChainTransferValidationParams = {
+ [CROSS_CHAIN_TRANSFER_AMOUNT_FIELD]: {
+ minAmount: currentToken
+ ? newMonetaryAmount(currentToken.minTransferAmount, getCurrencyFromTicker(currentToken.value), true)
+ : undefined,
+ maxAmount: currentToken
+ ? newMonetaryAmount(currentToken.balance, getCurrencyFromTicker(currentToken.value), true)
+ : undefined
+ }
+ };
+
+ const mutateXcmTransfer = async (formData: CrossChainTransferFormData) => {
+ if (!data || !formData || !currentToken) return;
+
+ const { signer } = await web3FromAddress(formData[CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD] as string);
+ 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 transferAmount = newMonetaryAmount(
+ form.values[CROSS_CHAIN_TRANSFER_AMOUNT_FIELD] || 0,
+ getCurrencyFromTicker(currentToken.value),
+ 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 handleSubmit = (formData: CrossChainTransferFormData) => {
+ xcmTransferMutation.mutate(formData);
+ };
+
+ const form = useForm({
+ initialValues: {
+ [CROSS_CHAIN_TRANSFER_AMOUNT_FIELD]: '',
+ [CROSS_CHAIN_TRANSFER_TOKEN_FIELD]: '',
+ [CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD]: accountId?.toString() || ''
+ },
+ onSubmit: handleSubmit,
+ 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);
+
+ const destinationChains = getDestinationChains(chain);
+
+ setDestinationChains(destinationChains);
+ form.setFieldValue(CROSS_CHAIN_TRANSFER_TO_FIELD, destinationChains[0].id);
+ };
+
+ const handleDestinationChainChange = async (chain: ChainName, name: string) => {
+ if (!accountId) return;
+
+ form.setFieldValue(name, chain);
+
+ setTokenData(chain);
+ };
+
+ const handleTickerChange = (ticker: string, name: string) => {
+ form.setFieldValue(name, ticker);
+ setCurrentToken(transferableTokens.find((token) => token.value === ticker));
+ };
+
+ const handleDestinationAccountChange: ChangeEventHandler = (e) => {
+ form.setFieldValue(CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD, e.target.value);
+ };
+
+ const setTokenData = useCallback(
+ async (destination: ChainName) => {
+ if (!accountId || !form) return;
+
+ const tokens = await getAvailableTokens(
+ form.values[CROSS_CHAIN_TRANSFER_FROM_FIELD] as ChainName,
+ destination,
+ accountId.toString(),
+ form.values[CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD] as string
+ );
+
+ if (!tokens) return;
+
+ setTransferableTokens(tokens);
+
+ // Update token data if selected token exists in new data
+ const token = tokens.find((token) => token.value === currentToken?.value) || tokens[0];
+
+ setCurrentToken(token);
+ form.setFieldValue(CROSS_CHAIN_TRANSFER_TOKEN_FIELD, token.value);
+ },
+ [accountId, currentToken, form, getAvailableTokens]
+ );
+
+ const transferMonetaryAmount = currentToken
+ ? newSafeMonetaryAmount(
+ form.values[CROSS_CHAIN_TRANSFER_AMOUNT_FIELD] || 0,
+ getCurrencyFromTicker(currentToken.value),
+ true
+ )
+ : 0;
+
+ const valueUSD = transferMonetaryAmount
+ ? convertMonetaryAmountToValueInUSD(
+ transferMonetaryAmount,
+ getTokenPrice(prices, currentToken?.value as string)?.usd
+ )
+ : 0;
+
+ const isCTADisabled = isFormDisabled(form) || form.values[CROSS_CHAIN_TRANSFER_AMOUNT_FIELD] === '';
+ const amountShouldValidate = form.values[CROSS_CHAIN_TRANSFER_AMOUNT_FIELD] !== '';
+
+ useEffect(() => {
+ if (!originatingChains?.length) return;
+
+ // This prevents a render loop caused by setFieldValue
+ if (form.values[CROSS_CHAIN_TRANSFER_FROM_FIELD]) return;
+
+ const destinationChains = getDestinationChains(originatingChains[0].id);
+
+ form.setFieldValue(CROSS_CHAIN_TRANSFER_FROM_FIELD, originatingChains[0].id);
+ form.setFieldValue(CROSS_CHAIN_TRANSFER_TO_FIELD, destinationChains[0].id);
+
+ setDestinationChains(destinationChains);
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [originatingChains]);
+
+ useEffect(() => {
+ if (!destinationChains?.length) return;
+ if (!accountId) return;
+
+ setTokenData(destinationChains[0].id);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [accountId, destinationChains]);
+
+ if (!originatingChains || !destinationChains || !transferableTokens.length) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+export default CrossChainTransferForm;
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx
index 3a6611d6e2..339e83d336 100644
--- a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx
@@ -3,12 +3,16 @@ import { forwardRef, ForwardRefExoticComponent, RefAttributes } from 'react';
import { IconProps } from '@/component-library/Icon';
import { StyledFallbackIcon } from './ChainIcon.style';
-import { INTERLAY, KINTSUGI, KUSAMA, POLKADOT, STATEMINE, STATEMINT } from './icons';
+import { BIFROST, HEIKO, HYDRA, INTERLAY, KARURA, KINTSUGI, KUSAMA, POLKADOT, STATEMINE, STATEMINT } from './icons';
type ChainComponent = ForwardRefExoticComponent>;
const chainsIcon: Record = {
+ BIFROST,
+ HEIKO,
+ HYDRA,
INTERLAY,
+ KARURA,
KINTSUGI,
KUSAMA,
POLKADOT,
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Bifrost.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Bifrost.tsx
new file mode 100644
index 0000000000..6abf1e24bd
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Bifrost.tsx
@@ -0,0 +1,38 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const BIFROST = forwardRef((props, ref) => (
+
+ BIFROST
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+));
+
+BIFROST.displayName = 'KINTSUGI';
+
+export { BIFROST };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Heiko.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Heiko.tsx
new file mode 100644
index 0000000000..39afe777c3
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Heiko.tsx
@@ -0,0 +1,47 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const HEIKO = forwardRef((props, ref) => (
+
+ HEIKO
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+));
+
+HEIKO.displayName = 'HEIKO';
+
+export { HEIKO };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Hydra.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Hydra.tsx
new file mode 100644
index 0000000000..58f99aa0e3
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Hydra.tsx
@@ -0,0 +1,32 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const HYDRA = forwardRef((props, ref) => (
+
+ HYDRA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+));
+
+HYDRA.displayName = 'INTERLAY';
+
+export { HYDRA };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Karura.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Karura.tsx
new file mode 100644
index 0000000000..2306754c2f
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Karura.tsx
@@ -0,0 +1,51 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const KARURA = forwardRef((props, ref) => (
+
+ KARURA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+));
+
+KARURA.displayName = 'KINTSUGI';
+
+export { KARURA };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts
index df6ed50da9..e5d13a7014 100644
--- a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts
@@ -1,4 +1,8 @@
+export { BIFROST } from './Bifrost';
+export { HEIKO } from './Heiko';
+export { HYDRA } from './Hydra';
export { INTERLAY } from './Interlay';
+export { KARURA } from './Karura';
export { KINTSUGI } from './Kintsugi';
export { KUSAMA } from './Kusama';
export { POLKADOT } from './Polkadot';
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.style.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.style.tsx
new file mode 100644
index 0000000000..fe740a65eb
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.style.tsx
@@ -0,0 +1,29 @@
+import styled from 'styled-components';
+
+import { Flex } from '@/component-library/Flex';
+import { Span } from '@/component-library/Text';
+import { theme } from '@/component-library/theme';
+
+type StyledListItemSelectedLabelProps = {
+ $isSelected: boolean;
+};
+
+const StyledChain = styled.span`
+ font-size: ${theme.text.s};
+ color: ${theme.colors.textPrimary};
+ overflow: hidden;
+ text-overflow: ellipsis;
+`;
+
+const StyledListItemLabel = styled(Span)`
+ color: ${({ $isSelected }) =>
+ $isSelected ? theme.tokenInput.list.item.selected.text : theme.tokenInput.list.item.default.text};
+ text-overflow: ellipsis;
+ overflow: hidden;
+`;
+
+const StyledListChainWrapper = styled(Flex)`
+ overflow: hidden;
+`;
+
+export { StyledChain, StyledListChainWrapper, StyledListItemLabel };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.tsx
new file mode 100644
index 0000000000..1506d894e6
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/ChainSelect.tsx
@@ -0,0 +1,53 @@
+import { Flex } from '@/component-library';
+import { Item, Select, SelectProps } from '@/component-library';
+import { useSelectModalContext } from '@/component-library/Select/SelectModalContext';
+import { ChainData } from '@/types/chains';
+
+import { ChainIcon } from '../ChainIcon';
+import { StyledChain, StyledListChainWrapper, StyledListItemLabel } from './ChainSelect.style';
+
+type ChainSelectProps = Omit, 'children' | 'type'>;
+
+const ListItem = ({ data }: { data: ChainData }) => {
+ const isSelected = useSelectModalContext().selectedItem?.key === data.id;
+
+ return (
+
+
+
+ {data.display}
+
+
+ );
+};
+
+const Value = ({ data }: { data: ChainData }) => (
+
+
+ {data.display}
+
+);
+
+const ChainSelect = ({ ...props }: ChainSelectProps): JSX.Element => {
+ return (
+
+
+ {...props}
+ type='modal'
+ renderValue={(item) => }
+ modalTitle='Select Token'
+ >
+ {(data: ChainData) => (
+ -
+
+
+ )}
+
+
+ );
+};
+
+ChainSelect.displayName = 'ChainSelect';
+
+export { ChainSelect };
+export type { ChainSelectProps };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/index.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/index.tsx
new file mode 100644
index 0000000000..2e2851d120
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainSelect/index.tsx
@@ -0,0 +1,2 @@
+export type { ChainSelectProps } from './ChainSelect';
+export { ChainSelect } from './ChainSelect';
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/index.tsx b/src/pages/Transfer/CrossChainTransferForm/components/index.tsx
index 98ff9e319b..6cb7e0e8e5 100644
--- a/src/pages/Transfer/CrossChainTransferForm/components/index.tsx
+++ b/src/pages/Transfer/CrossChainTransferForm/components/index.tsx
@@ -1,4 +1,4 @@
-import { ChainIcon, ChainIconProps } from './ChainIcon';
+import { ChainSelect, ChainSelectProps } from './ChainSelect';
-export { ChainIcon };
-export type { ChainIconProps };
+export { ChainSelect };
+export type { ChainSelectProps };
diff --git a/src/pages/Transfer/CrossChainTransferForm/index.tsx b/src/pages/Transfer/CrossChainTransferForm/index.tsx
index 0996fe956d..b2417c4fb7 100644
--- a/src/pages/Transfer/CrossChainTransferForm/index.tsx
+++ b/src/pages/Transfer/CrossChainTransferForm/index.tsx
@@ -1,377 +1,3 @@
-import { FixedPointNumber } from '@acala-network/sdk-core';
-import { BasicToken, CrossChainTransferParams } from '@interlay/bridge';
-import { CurrencyExt, DefaultTransactionAPI, newMonetaryAmount } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { ApiPromise } from '@polkadot/api';
-import { web3FromAddress } from '@polkadot/extension-dapp';
-import Big from 'big.js';
-import * as React from 'react';
-import { useEffect } from 'react';
-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 { firstValueFrom } from 'rxjs';
+import CrossChainTransferForm from './CrossChainTransferForm';
-import { ParachainStatus, StoreType } from '@/common/types/util.types';
-import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils';
-import { AuthCTA } from '@/components';
-import Accounts from '@/legacy-components/Accounts';
-import AvailableBalanceUI from '@/legacy-components/AvailableBalanceUI';
-import Chains, { ChainOption } from '@/legacy-components/Chains';
-import ErrorFallback from '@/legacy-components/ErrorFallback';
-import ErrorModal from '@/legacy-components/ErrorModal';
-import FormTitle from '@/legacy-components/FormTitle';
-import PrimaryColorEllipsisLoader from '@/legacy-components/PrimaryColorEllipsisLoader';
-import TokenField from '@/legacy-components/TokenField';
-import { KeyringPair, useSubstrateSecureState } from '@/lib/substrate';
-import STATUSES from '@/utils/constants/statuses';
-import { getExtrinsicStatus } from '@/utils/helpers/extrinsic';
-import { getTokenPrice } from '@/utils/helpers/prices';
-import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
-import { useXCMBridge } from '@/utils/hooks/api/xcm/use-xcm-bridge';
-
-import { ChainIcon } from './components';
-
-const TRANSFER_AMOUNT = 'transfer-amount';
-
-type CrossChainTransferFormData = {
- [TRANSFER_AMOUNT]: string;
-};
-
-const CrossChainTransferForm = (): JSX.Element => {
- const [fromChains, setFromChains] = React.useState | undefined>(undefined);
- const [fromChain, setFromChain] = React.useState(undefined);
- const [toChains, setToChains] = React.useState | undefined>(undefined);
- const [toChain, setToChain] = React.useState(undefined);
- const [transferableBalance, setTransferableBalance] = React.useState(undefined);
- const [destination, setDestination] = React.useState(undefined);
- const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
- const [submitError, setSubmitError] = React.useState(null);
- const [approxUsdValue, setApproxUsdValue] = React.useState('0');
- const [currency, setCurrency] = React.useState(undefined);
-
- // TODO: this will need to be refactored when we support multiple currencies
- // per channel, but so will the UI so better to handle this then.
- const { t } = useTranslation();
- const prices = useGetPrices();
-
- const { XCMBridge, XCMProvider } = useXCMBridge();
-
- const {
- register,
- handleSubmit,
- formState: { errors },
- reset,
- setValue,
- trigger
- } = useForm({
- mode: 'onChange'
- });
-
- const { selectedAccount } = useSubstrateSecureState();
- const { parachainStatus } = useSelector((state: StoreType) => state.general);
-
- useEffect(() => {
- if (!XCMBridge) return;
- if (!fromChain) return;
- if (!toChain) return;
- // TODO: This handles a race condition. Will need to be fixed properly
- // when supporting USDT
- if (fromChain.name === toChain.name) return;
-
- const tokens = XCMBridge.router.getAvailableTokens({ from: fromChain.type, to: toChain.type });
-
- const supportedCurrency = XCMBridge.findAdapter(fromChain.type).getToken(tokens[0], fromChain.type);
-
- setCurrency(supportedCurrency);
- }, [fromChain, toChain, XCMBridge]);
-
- useEffect(() => {
- if (!XCMBridge) return;
- if (!fromChain) return;
- if (!toChain) return;
- if (!selectedAccount) return;
- if (!currency) return;
- if (!destination) return;
- // TODO: This handles a race condition. Will need to be fixed properly
- // when supporting USDT
- if (toChain.type === fromChain.type) return;
-
- const getMaxTransferrable = async () => {
- // TODO: Resolve type issue caused by version mismatch
- // and remove casting to `any`
- const inputConfigs: any = await firstValueFrom(
- XCMBridge.findAdapter(fromChain.type).subscribeInputConfigs({
- to: toChain?.type,
- token: currency.symbol,
- address: destination.address,
- signer: selectedAccount.address
- }) as any
- );
-
- const maxInputToBig = Big(inputConfigs.maxInput.toString());
-
- // Never show less than zero
- const transferableBalance = inputConfigs.maxInput < inputConfigs.minInput ? 0 : maxInputToBig;
-
- setTransferableBalance(newMonetaryAmount(transferableBalance, (currency as unknown) as CurrencyExt, true));
- };
-
- getMaxTransferrable();
- }, [currency, fromChain, toChain, selectedAccount, destination, XCMBridge]);
-
- useEffect(() => {
- if (!XCMBridge) return;
- if (!XCMProvider) return;
-
- const availableFromChains: Array = XCMBridge.adapters.map((adapter: any) => {
- return {
- type: adapter.chain.id,
- name: adapter.chain.display,
- icon:
- };
- });
-
- setFromChains(availableFromChains);
- setFromChain(availableFromChains[0]);
- }, [XCMBridge, XCMProvider]);
-
- useEffect(() => {
- if (!XCMBridge) return;
- if (!fromChain) return;
-
- const destinationChains = XCMBridge.router.getDestinationChains({ from: fromChain.type });
-
- const availableToChains = destinationChains.map((chain: any) => {
- return {
- type: chain.id,
- name: chain.display,
- icon:
- };
- });
-
- setToChains(availableToChains);
- setToChain(availableToChains[0]);
- }, [fromChain, XCMBridge]);
-
- const onSubmit = async (data: CrossChainTransferFormData) => {
- if (!selectedAccount) return;
- if (!destination) return;
-
- try {
- setSubmitStatus(STATUSES.PENDING);
-
- if (!XCMBridge || !fromChain || !toChain) return;
-
- const sendTransaction = async () => {
- const { signer } = await web3FromAddress(selectedAccount.address.toString());
-
- const adapter = XCMBridge.findAdapter(fromChain.type);
-
- const apiPromise = (XCMProvider.getApiPromise(fromChain.type) as unknown) as ApiPromise;
-
- apiPromise.setSigner(signer);
-
- // TODO: Version mismatch with ApiPromise type. This should be inferred.
- adapter.setApi(apiPromise as any);
-
- const transferAmount = new MonetaryAmount((currency as unknown) as CurrencyExt, data[TRANSFER_AMOUNT]);
- const transferAmountString = transferAmount.toString(true);
- const transferAmountDecimals = transferAmount.currency.decimals;
-
- // TODO: Transaction is in promise form
- const tx: any = adapter.createTx({
- amount: FixedPointNumber.fromInner(transferAmountString, transferAmountDecimals),
- to: toChain.type,
- token: currency?.symbol,
- address: destination.address
- } as CrossChainTransferParams);
-
- const inBlockStatus = getExtrinsicStatus('InBlock');
-
- await DefaultTransactionAPI.sendLogged(apiPromise, selectedAccount.address, tx, undefined, inBlockStatus);
- };
-
- await sendTransaction();
-
- setSubmitStatus(STATUSES.RESOLVED);
- } catch (error) {
- setSubmitStatus(STATUSES.REJECTED);
- setSubmitError(error);
- }
- };
-
- const handleUpdateUsdAmount = (value: string) => {
- if (!value) return;
-
- const tokenAmount = newMonetaryAmount(value, (currency as unknown) as CurrencyExt, true);
-
- const usd = currency
- ? displayMonetaryAmountInUSDFormat(tokenAmount, getTokenPrice(prices, currency.symbol)?.usd)
- : '0';
-
- setApproxUsdValue(usd);
- };
-
- const validateTransferAmount = async (value: string) => {
- if (!toChain) return;
- if (!fromChain) return;
- if (!destination) return;
- if (!selectedAccount) return;
- if (!currency) return;
-
- const balanceMonetaryAmount = newMonetaryAmount(transferableBalance, (currency as unknown) as CurrencyExt, true);
- const transferAmount = newMonetaryAmount(value, (currency as unknown) as CurrencyExt, true);
-
- // TODO: Resolve type issue caused by version mismatch
- // and remove casting to `any`
- const inputConfigs: any = await firstValueFrom(
- XCMBridge.findAdapter(fromChain.type).subscribeInputConfigs({
- to: toChain?.type,
- token: currency?.symbol,
- address: destination.address,
- signer: selectedAccount.address
- }) as any
- );
-
- const minInputToBig = Big(inputConfigs.minInput.toString());
- const maxInputToBig = Big(inputConfigs.maxInput.toString());
-
- if (balanceMonetaryAmount.lt(transferAmount)) {
- return t('xcm_transfer.validation.insufficient_funds');
- } else if (minInputToBig.gt(transferableBalance)) {
- return t('xcm_transfer.validation.balance_lower_minimum');
- } else if (minInputToBig.gt(transferAmount.toBig())) {
- return t('xcm_transfer.validation.transfer_more_than_minimum', {
- amount: `${inputConfigs.minInput.toString()} ${currency.symbol}`
- });
- } else if (maxInputToBig.lt(transferAmount.toBig())) {
- return t('xcm_transfer.validation.transfer_less_than_maximum', {
- amount: `${inputConfigs.maxInput.toString()} ${currency.symbol}`
- });
- } else {
- return undefined;
- }
- };
-
- const handleSetFromChain = (chain: ChainOption) => {
- // Return from function is user clicks on current chain option
- if (chain === fromChain) return;
-
- // Note: this is a workaround but ok for now. Component will be refactored
- // when we introduce support for multiple currencies per channel
- setCurrency(undefined);
- setToChain(undefined);
- setValue(TRANSFER_AMOUNT, '');
- setFromChain(chain);
- };
-
- const handleSetToChain = (chain: ChainOption) => {
- // Return from function is user clicks on current chain option
- if (chain === toChain) return;
-
- // Note: this is a workaround but ok for now. Component will be refactored
- // when we introduce support for multiple currencies per channel
- setCurrency(undefined);
- setValue(TRANSFER_AMOUNT, '');
- setToChain(chain);
- };
-
- const handleClickBalance = () => {
- setValue(TRANSFER_AMOUNT, transferableBalance.toString());
- handleUpdateUsdAmount(transferableBalance);
- trigger(TRANSFER_AMOUNT);
- };
-
- // 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]: ''
- });
- }, [submitStatus, reset, t]);
-
- if (!XCMBridge || !toChain || !fromChain || !currency) {
- return ;
- }
-
- return (
- <>
-
- {submitStatus === STATUSES.REJECTED && submitError && (
- {
- setSubmitStatus(STATUSES.IDLE);
- setSubmitError(null);
- }}
- title='Error'
- description={typeof submitError === 'string' ? submitError : submitError.message}
- />
- )}
- >
- );
-};
-
-export default withErrorBoundary(CrossChainTransferForm, {
- FallbackComponent: ErrorFallback,
- onReset: () => {
- window.location.reload();
- }
-});
+export default CrossChainTransferForm;
diff --git a/src/pages/Transfer/Transfer.style.tsx b/src/pages/Transfer/Transfer.style.tsx
new file mode 100644
index 0000000000..4ec3066518
--- /dev/null
+++ b/src/pages/Transfer/Transfer.style.tsx
@@ -0,0 +1,9 @@
+import styled from 'styled-components';
+
+import { theme } from '@/component-library';
+
+const StyledWrapper = styled.div`
+ margin-top: ${theme.spacing.spacing6};
+`;
+
+export { StyledWrapper };
diff --git a/src/pages/Transfer/index.tsx b/src/pages/Transfer/index.tsx
index ed2ea96777..2dbbc7ac0b 100644
--- a/src/pages/Transfer/index.tsx
+++ b/src/pages/Transfer/index.tsx
@@ -1,111 +1,31 @@
import clsx from 'clsx';
-import * as React from 'react';
-import { useTranslation } from 'react-i18next';
-import Hr1 from '@/legacy-components/hrs/Hr1';
+import { Flex, Tabs, TabsItem } from '@/component-library';
import Panel from '@/legacy-components/Panel';
-import InterlayRouterLink from '@/legacy-components/UI/InterlayRouterLink';
-import InterlayTabGroup, {
- InterlayTab,
- InterlayTabList,
- InterlayTabPanel,
- InterlayTabPanels
-} from '@/legacy-components/UI/InterlayTabGroup';
-import WarningBanner from '@/legacy-components/WarningBanner';
import MainContainer from '@/parts/MainContainer';
-import { QUERY_PARAMETERS } from '@/utils/constants/links';
-import { POLKADOT } from '@/utils/constants/relay-chain-names';
-import useQueryParams from '@/utils/hooks/use-query-params';
-import useUpdateQueryParameters, { QueryParameters } from '@/utils/hooks/use-update-query-parameters';
import CrossChainTransferForm from './CrossChainTransferForm';
+import { StyledWrapper } from './Transfer.style';
import TransferForm from './TransferForm';
-const TAB_IDS = Object.freeze({
- transfer: 'transfer',
- crossChainTransfer: 'crossChainTransfer'
-});
-
-const TAB_ITEMS = [
- {
- id: TAB_IDS.transfer,
- label: 'transfer'
- },
- {
- id: TAB_IDS.crossChainTransfer,
- label: 'cross chain transfer'
- }
-];
-
const Transfer = (): JSX.Element | null => {
- const queryParams = useQueryParams();
- const selectedTabId = queryParams.get(QUERY_PARAMETERS.TAB);
- const updateQueryParameters = useUpdateQueryParameters();
-
- const { t } = useTranslation();
-
- const updateQueryParametersRef = React.useRef<(newQueryParameters: QueryParameters) => void>();
-
- React.useLayoutEffect(() => {
- updateQueryParametersRef.current = updateQueryParameters;
- });
-
- React.useEffect(() => {
- if (!updateQueryParametersRef.current) return;
-
- const tabIdValues = Object.values(TAB_IDS);
- switch (true) {
- case selectedTabId === null:
- case selectedTabId && !tabIdValues.includes(selectedTabId):
- updateQueryParametersRef.current({
- [QUERY_PARAMETERS.TAB]: TAB_IDS.transfer
- });
- }
- }, [selectedTabId]);
-
- const selectedTabIndex = TAB_ITEMS.findIndex((tabItem) => tabItem.id === selectedTabId);
-
- const handleTabSelect = (index: number) => {
- updateQueryParameters({
- [QUERY_PARAMETERS.TAB]: TAB_ITEMS[index].id
- });
- };
-
return (
- {process.env.REACT_APP_RELAY_CHAIN_NAME === POLKADOT && (
-
-
- In order to transfer Interlay tokens to Acala or Moonbeam, please use their respective dApps. Send tokens to{' '}
-
- Acala
- {' '}
- |{' '}
-
- Moonbeam
-
-
-
- )}
-
-
- {TAB_ITEMS.map((tabItem) => (
-
- {t(tabItem.label)}
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/src/types/chains.d.ts b/src/types/chains.d.ts
new file mode 100644
index 0000000000..4f3fdfb322
--- /dev/null
+++ b/src/types/chains.d.ts
@@ -0,0 +1,10 @@
+import { ChainName } from '@interlay/bridge';
+
+type ChainData = {
+ display: string;
+ id: ChainName;
+};
+
+type Chains = ChainData[];
+
+export type { ChainData, Chains };
diff --git a/src/types/chains.types.ts b/src/types/chains.types.ts
deleted file mode 100644
index 74ee07fd8b..0000000000
--- a/src/types/chains.types.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-type XCMChains = 'polkadot' | 'interlay';
-
-export type { XCMChains };
diff --git a/src/utils/hooks/api/xcm/use-xcm-bridge.ts b/src/utils/hooks/api/xcm/use-xcm-bridge.ts
index 4b086c55c8..32a6845d77 100644
--- a/src/utils/hooks/api/xcm/use-xcm-bridge.ts
+++ b/src/utils/hooks/api/xcm/use-xcm-bridge.ts
@@ -1,70 +1,150 @@
+import { FixedPointNumber } from '@acala-network/sdk-core';
import { ApiProvider, Bridge, ChainName } from '@interlay/bridge/build';
-import { useEffect, useState } from 'react';
+import { BaseCrossChainAdapter } from '@interlay/bridge/build/base-chain-adapter';
+import { atomicToBaseAmount, CurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api';
+import Big from 'big.js';
+import { useCallback } from 'react';
+import { useErrorHandler } from 'react-error-boundary';
+import { useQuery, UseQueryResult } from 'react-query';
import { firstValueFrom } from 'rxjs';
+import { convertMonetaryAmountToValueInUSD, formatUSD } from '@/common/utils/utils';
import { XCM_ADAPTERS } from '@/config/relay-chains';
-import { BITCOIN_NETWORK } from '@/constants';
+import { Chains } from '@/types/chains';
+import { getTokenPrice } from '@/utils/helpers/prices';
+import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
-// MEMO: BitcoinNetwork type is not available on XCM bridge
-const XCMNetwork = BITCOIN_NETWORK === 'mainnet' ? 'mainnet' : 'testnet';
+import { XCMEndpoints } from './xcm-endpoints';
const XCMBridge = new Bridge({
adapters: Object.values(XCM_ADAPTERS)
});
-// TODO: This config needs to be pushed higher up the app.
-// Not sure how this will look: something to decide when
-// adding USDT support.
-const getEndpoints = (chains: ChainName[]) => {
- switch (true) {
- case chains.includes('kusama'):
- return {
- kusama: ['wss://kusama-rpc.polkadot.io', 'wss://kusama.api.onfinality.io/public-ws'],
- kintsugi: ['wss://api-kusama.interlay.io/parachain', 'wss://kintsugi.api.onfinality.io/public-ws'],
- statemine: ['wss://statemine-rpc.polkadot.io', 'wss://statemine.api.onfinality.io/public-ws']
- };
- case chains.includes('polkadot'):
- return {
- polkadot: ['wss://rpc.polkadot.io', 'wss://polkadot.api.onfinality.io/public-ws'],
- interlay: ['wss://api.interlay.io/parachain', 'wss://interlay.api.onfinality.io/public-ws'],
- statemint: ['wss://statemint-rpc.polkadot.io', 'wss://statemint.api.onfinality.io/public-ws']
- };
-
- default:
- return undefined;
- }
+type XCMBridgeData = {
+ bridge: Bridge;
+ provider: ApiProvider;
};
-// const useXCMBridge = (): { XCMProvider: ApiProvider; XCMBridge: Bridge } => {
-const useXCMBridge = (): { XCMProvider: ApiProvider; XCMBridge: Bridge } => {
- const [XCMProvider, setXCMProvider] = useState();
-
- useEffect(() => {
- const createBridge = async () => {
- const XCMProvider = new ApiProvider(XCMNetwork);
- const chains = Object.keys(XCM_ADAPTERS) as ChainName[];
-
- // Check connection
- // TODO: Get rid of any casting - mismatch between ApiRx types
- await firstValueFrom(XCMProvider.connectFromChain(chains, getEndpoints(chains)) as any);
-
- // Set Apis
- await Promise.all(
- chains.map((chain: ChainName) =>
- // TODO: Get rid of any casting - mismatch between ApiRx types
- XCMBridge.findAdapter(chain).setApi(XCMProvider.getApi(chain) as any)
- )
- );
+type XCMTokenData = {
+ balance: string;
+ balanceUSD: string;
+ destFee: FixedPointNumber;
+ originFee: string;
+ minTransferAmount: Big;
+ value: string;
+};
+
+type UseXCMBridge = UseQueryResult & {
+ originatingChains: Chains | undefined;
+ getDestinationChains: (chain: ChainName) => Chains;
+ getAvailableTokens: (
+ from: ChainName,
+ to: ChainName,
+ originAddress: string,
+ destinationAddress: string
+ ) => Promise;
+};
+
+const initXCMBridge = async () => {
+ const XCMProvider = new ApiProvider();
+ const chains = Object.keys(XCM_ADAPTERS) as ChainName[];
+
+ await firstValueFrom(XCMProvider.connectFromChain(chains, XCMEndpoints));
+
+ // Set Apis
+ await Promise.all(chains.map((chain: ChainName) => XCMBridge.findAdapter(chain).setApi(XCMProvider.getApi(chain))));
+
+ return { provider: XCMProvider, bridge: XCMBridge };
+};
+
+const useXCMBridge = (): UseXCMBridge => {
+ const queryKey = ['available-xcm-channels'];
+
+ const queryResult = useQuery({
+ queryKey,
+ queryFn: initXCMBridge,
+ refetchInterval: false
+ });
+
+ const { data, error } = queryResult;
+ const prices = useGetPrices();
- setXCMProvider(XCMProvider);
+ const originatingChains = data?.bridge.adapters.map((adapter: BaseCrossChainAdapter) => {
+ return {
+ display: adapter.chain.display,
+ id: adapter.chain.id as ChainName
};
+ });
+
+ const getDestinationChains = useCallback(
+ (chain: ChainName): Chains => {
+ return XCMBridge.router
+ .getDestinationChains({ from: chain })
+ .filter((destinationChain) =>
+ originatingChains?.some((originatingChain) => originatingChain.id === destinationChain.id)
+ ) as Chains;
+ },
+ [originatingChains]
+ );
+
+ const getAvailableTokens = useCallback(
+ async (from, to, originAddress, destinationAddress) => {
+ if (!data) return;
+
+ const tokens = XCMBridge.router.getAvailableTokens({ from, to });
+
+ const inputConfigs = await Promise.all(
+ tokens.map(async (token) => {
+ const inputConfig = await firstValueFrom(
+ data.bridge.findAdapter(from).subscribeInputConfigs({
+ to,
+ token,
+ address: destinationAddress,
+ signer: originAddress
+ })
+ );
+
+ // TODO: resolve type mismatch with BaseCrossChainAdapter and remove `any`
+ const originAdapter = data.bridge.findAdapter(from) as any;
+
+ const maxInputToBig = Big(inputConfig.maxInput.toString());
+ const minInputToBig = Big(inputConfig.minInput.toString());
+
+ // Never show less than zero
+ const transferableBalance = inputConfig.maxInput < inputConfig.minInput ? 0 : maxInputToBig;
+ const currency = XCMBridge.findAdapter(from).getToken(token, from);
+
+ const nativeToken = originAdapter.getNativeToken();
+
+ const amount = newMonetaryAmount(transferableBalance, (currency as unknown) as CurrencyExt, true);
+ const balanceUSD = convertMonetaryAmountToValueInUSD(amount, getTokenPrice(prices, token)?.usd);
+ const originFee = atomicToBaseAmount(inputConfig.estimateFee, nativeToken as CurrencyExt);
+
+ return {
+ balance: transferableBalance.toString(),
+ balanceUSD: formatUSD(balanceUSD || 0, { compact: true }),
+ destFee: inputConfig.destFee.balance,
+ originFee: `${originFee.toString()} ${nativeToken.symbol}`,
+ minTransferAmount: minInputToBig,
+ value: token
+ };
+ })
+ );
+
+ return inputConfigs;
+ },
+ [data, prices]
+ );
- if (!XCMProvider) {
- createBridge();
- }
- }, [XCMProvider]);
+ useErrorHandler(error);
- return { XCMProvider, XCMBridge };
+ return {
+ ...queryResult,
+ originatingChains,
+ getDestinationChains,
+ getAvailableTokens
+ };
};
export { useXCMBridge };
+export type { UseXCMBridge, XCMTokenData };
diff --git a/src/utils/hooks/api/xcm/xcm-endpoints.ts b/src/utils/hooks/api/xcm/xcm-endpoints.ts
new file mode 100644
index 0000000000..6b8407c0df
--- /dev/null
+++ b/src/utils/hooks/api/xcm/xcm-endpoints.ts
@@ -0,0 +1,33 @@
+import { ChainName } from '@interlay/bridge';
+
+type XCMEndpointsRecord = Record;
+
+const XCMEndpoints: XCMEndpointsRecord = {
+ acala: [
+ 'wss://acala-rpc-0.aca-api.network',
+ 'wss://acala-rpc-1.aca-api.network',
+ 'wss://acala-rpc-3.aca-api.network/ws',
+ 'wss://acala-rpc.dwellir.com'
+ ],
+ astar: ['wss://rpc.astar.network', 'wss://astar-rpc.dwellir.com'],
+ bifrost: ['wss://bifrost-rpc.dwellir.com'],
+ heiko: ['wss://heiko-rpc.parallel.fi'],
+ hydra: ['wss://rpc.hydradx.cloud', 'wss://hydradx-rpc.dwellir.com'],
+ interlay: ['wss://api.interlay.io/parachain'],
+ karura: [
+ 'wss://karura-rpc-0.aca-api.network',
+ 'wss://karura-rpc-1.aca-api.network',
+ 'wss://karura-rpc-2.aca-api.network/ws',
+ 'wss://karura-rpc-3.aca-api.network/ws',
+ 'wss://karura-rpc.dwellir.com'
+ ],
+ kintsugi: ['wss://api-kusama.interlay.io/parachain'],
+ kusama: ['wss://kusama-rpc.polkadot.io', 'wss://kusama-rpc.dwellir.com'],
+ parallel: ['wss://rpc.parallel.fi'],
+ polkadot: ['wss://rpc.polkadot.io', 'wss://polkadot-rpc.dwellir.com'],
+ statemine: ['wss://statemine-rpc.polkadot.io', 'wss://statemine-rpc.dwellir.com'],
+ statemint: ['wss://statemint-rpc.polkadot.io', 'wss://statemint-rpc.dwellir.com']
+};
+
+export { XCMEndpoints };
+export type { XCMEndpointsRecord };
diff --git a/yarn.lock b/yarn.lock
index 6e1717a6aa..c887f3b4fd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,16 @@
# yarn lockfile v1
+"@acala-network/api-derive@4.1.8-13":
+ version "4.1.8-13"
+ resolved "https://registry.yarnpkg.com/@acala-network/api-derive/-/api-derive-4.1.8-13.tgz#0ac02da5494c9f6ea8d52235836ecb369dea443d"
+ integrity sha512-Bm7005fPvFMcohvlpbGJMpm0Vm/63PTkRcg0shZvcjuMak3YSR0NhceZRnMoHz+I0Ond5XGRjZVZA/eyRMbSsg==
+ dependencies:
+ "@acala-network/types" "4.1.8-13"
+ "@babel/runtime" "^7.10.2"
+ "@open-web3/orml-types" "^1.1.4"
+ "@polkadot/api-derive" "^8.5.1"
+
"@acala-network/api-derive@4.1.8-9":
version "4.1.8-9"
resolved "https://registry.yarnpkg.com/@acala-network/api-derive/-/api-derive-4.1.8-9.tgz#f4d3969665fe2e92d2fca73d2c403e4f26b519bd"
@@ -12,7 +22,19 @@
"@open-web3/orml-types" "^1.1.4"
"@polkadot/api-derive" "^8.5.1"
-"@acala-network/api@4.1.8-9", "@acala-network/api@~4.1.8-9":
+"@acala-network/api@4.1.8-13":
+ version "4.1.8-13"
+ resolved "https://registry.yarnpkg.com/@acala-network/api/-/api-4.1.8-13.tgz#8127edaba9802eaa6a20678e823f43f2affb6067"
+ integrity sha512-+m032NiYPAvbOHeaJrCKQuACe9hykNTpQpDKeKkg0RME9JnFKeR7TYLkWtInhbmql6b8LxAAdpy2gdQctrsCRA==
+ dependencies:
+ "@acala-network/api-derive" "4.1.8-13"
+ "@acala-network/types" "4.1.8-13"
+ "@babel/runtime" "^7.10.2"
+ "@open-web3/orml-api-derive" "^1.1.4"
+ "@polkadot/api" "^9.9.1"
+ "@polkadot/rpc-core" "^9.9.1"
+
+"@acala-network/api@~4.1.8-9":
version "4.1.8-9"
resolved "https://registry.yarnpkg.com/@acala-network/api/-/api-4.1.8-9.tgz#9213e09b7c43b3df95eaf47fe78c989ddfe4207e"
integrity sha512-9kpYQYe5vBCKWlyABh+Q2sjONDdtNfdv0PL0Tek3bpt00a3VjNIZvQro5ZSwzdpGJs5YcsiWPRMBq3iMgJNtGQ==
@@ -29,7 +51,7 @@
resolved "https://registry.yarnpkg.com/@acala-network/contracts/-/contracts-4.3.4.tgz#f37cf54894c72b762df539042a61f90b10b68600"
integrity sha512-oBgXGUjRW+lRo9TWGtCB1+OpEOFfhxW//wReb7V/YdbEElVvYuKw3lmfly/eZ/mdBgqxA3eXxNW0AgXiyOn2NQ==
-"@acala-network/eth-providers@^2.5.4":
+"@acala-network/eth-providers@^2.5.9":
version "2.6.5"
resolved "https://registry.yarnpkg.com/@acala-network/eth-providers/-/eth-providers-2.6.5.tgz#9087abe44a0686de5188ea962961519ecff20e66"
integrity sha512-Y0hi0LRN8pJ144dv9WcSi9nPn5Wez0h745EGa1/6NFtU7jsua0jg25WYJ53s17rXIMz8GUKdln9SAIeShQiEtw==
@@ -79,10 +101,10 @@
"@ethersproject/wallet" "~5.7.0"
"@polkadot/util-crypto" "^10.2.1"
-"@acala-network/sdk-core@4.1.8-9":
- version "4.1.8-9"
- resolved "https://registry.yarnpkg.com/@acala-network/sdk-core/-/sdk-core-4.1.8-9.tgz#47de650483f74aa9320d9ff9a8cdcf0e48b4a192"
- integrity sha512-hjJ4Qs20aacg9vUnt2xZne3nN+c73zS7sBklVwtzXLlW87QWKDHdvkRkGZyeeKujaGRnqODhYIPtGtPqd0t+ag==
+"@acala-network/sdk-core@4.1.8-13":
+ version "4.1.8-13"
+ resolved "https://registry.yarnpkg.com/@acala-network/sdk-core/-/sdk-core-4.1.8-13.tgz#ff69ef993f5a36caa31744384c389765ede7cc96"
+ integrity sha512-4q9lksLJ/8lXA/f/t9GHQqv8ePIT2vId7rkaoqE/jASq6ngRFg2heV/6eScCKudr2aJN68YX3Jf0hwH6eazVLQ==
dependencies:
"@polkadot/api" "^9.9.1"
"@polkadot/types" "^9.9.1"
@@ -91,14 +113,14 @@
events "^3.2.0"
lodash "^4.17.20"
-"@acala-network/sdk@4.1.8-9":
- version "4.1.8-9"
- resolved "https://registry.yarnpkg.com/@acala-network/sdk/-/sdk-4.1.8-9.tgz#3501296ec663346e2118dcfcc72e31afcdf63fbb"
- integrity sha512-/e624PRyzwUJUEW4g7y4kVjs4WsDU2S6KPvn2Nbojl0bz0wrm05ghjD3lW98m8CcLLLv4wa4hldegFzx79LYgw==
+"@acala-network/sdk@4.1.8-13":
+ version "4.1.8-13"
+ resolved "https://registry.yarnpkg.com/@acala-network/sdk/-/sdk-4.1.8-13.tgz#f603a6c84c4654971495676345f4a24041ff1c02"
+ integrity sha512-3apYrmQ+WZWzEYd0sdLCpTYe8SagMMK2+0vj35ANVvD92FHUUkHTtJAEiCu81y0ujFuFbtx/VxA0uGVb/fBZ6A==
dependencies:
- "@acala-network/api" "4.1.8-9"
- "@acala-network/eth-providers" "^2.5.4"
- "@acala-network/type-definitions" "4.1.8-9"
+ "@acala-network/api" "4.1.8-13"
+ "@acala-network/eth-providers" "^2.5.9"
+ "@acala-network/type-definitions" "4.1.8-13"
"@ethersproject/bignumber" "^5.7.0"
"@polkadot/api" "^9.9.1"
"@polkadot/types" "^9.9.1"
@@ -114,6 +136,13 @@
lru-cache "^7.14.1"
rxjs "^7.5.7"
+"@acala-network/type-definitions@4.1.8-13":
+ version "4.1.8-13"
+ resolved "https://registry.yarnpkg.com/@acala-network/type-definitions/-/type-definitions-4.1.8-13.tgz#a295d3f3feb1d36cadbda634c180f53eb90cca61"
+ integrity sha512-AMXbqsJehhDcwEngSB173eQvuCAsXEm/7rNZMQ8KLG56a8FrNAgrEz+83foogLuTcehCPUPfC0R1Ef/+874rRw==
+ dependencies:
+ "@open-web3/orml-type-definitions" "^1.1.4"
+
"@acala-network/type-definitions@4.1.8-9":
version "4.1.8-9"
resolved "https://registry.yarnpkg.com/@acala-network/type-definitions/-/type-definitions-4.1.8-9.tgz#be238e2e269cd701b79b0af5f9ed4d9c168d94c0"
@@ -121,13 +150,23 @@
dependencies:
"@open-web3/orml-type-definitions" "^1.1.4"
-"@acala-network/type-definitions@^4.1.5":
- version "4.1.5"
- resolved "https://registry.yarnpkg.com/@acala-network/type-definitions/-/type-definitions-4.1.5.tgz#c02624ba9bb637588ddd184a4ce35ab7d9de2bf6"
- integrity sha512-XwXtKf5ESfzGk32N1sE3MlBtnamz2JZYtjB6KcKe9eOyv+3lowQvRn4Z347rNSEp+tpenZWnLwBXk4XWhdiSoQ==
+"@acala-network/type-definitions@^4.1.8-1":
+ version "4.1.8-14"
+ resolved "https://registry.yarnpkg.com/@acala-network/type-definitions/-/type-definitions-4.1.8-14.tgz#f0d1dd5f0e50c5b16e19fc222d351b4ec4524928"
+ integrity sha512-3PDYFaT8s9PYgZZNNtOEco5Oyn/oQlnuYrBe6WQX1bQBhAbUQjMDhuaqoqRF61CFtxYTgw/6kiFRf/aUNhigGQ==
dependencies:
"@open-web3/orml-type-definitions" "^1.1.4"
+"@acala-network/types@4.1.8-13":
+ version "4.1.8-13"
+ resolved "https://registry.yarnpkg.com/@acala-network/types/-/types-4.1.8-13.tgz#919fc5ad818f535caba0fc2ea0477085e570d93b"
+ integrity sha512-XBIupGrNyY1xSptC59GNE89C4wJ2pb/QwRiRkQUNzDSTfLbjUSCOpDqjSfZIxj21+/zhZtw+6+uS+HnoTpsQeg==
+ dependencies:
+ "@acala-network/type-definitions" "4.1.8-13"
+ "@babel/runtime" "^7.10.2"
+ "@open-web3/api-mobx" "^1.1.4"
+ "@open-web3/orml-types" "^1.1.4"
+
"@acala-network/types@4.1.8-9", "@acala-network/types@~4.1.8-9":
version "4.1.8-9"
resolved "https://registry.yarnpkg.com/@acala-network/types/-/types-4.1.8-9.tgz#afc11f555dc900149eff132857f456500dcfb892"
@@ -1270,7 +1309,7 @@
core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.1", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
@@ -1321,10 +1360,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-"@bifrost-finance/type-definitions@1.7.1":
- version "1.7.1"
- resolved "https://registry.yarnpkg.com/@bifrost-finance/type-definitions/-/type-definitions-1.7.1.tgz#d64e89eebf5d325ecca636261373945e14c4c508"
- integrity sha512-9AJIFFtlTKUGNJ8ITkgDUUJD+Iodb2Cp6qbVl5mAKuaws9QrLpgKYTT09GoKltQTg5bbDc8+ygbcabntUeTZGw==
+"@bifrost-finance/type-definitions@1.7.2":
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/@bifrost-finance/type-definitions/-/type-definitions-1.7.2.tgz#13139a69e3e98d175a4751d7fd78dcfebac29943"
+ integrity sha512-JL19CHFL4DxO29LRrv9o7r7Au9TtY+8pwG4fMP8M6jq2/MkvWd7OQFn1lmEy58akntNrVReIkZPuP81MFKv9jg==
dependencies:
"@open-web3/orml-type-definitions" "^0.9.4-38"
@@ -1552,10 +1591,10 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
-"@docknetwork/node-types@0.13.0":
- version "0.13.0"
- resolved "https://registry.yarnpkg.com/@docknetwork/node-types/-/node-types-0.13.0.tgz#8b643f9cb52c3563d3db91ac84e06836b0f6f199"
- integrity sha512-k+NZksUGqc1Cz8eG+EzCPRyRalgho/xy4fh5Dqsbe9LwLeklrrtfAMaklPRtkt0yja8ueg1DGnCtHq00e99j4Q==
+"@docknetwork/node-types@0.15.0":
+ version "0.15.0"
+ resolved "https://registry.yarnpkg.com/@docknetwork/node-types/-/node-types-0.15.0.tgz#eed5c719380865bf989ccd2550844dadb7abdd19"
+ integrity sha512-ACIHUIiAt82nhYxtwHSyS4JaJ28UbWS+fAwbTblKcsQBe7YRM2tjbLmkaqQjGPjxJS+wmh/xf7/PnA8PfboNZg==
"@edgeware/node-types@3.6.2-wako":
version "3.6.2-wako"
@@ -1584,10 +1623,10 @@
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
-"@equilab/definitions@1.4.14":
- version "1.4.14"
- resolved "https://registry.yarnpkg.com/@equilab/definitions/-/definitions-1.4.14.tgz#c384f3eca003293d5f2c0a42235bdbe0a60626dd"
- integrity sha512-F8jDESrhUpapqGSTXWND+5/DOqFlnh/oEejIYVIzF2WeUreHJzUPpI8a8Hb9plCLxj5sxYJ+3JL/5epMcrXDaQ==
+"@equilab/definitions@1.4.18":
+ version "1.4.18"
+ resolved "https://registry.yarnpkg.com/@equilab/definitions/-/definitions-1.4.18.tgz#e544951b50278705af3d9fa4ba91e04df53a3d06"
+ integrity sha512-rFEPaHmdn5I1QItbQun9H/x+o3hgjA6kLYLrNN6nl/ndtQMY2tqx/mQfcGIlKA1xVmyn9mUAqD8G0P/nBHD3yA==
"@eslint/eslintrc@^0.4.3":
version "0.4.3"
@@ -1985,6 +2024,15 @@
dependencies:
tslib "2.4.0"
+"@frequency-chain/api-augment@^1.0.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@frequency-chain/api-augment/-/api-augment-1.6.0.tgz#a611d191328e11ccf24aff82fe2d165b9b6a0eb8"
+ integrity sha512-OkyLC4ttgkB+6PpTN94NIWPgi6rEclzK7pBSULtfl6ZhgjW9IalykbJmispG3Ntgwdb69TMUU0wSdDPBS15r9A==
+ dependencies:
+ "@polkadot/api" "^10.3.2"
+ "@polkadot/rpc-provider" "^10.3.2"
+ "@polkadot/types" "^10.3.2"
+
"@gar/promisify@^1.0.1":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
@@ -2051,18 +2099,17 @@
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
-"@interlay/bridge@^0.2.4":
- version "0.2.4"
- resolved "https://registry.yarnpkg.com/@interlay/bridge/-/bridge-0.2.4.tgz#83f446575d1b66cac7601bc4b771c3b19b9137b5"
- integrity sha512-XYgLhd4anvoaLL9C+Su/BDATd0K6rQipZXjQW3wuqTYyy+Pr7ItNGu4FbSGLqid1osn7b7No4sXQ5WwFJsZSQA==
- dependencies:
- "@acala-network/api" "4.1.8-9"
- "@acala-network/sdk" "4.1.8-9"
- "@acala-network/sdk-core" "4.1.8-9"
- "@polkadot/api" "^9.11.1"
- "@polkadot/apps-config" "^0.122.2"
- "@polkadot/types" "^9.11.1"
- "@polkadot/types-augment" "^9.11.1"
+"@interlay/bridge@^0.3.9":
+ version "0.3.9"
+ resolved "https://registry.yarnpkg.com/@interlay/bridge/-/bridge-0.3.9.tgz#fc39c64708eab2f55cb0bbb970f2f0e72583cb37"
+ integrity sha512-QCeTux1f3LwLJ/dcfHmOTOuF8ocfpo9WDNV7Z1GWTHX/I8lspidj4xh8c/g2+jNZnHMiINXCSvHGPPr05lTnQg==
+ dependencies:
+ "@acala-network/api" "4.1.8-13"
+ "@acala-network/sdk" "4.1.8-13"
+ "@acala-network/sdk-core" "4.1.8-13"
+ "@polkadot/api" "^9.14.2"
+ "@polkadot/apps-config" "^0.124.1"
+ "@polkadot/types" "^9.14.2"
axios "^0.27.2"
lodash "^4.17.20"
@@ -2090,16 +2137,16 @@
isomorphic-fetch "^3.0.0"
regtest-client "^0.2.0"
+"@interlay/interbtc-types@1.11.0":
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/@interlay/interbtc-types/-/interbtc-types-1.11.0.tgz#5b94066ddee1fd677de928531db36e6ae439e08f"
+ integrity sha512-bn3XjyRlXyhe1QKUHx5IEQJDNC6LoSCJJIkTnSp5xm52GRBEWgHOvLAnfJi3gyj7A3lV/yA2Xjqf294bZgMmfw==
+
"@interlay/interbtc-types@1.12.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@interlay/interbtc-types/-/interbtc-types-1.12.0.tgz#07dc8e15690292387124dbc2bbb7bf5bc8b68001"
integrity sha512-ELJa2ftIbe8Ds2ejS7kO5HumN9EB5l2OBi3Qsy5iHJsHKq2HtXfFoKnW38HarM6hADrWG+e/yNGHSKJIJzEZuA==
-"@interlay/interbtc-types@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@interlay/interbtc-types/-/interbtc-types-1.9.0.tgz#beffd3b04bc1d9dba49f3ddc338b5867b81dec3d"
- integrity sha512-G/jOHXM6lqoFAPquESAxsjt5ETrmcPTDC36WFRWYpoRUfFxcjq6TkWGxUC5/RS6jIoWMQ6lEJZupmlm/QNdbAg==
-
"@interlay/monetary-js@0.7.2":
version "0.7.2"
resolved "https://registry.yarnpkg.com/@interlay/monetary-js/-/monetary-js-0.7.2.tgz#a54a315b60be12f5b1a9c31f0d71d5e8ee7ba174"
@@ -2434,10 +2481,10 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
-"@kiltprotocol/type-definitions@^0.2.1":
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/@kiltprotocol/type-definitions/-/type-definitions-0.2.1.tgz#0469b0bcc58063be0b02ffbf6f779176c6b0a00d"
- integrity sha512-By09MH20P+rXadiZDnw2XeAw7bQLgNazOyNS3gPdU1L4Jx+lU9OtvIgZEA+T/TY/KM5nTv32s3c4wZ7v1s2znw==
+"@kiltprotocol/type-definitions@^0.30.0":
+ version "0.30.0"
+ resolved "https://registry.yarnpkg.com/@kiltprotocol/type-definitions/-/type-definitions-0.30.0.tgz#00e99636a1c4405071021242cd509090c8f14287"
+ integrity sha512-1UpPDjX8PFqTFm3lRRfYUPEY9M8KrbpRinf4q4K843lY5GdTxQaevrVdK9/WCHKywLyDa4tSrlUv9KQjrTP4bg==
"@laminar/type-definitions@0.3.1":
version "0.3.1"
@@ -2446,22 +2493,22 @@
dependencies:
"@open-web3/orml-type-definitions" "^0.8.2-9"
-"@logion/node-api@^0.7.0":
- version "0.7.2"
- resolved "https://registry.yarnpkg.com/@logion/node-api/-/node-api-0.7.2.tgz#af164f13831f1b89b130597ca70bf0e863f0b79f"
- integrity sha512-EAyRp1MAAS4bGuxnuAYExssfwvFHLsmCyPWH0wMIik5eLHqNiPA1LwylYH5AQx8P1w11/nHVqRc5ly7dlf5E+w==
+"@logion/node-api@^0.9.0-3":
+ version "0.9.0-3"
+ resolved "https://registry.yarnpkg.com/@logion/node-api/-/node-api-0.9.0-3.tgz#b02741acbf30517d537d48b75ffc83b366f09b87"
+ integrity sha512-6m2My8yI9jmhqP6FHPJdrsqdg/1vyJtY1/4cnuqCByJaVgNDGrcdtcmzW4BXCww+hJMrdm3PeLthKHCrwpo0gA==
dependencies:
- "@polkadot/api" "^9.8.1"
- "@polkadot/util" "^10.1.12"
- "@polkadot/util-crypto" "^10.1.12"
+ "@polkadot/api" "^9.10.1"
+ "@polkadot/util" "^10.2.1"
+ "@polkadot/util-crypto" "^10.2.1"
"@types/uuid" "^8.3.4"
fast-sha256 "^1.3.0"
uuid "^8.3.2"
-"@mangata-finance/types@^0.9.0":
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/@mangata-finance/types/-/types-0.9.0.tgz#8272a01d87243f693d0e0889070afb0d00b4f91f"
- integrity sha512-37yIP9xh+6kTt+UQJsbPt0OCrDIZsqGRh3f7sEtK7zX4G8uWrg/GHcHtGZQRruNIOAO8+1PLXuMfLokSzojVBw==
+"@mangata-finance/types@^0.17.0":
+ version "0.17.0"
+ resolved "https://registry.yarnpkg.com/@mangata-finance/types/-/types-0.17.0.tgz#299b0bd21e30e17ee65c25f18d4a89871f521930"
+ integrity sha512-v0o7rePG4P2fDH1yVvSfuHpHQCA7Xki9IwPMTu51Y4FoQdvD1zHUOI4mIOc3ssjOAJsCePNdsTm+/xj3DeiSxQ==
"@mdx-js/mdx@^1.6.22":
version "1.6.22"
@@ -2732,17 +2779,17 @@
dependencies:
"@open-web3/orml-type-definitions" "1.1.4"
-"@parallel-finance/type-definitions@1.7.13":
- version "1.7.13"
- resolved "https://registry.yarnpkg.com/@parallel-finance/type-definitions/-/type-definitions-1.7.13.tgz#08c92e07496d2757d9a89879e7d3d08b2bf49744"
- integrity sha512-v2M1uCfBnQ2wiYEk/2ftTdSB4OV8nTL38qhB6ApykxHCdXu4Nz1lJKFjW8OW1DTXB8xCfkj8VX8eY0UDez535g==
+"@parallel-finance/type-definitions@1.7.14":
+ version "1.7.14"
+ resolved "https://registry.yarnpkg.com/@parallel-finance/type-definitions/-/type-definitions-1.7.14.tgz#02ca0d8a8d2894fa1d22c8625bd4edfcfbeffc4c"
+ integrity sha512-64cIrOcS5z2SSzTAITg3qDdQReoBLCZhAGHzR1VnYQzF0u59Ow6XnWmg0/R4EuyhnsqW4aMhnrmlVE7RhG9kPg==
dependencies:
"@open-web3/orml-type-definitions" "^1.1.4"
-"@phala/typedefs@0.2.32":
- version "0.2.32"
- resolved "https://registry.yarnpkg.com/@phala/typedefs/-/typedefs-0.2.32.tgz#4c66dce9b5a975226bbbdbdef09bccfde2e54878"
- integrity sha512-G1ifICDNW6NtixqCVfJHBI82Detwzzmzs4gpE1RrMsTxfoKIbxkX8nx3DxZUhFYqikGEkQVxNmEi3jpC0zDrsw==
+"@phala/typedefs@0.2.33":
+ version "0.2.33"
+ resolved "https://registry.yarnpkg.com/@phala/typedefs/-/typedefs-0.2.33.tgz#6f18d73b5104db6a594d08be571954385b3e509b"
+ integrity sha512-CaRzIGfU6CUIKLPswYtOw/xbtTttqmJZpr3fhkxLvkBQMXIH14iISD763OFXtWui7DrAMBKo/bHawvFNgWGKTg==
"@pmmmwh/react-refresh-webpack-plugin@0.4.3", "@pmmmwh/react-refresh-webpack-plugin@^0.4.3":
version "0.4.3"
@@ -2829,7 +2876,7 @@
"@polkadot/util-crypto" "^10.4.2"
rxjs "^7.8.0"
-"@polkadot/api-derive@9.10.3", "@polkadot/api-derive@9.14.2", "@polkadot/api-derive@^8.5.1", "@polkadot/api-derive@^9.14.2", "@polkadot/api-derive@^9.7.1":
+"@polkadot/api-derive@9.10.3", "@polkadot/api-derive@9.14.2", "@polkadot/api-derive@^8.5.1", "@polkadot/api-derive@^9.13.2", "@polkadot/api-derive@^9.14.2":
version "9.14.2"
resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-9.14.2.tgz#e8fcd4ee3f2b80b9fe34d4dec96169c3bdb4214d"
integrity sha512-yw9OXucmeggmFqBTMgza0uZwhNjPxS7MaT7lSCUIRKckl1GejdV+qMhL3XFxPFeYzXwzFpdPG11zWf+qJlalqw==
@@ -2845,7 +2892,7 @@
"@polkadot/util-crypto" "^10.4.2"
rxjs "^7.8.0"
-"@polkadot/api@9.10.3", "@polkadot/api@9.14.2", "@polkadot/api@^7.2.1", "@polkadot/api@^9.11.1", "@polkadot/api@^9.14.2", "@polkadot/api@^9.4.2", "@polkadot/api@^9.7.1", "@polkadot/api@^9.8.1", "@polkadot/api@^9.9.1", "@polkadot/api@latest":
+"@polkadot/api@9.10.3", "@polkadot/api@9.14.2", "@polkadot/api@^10.3.2", "@polkadot/api@^7.2.1", "@polkadot/api@^9.10.1", "@polkadot/api@^9.13.2", "@polkadot/api@^9.14.2", "@polkadot/api@^9.4.2", "@polkadot/api@^9.9.1", "@polkadot/api@latest":
version "9.14.2"
resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-9.14.2.tgz#d5cee02236654c6063d7c4b70c78c290db5aba8d"
integrity sha512-R3eYFj2JgY1zRb+OCYQxNlJXCs2FA+AU4uIEiVcXnVLmR3M55tkRNEwYAZmiFxx0pQmegGgPMc33q7TWGdw24A==
@@ -2868,48 +2915,49 @@
eventemitter3 "^5.0.0"
rxjs "^7.8.0"
-"@polkadot/apps-config@^0.122.2":
- version "0.122.2"
- resolved "https://registry.yarnpkg.com/@polkadot/apps-config/-/apps-config-0.122.2.tgz#b15d2dbfc43b0e8bc32fc14bd56b97de3abb83e3"
- integrity sha512-EBINVhOe4w5gzjOJ/lIzYJDP1DiZY8SWBf8Jp25DOwdSvUsyV1AYyrGAgmz+kE+jtakEMKOZpXdRt3OwGYLPqw==
+"@polkadot/apps-config@^0.124.1":
+ version "0.124.1"
+ resolved "https://registry.yarnpkg.com/@polkadot/apps-config/-/apps-config-0.124.1.tgz#4d993fcc198118dfe4aa9200ce6055b48cab96b3"
+ integrity sha512-SqDLf0ksU5WkU96L3nIiICwaBDLj4APYjKkwpSUAWk1NcvXDWZQQG56obgaLPHZ2If6GZrQge/fUmItuRBIZrg==
dependencies:
- "@acala-network/type-definitions" "^4.1.5"
- "@babel/runtime" "^7.20.1"
- "@bifrost-finance/type-definitions" "1.7.1"
+ "@acala-network/type-definitions" "^4.1.8-1"
+ "@babel/runtime" "^7.20.13"
+ "@bifrost-finance/type-definitions" "1.7.2"
"@crustio/type-definitions" "1.3.0"
"@darwinia/types" "2.8.10"
"@darwinia/types-known" "2.8.10"
"@digitalnative/type-definitions" "1.1.27"
- "@docknetwork/node-types" "0.13.0"
+ "@docknetwork/node-types" "0.15.0"
"@edgeware/node-types" "3.6.2-wako"
- "@equilab/definitions" "1.4.14"
- "@interlay/interbtc-types" "1.9.0"
- "@kiltprotocol/type-definitions" "^0.2.1"
+ "@equilab/definitions" "1.4.18"
+ "@frequency-chain/api-augment" "^1.0.0"
+ "@interlay/interbtc-types" "1.11.0"
+ "@kiltprotocol/type-definitions" "^0.30.0"
"@laminar/type-definitions" "0.3.1"
- "@logion/node-api" "^0.7.0"
- "@mangata-finance/types" "^0.9.0"
+ "@logion/node-api" "^0.9.0-3"
+ "@mangata-finance/types" "^0.17.0"
"@metaverse-network-sdk/type-definitions" "^0.0.1-13"
- "@parallel-finance/type-definitions" "1.7.13"
- "@phala/typedefs" "0.2.32"
- "@polkadot/api" "^9.7.1"
- "@polkadot/api-derive" "^9.7.1"
- "@polkadot/networks" "^10.1.11"
- "@polkadot/types" "^9.7.1"
- "@polkadot/util" "^10.1.11"
- "@polkadot/x-fetch" "^10.1.11"
+ "@parallel-finance/type-definitions" "1.7.14"
+ "@phala/typedefs" "0.2.33"
+ "@polkadot/api" "^9.13.2"
+ "@polkadot/api-derive" "^9.13.2"
+ "@polkadot/networks" "^10.3.1"
+ "@polkadot/types" "^9.13.2"
+ "@polkadot/util" "^10.3.1"
+ "@polkadot/x-fetch" "^10.3.1"
"@polymathnetwork/polymesh-types" "0.0.2"
"@snowfork/snowbridge-types" "0.2.7"
- "@sora-substrate/type-definitions" "1.10.21"
- "@subsocial/definitions" "^0.7.8-dev.0"
- "@unique-nft/opal-testnet-types" "930.31.0"
- "@unique-nft/quartz-mainnet-types" "930.31.0"
- "@unique-nft/unique-mainnet-types" "930.31.0"
- "@zeitgeistpm/type-defs" "0.9.0"
+ "@sora-substrate/type-definitions" "1.12.4"
+ "@subsocial/definitions" "^0.7.9"
+ "@unique-nft/opal-testnet-types" "930.34.0"
+ "@unique-nft/quartz-mainnet-types" "930.34.0"
+ "@unique-nft/unique-mainnet-types" "930.33.0"
+ "@zeitgeistpm/type-defs" "0.10.0"
"@zeroio/type-definitions" "0.0.14"
lodash "^4.17.21"
moonbeam-types-bundle "2.0.9"
pontem-types-bundle "1.0.15"
- rxjs "^7.5.7"
+ rxjs "^7.8.0"
"@polkadot/extension-dapp@0.44.1":
version "0.44.1"
@@ -2942,6 +2990,15 @@
"@polkadot/util" "10.4.2"
"@polkadot/util-crypto" "10.4.2"
+"@polkadot/keyring@^10.3.1":
+ version "10.3.1"
+ resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-10.3.1.tgz#f13fed33686ff81b1e486721e52299eba9e6c4a6"
+ integrity sha512-xBkUtyQ766NVS1ccSYbQssWpxAhSf0uwkw9Amj8TFhu++pnZcVm+EmM2VczWqgOkmWepO7MGRjEXeOIw1YUGiw==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ "@polkadot/util" "10.3.1"
+ "@polkadot/util-crypto" "10.3.1"
+
"@polkadot/keyring@^6.9.1":
version "6.11.1"
resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-6.11.1.tgz#2510c349c965c74cc2f108f114f1048856940604"
@@ -2969,7 +3026,7 @@
"@polkadot/util" "8.7.1"
"@polkadot/util-crypto" "8.7.1"
-"@polkadot/networks@10.4.2", "@polkadot/networks@^10.1.11", "@polkadot/networks@^10.1.6", "@polkadot/networks@^10.4.2":
+"@polkadot/networks@10.4.2", "@polkadot/networks@^10.1.6", "@polkadot/networks@^10.4.2":
version "10.4.2"
resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-10.4.2.tgz#d7878c6aad8173c800a21140bfe5459261724456"
integrity sha512-FAh/znrEvWBiA/LbcT5GXHsCFUl//y9KqxLghSr/CreAmAergiJNT0MVUezC7Y36nkATgmsr4ylFwIxhVtuuCw==
@@ -2978,6 +3035,32 @@
"@polkadot/util" "10.4.2"
"@substrate/ss58-registry" "^1.38.0"
+"@polkadot/networks@^10.3.1":
+ version "10.3.1"
+ resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-10.3.1.tgz#097a2c4cd25eff59fe6c11299f58feedd4335042"
+ integrity sha512-W9E1g6zRbIVyF7sGqbpxH0P6caxtBHNEwvDa5/8ZQi9UsLj6mUs0HdwZtAdIo3KcSO4uAyV9VYJjY/oAWWcnXg==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ "@polkadot/util" "10.3.1"
+ "@substrate/ss58-registry" "^1.38.0"
+
+"@polkadot/react-identicon@^2.11.1":
+ version "2.11.1"
+ resolved "https://registry.yarnpkg.com/@polkadot/react-identicon/-/react-identicon-2.11.1.tgz#8f81f142f7c7763fe2d499580b85b3879f4eb081"
+ integrity sha512-pqEsiXuKOXDrXNnsFB1JyBbFqedbiDwtP0yewIQ9vtwJwm01o7oE0yGfYS88hnPN1mLDc++MMcqvrHBsxYr2Lw==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ "@polkadot/keyring" "^10.3.1"
+ "@polkadot/ui-settings" "2.11.1"
+ "@polkadot/ui-shared" "2.11.1"
+ "@polkadot/util" "^10.3.1"
+ "@polkadot/util-crypto" "^10.3.1"
+ color "^3.2.1"
+ ethereum-blockies-base64 "^1.0.2"
+ jdenticon "3.2.0"
+ react-copy-to-clipboard "^5.1.0"
+ styled-components "^5.3.6"
+
"@polkadot/rpc-augment@9.14.2", "@polkadot/rpc-augment@^9.14.2":
version "9.14.2"
resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-9.14.2.tgz#eb70d5511463dab8d995faeb77d4edfe4952fe26"
@@ -3001,7 +3084,7 @@
"@polkadot/util" "^10.4.2"
rxjs "^7.8.0"
-"@polkadot/rpc-provider@9.14.2", "@polkadot/rpc-provider@^8.7.1", "@polkadot/rpc-provider@^9.14.2":
+"@polkadot/rpc-provider@9.14.2", "@polkadot/rpc-provider@^10.3.2", "@polkadot/rpc-provider@^8.7.1", "@polkadot/rpc-provider@^9.14.2":
version "9.14.2"
resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-9.14.2.tgz#0dea667f3a03bf530f202cba5cd360df19b32e30"
integrity sha512-YTSywjD5PF01V47Ru5tln2LlpUwJiSOdz6rlJXPpMaY53hUp7+xMU01FVAQ1bllSBNisSD1Msv/mYHq84Oai2g==
@@ -3021,7 +3104,7 @@
optionalDependencies:
"@substrate/connect" "0.7.19"
-"@polkadot/types-augment@9.14.2", "@polkadot/types-augment@^9.11.1", "@polkadot/types-augment@^9.14.2":
+"@polkadot/types-augment@9.14.2", "@polkadot/types-augment@^9.14.2":
version "9.14.2"
resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-9.14.2.tgz#1a478e18e713b04038f3e171287ee5abe908f0aa"
integrity sha512-WO9d7RJufUeY3iFgt2Wz762kOu1tjEiGBR5TT4AHtpEchVHUeosVTrN9eycC+BhleqYu52CocKz6u3qCT/jKLg==
@@ -3069,7 +3152,7 @@
"@babel/runtime" "^7.20.13"
"@polkadot/util" "^10.4.2"
-"@polkadot/types@9.10.3", "@polkadot/types@9.14.2", "@polkadot/types@^4.13.1", "@polkadot/types@^6.0.5", "@polkadot/types@^7.2.1", "@polkadot/types@^8.7.1", "@polkadot/types@^9.11.1", "@polkadot/types@^9.14.2", "@polkadot/types@^9.7.1", "@polkadot/types@^9.9.1":
+"@polkadot/types@9.10.3", "@polkadot/types@9.14.2", "@polkadot/types@^10.3.2", "@polkadot/types@^4.13.1", "@polkadot/types@^6.0.5", "@polkadot/types@^7.2.1", "@polkadot/types@^8.7.1", "@polkadot/types@^9.13.2", "@polkadot/types@^9.14.2", "@polkadot/types@^9.9.1":
version "9.14.2"
resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-9.14.2.tgz#5105f41eb9e8ea29938188d21497cbf1753268b8"
integrity sha512-hGLddTiJbvowhhUZJ3k+olmmBc1KAjWIQxujIUIYASih8FQ3/YJDKxaofGOzh0VygOKW3jxQBN2VZPofyDP9KQ==
@@ -3097,6 +3180,17 @@
rxjs "^7.5.6"
store "^2.0.12"
+"@polkadot/ui-settings@2.11.1":
+ version "2.11.1"
+ resolved "https://registry.yarnpkg.com/@polkadot/ui-settings/-/ui-settings-2.11.1.tgz#e3474097a6f4246423731e9b5cce3a5bb9482349"
+ integrity sha512-7yZwb3VxGh7VPHkyygktL7Oep0c4XUyKkYGwSmgP2Gt2IcvnGXFUQVSEARKs9FCanl19f2CEU1m19+FFrjlUNQ==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ "@polkadot/networks" "^10.3.1"
+ "@polkadot/util" "^10.3.1"
+ eventemitter3 "^4.0.7"
+ store "^2.0.12"
+
"@polkadot/ui-settings@2.9.7":
version "2.9.7"
resolved "https://registry.yarnpkg.com/@polkadot/ui-settings/-/ui-settings-2.9.7.tgz#c9fcd7dc8d1de36826e06c347f27d9a9df56810c"
@@ -3108,7 +3202,15 @@
eventemitter3 "^4.0.7"
store "^2.0.12"
-"@polkadot/util-crypto@10.4.2", "@polkadot/util-crypto@6.11.1", "@polkadot/util-crypto@7.9.2", "@polkadot/util-crypto@8.7.1", "@polkadot/util-crypto@^10.1.12", "@polkadot/util-crypto@^10.1.6", "@polkadot/util-crypto@^10.2.1", "@polkadot/util-crypto@^10.2.4", "@polkadot/util-crypto@^10.4.2", "@polkadot/util-crypto@^9.4.1":
+"@polkadot/ui-shared@2.11.1":
+ version "2.11.1"
+ resolved "https://registry.yarnpkg.com/@polkadot/ui-shared/-/ui-shared-2.11.1.tgz#b4dfe2310003ce4621fcbb5e94daa8c76b45a028"
+ integrity sha512-+qCLPT3SEnHOG3WvO0iYSJ6zArPQGCz9nHx8X8rw9GhffdiEC20ae63jB6dQTjR5GppPQx0aLE/cOppWn/HpRg==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ color "^3.2.1"
+
+"@polkadot/util-crypto@10.3.1", "@polkadot/util-crypto@10.4.2", "@polkadot/util-crypto@6.11.1", "@polkadot/util-crypto@7.9.2", "@polkadot/util-crypto@8.7.1", "@polkadot/util-crypto@^10.1.6", "@polkadot/util-crypto@^10.2.1", "@polkadot/util-crypto@^10.2.4", "@polkadot/util-crypto@^10.3.1", "@polkadot/util-crypto@^10.4.2", "@polkadot/util-crypto@^9.4.1":
version "10.4.2"
resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-10.4.2.tgz#871fb69c65768bd48c57bb5c1f76a85d979fb8b5"
integrity sha512-RxZvF7C4+EF3fzQv8hZOLrYCBq5+wA+2LWv98nECkroChY3C2ZZvyWDqn8+aonNULt4dCVTWDZM0QIY6y4LUAQ==
@@ -3125,7 +3227,7 @@
ed2curve "^0.3.0"
tweetnacl "^1.0.3"
-"@polkadot/util@10.4.2", "@polkadot/util@6.11.1", "@polkadot/util@7.9.2", "@polkadot/util@8.7.1", "@polkadot/util@^10.1.11", "@polkadot/util@^10.1.12", "@polkadot/util@^10.1.6", "@polkadot/util@^10.2.1", "@polkadot/util@^10.2.4", "@polkadot/util@^10.4.2", "@polkadot/util@^9.4.1":
+"@polkadot/util@10.3.1", "@polkadot/util@10.4.2", "@polkadot/util@6.11.1", "@polkadot/util@7.9.2", "@polkadot/util@8.7.1", "@polkadot/util@^10.1.6", "@polkadot/util@^10.2.1", "@polkadot/util@^10.2.4", "@polkadot/util@^10.3.1", "@polkadot/util@^10.4.2", "@polkadot/util@^9.4.1":
version "10.4.2"
resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-10.4.2.tgz#df41805cb27f46b2b4dad24c371fa2a68761baa1"
integrity sha512-0r5MGICYiaCdWnx+7Axlpvzisy/bi1wZGXgCSw5+ZTyPTOqvsYRqM2X879yxvMsGfibxzWqNzaiVjToz1jvUaA==
@@ -3197,7 +3299,7 @@
"@babel/runtime" "^7.20.13"
"@polkadot/x-global" "10.4.2"
-"@polkadot/x-fetch@^10.1.11", "@polkadot/x-fetch@^10.4.2":
+"@polkadot/x-fetch@^10.3.1", "@polkadot/x-fetch@^10.4.2":
version "10.4.2"
resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-10.4.2.tgz#bc6ba70de71a252472fbe36180511ed920e05f05"
integrity sha512-Ubb64yaM4qwhogNP+4mZ3ibRghEg5UuCYRMNaCFoPgNAY8tQXuDKrHzeks3+frlmeH9YRd89o8wXLtWouwZIcw==
@@ -4599,10 +4701,10 @@
"@polkadot/keyring" "^8.2.2"
"@polkadot/types" "^7.2.1"
-"@sora-substrate/type-definitions@1.10.21":
- version "1.10.21"
- resolved "https://registry.yarnpkg.com/@sora-substrate/type-definitions/-/type-definitions-1.10.21.tgz#1fb225cc8036729cdfb8fd2fcdc72bfa18251781"
- integrity sha512-QPtJk6ZjPK9RwpMG+YdMI319dRbSr01C5D52TNOf9UAk6FA9fGTXtn6kH6pR185Ssu/Ww50LmU+NpDP45RPYVA==
+"@sora-substrate/type-definitions@1.12.4":
+ version "1.12.4"
+ resolved "https://registry.yarnpkg.com/@sora-substrate/type-definitions/-/type-definitions-1.12.4.tgz#e4bfc1d5d58c20dd589cfcd73ab86f798684221f"
+ integrity sha512-G+s1DTKGfkncUXUPXQNNrbj/9ZnNxksEXBmqP/RQrmnfYE3C59P5Zkp+D98WsXobkWOnMxqBDlK+VbUQbvMoRA==
dependencies:
"@open-web3/orml-type-definitions" "0.9.4-26"
@@ -5434,7 +5536,7 @@
regenerator-runtime "^0.13.7"
resolve-from "^5.0.0"
-"@subsocial/definitions@^0.7.8-dev.0":
+"@subsocial/definitions@^0.7.9":
version "0.7.14"
resolved "https://registry.yarnpkg.com/@subsocial/definitions/-/definitions-0.7.14.tgz#1397f1ec806d60d9deb112b9f36d530400b711fe"
integrity sha512-dor5S6/tbY09n40e/dh7qFcqF9slMihOMDTXWBM5hTe8nS/Pf5Zp4/r9WiZxxYLoY2v5MlSqyJxjiSCjTxxjUw==
@@ -5465,9 +5567,9 @@
ws "^8.8.1"
"@substrate/ss58-registry@^1.38.0":
- version "1.39.0"
- resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.39.0.tgz#eb916ff5fea7fa02e77745823fde21af979273d2"
- integrity sha512-qZYpuE6n+mwew+X71dOur/CbMXj6rNW27o63JeJwdQH/GvcSKm3JLNhd+bGzwUKg0D/zD30Qc6p4JykArzM+tA==
+ version "1.38.0"
+ resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.38.0.tgz#b50cb28c77a0375fbf33dd29b7b28ee32871af9f"
+ integrity sha512-sHiVRWekGMRZAjPukN9/W166NM6D5wtHcK6RVyLy66kg3CHNZ1BXfpXcjOiXSwhbd7guQFDEwnOVaDrbk1XL1g==
"@surma/rollup-plugin-off-main-thread@^1.1.1":
version "1.4.2"
@@ -6311,20 +6413,20 @@
"@typescript-eslint/types" "4.33.0"
eslint-visitor-keys "^2.0.0"
-"@unique-nft/opal-testnet-types@930.31.0":
- version "930.31.0"
- resolved "https://registry.yarnpkg.com/@unique-nft/opal-testnet-types/-/opal-testnet-types-930.31.0.tgz#dc989976b5e91b4d8358a7af624d6e5e2ebf0b87"
- integrity sha512-IY4AxUx3uqjMEXy6iXrfVmu4oDTXOXaPjg5sb3WqnXpA7czjfSWZsQ/OtJFAWO+cbXUt8DM9ifs9/2hY2+O4RA==
+"@unique-nft/opal-testnet-types@930.34.0":
+ version "930.34.0"
+ resolved "https://registry.yarnpkg.com/@unique-nft/opal-testnet-types/-/opal-testnet-types-930.34.0.tgz#e4274976ebc9614dbec6c1a074674a3620eacb6f"
+ integrity sha512-6N5MQC5o4V5J0PZ/JmhRfYOtJTSpCjxxM1pdGysh6aIu/rSey8ELa/9BnGwLIZsOPxW77PKwnt7NIRc01Sze3g==
-"@unique-nft/quartz-mainnet-types@930.31.0":
- version "930.31.0"
- resolved "https://registry.yarnpkg.com/@unique-nft/quartz-mainnet-types/-/quartz-mainnet-types-930.31.0.tgz#009a37ac2dad085cfe0ebdca98de06f345fa35d6"
- integrity sha512-oZdHnX2TfglJ43PjKwAnUhx5VIcCDpeeQtRC8cXSvVKE2CqkW5ry/rgyavAs+HeUrwsD5JXHtebgH9P1dZ4vOw==
+"@unique-nft/quartz-mainnet-types@930.34.0":
+ version "930.34.0"
+ resolved "https://registry.yarnpkg.com/@unique-nft/quartz-mainnet-types/-/quartz-mainnet-types-930.34.0.tgz#d99a744b10575533441a0ca13f855eeca45a9047"
+ integrity sha512-YwJ3h7Q0crnvGsYfBXjxtPIpQnB9T5JY1LLAapLGvOO3A0iA1PWbSiqAgOdjZTt4zivYm3IbdhxQhyyY6d5jLA==
-"@unique-nft/unique-mainnet-types@930.31.0":
- version "930.31.0"
- resolved "https://registry.yarnpkg.com/@unique-nft/unique-mainnet-types/-/unique-mainnet-types-930.31.0.tgz#ea79a6fd3d9e8c115b13ef3048a87bf0a853c269"
- integrity sha512-acAKLL2TNS7X886SiNjMHo0dVmCECFd9vUSJxBZ1yjVVOhf6P6Nn+gA8jjgLKSg89VAqNQ9Op7a/vBN0Xo8+nw==
+"@unique-nft/unique-mainnet-types@930.33.0":
+ version "930.33.0"
+ resolved "https://registry.yarnpkg.com/@unique-nft/unique-mainnet-types/-/unique-mainnet-types-930.33.0.tgz#196bbe704882ad826b709c5ec9cbbb8067e456ee"
+ integrity sha512-KlliDzrwcyl1igi/rjltue/T6DZQP5yAijcFzWtCsKfLzkCPxcplzYgd5S+VKRoAFrndOMVXleXTUgpPSYiL9Q==
"@uphold/request-logger@^2.0.0":
version "2.0.0"
@@ -6614,10 +6716,10 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
-"@zeitgeistpm/type-defs@0.9.0":
- version "0.9.0"
- resolved "https://registry.yarnpkg.com/@zeitgeistpm/type-defs/-/type-defs-0.9.0.tgz#95496d4c7984c87cf53eeed1b97283e5c4538362"
- integrity sha512-H3EuEjrKtMlZBKEl08427Bda/c0t9BaUiwBNPn2T8ppM1RCEzfd1/3riHce6CyBCAQKR+w47Dylc+qK2VrBbNQ==
+"@zeitgeistpm/type-defs@0.10.0":
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/@zeitgeistpm/type-defs/-/type-defs-0.10.0.tgz#7f551f949b45b082541a254a9845ab15b2ff9148"
+ integrity sha512-nQBdyRbkIopPOVjRHk9c/RBWiQI6iYE8fs5rmtSNCXm6IxoXssk/1PtWE+UxXXq9mco7rPao9nJMeYXJ1Ro2kg==
"@zeroio/type-definitions@0.0.14":
version "0.0.14"
@@ -8160,6 +8262,13 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, can
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001342.tgz#87152b1e3b950d1fbf0093e23f00b6c8e8f1da96"
integrity sha512-bn6sOCu7L7jcbBbyNhLg0qzXdJ/PMbybZTH/BA6Roet9wxYRm6Tr9D0s0uhLkOZ6MSG+QU6txUgdpr3MXIVqjA==
+canvas-renderer@~2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/canvas-renderer/-/canvas-renderer-2.2.1.tgz#c1d131f78a9799aca8af9679ad0a005052b65550"
+ integrity sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==
+ dependencies:
+ "@types/node" "*"
+
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@@ -8547,7 +8656,7 @@ color-support@^1.1.2:
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
-color@^3.0.0:
+color@^3.0.0, color@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
@@ -10464,6 +10573,13 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+ethereum-blockies-base64@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/ethereum-blockies-base64/-/ethereum-blockies-base64-1.0.2.tgz#4aebca52142bf4d16a3144e6e2b59303e39ed2b3"
+ integrity sha512-Vg2HTm7slcWNKaRhCUl/L3b4KrB8ohQXdd5Pu3OI897EcR6tVRvUqdTwAyx+dnmoDzj8e2bwBLDQ50ByFmcz6w==
+ dependencies:
+ pnglib "0.0.1"
+
ethers@^5.6.2, ethers@~5.7.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e"
@@ -13086,6 +13202,13 @@ iterate-value@^1.0.2:
es-get-iterator "^1.0.2"
iterate-iterator "^1.0.1"
+jdenticon@3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/jdenticon/-/jdenticon-3.2.0.tgz#b5b9ef413cb66f70c600d6e69a764c977f248a46"
+ integrity sha512-z6Iq3fTODUMSOiR2nNYrqigS6Y0GvdXfyQWrUby7htDHvX7GNEwaWR4hcaL+FmhEgBe08Xkup/BKxXQhDJByPA==
+ dependencies:
+ canvas-renderer "~2.2.0"
+
jest-changed-files@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0"
@@ -15843,6 +15966,11 @@ please-upgrade-node@^3.2.0:
dependencies:
semver-compare "^1.0.0"
+pnglib@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/pnglib/-/pnglib-0.0.1.tgz#f9ab6f9c688f4a9d579ad8be28878a716e30c096"
+ integrity sha512-95ChzOoYLOPIyVmL+Y6X+abKGXUJlvOVLkB1QQkyXl7Uczc6FElUy/x01NS7r2GX6GRezloO/ecCX9h4U9KadA==
+
pnp-webpack-plugin@1.6.4:
version "1.6.4"
resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149"
@@ -17071,6 +17199,14 @@ react-chartjs-2@^2.11.1:
lodash "^4.17.19"
prop-types "^15.7.2"
+react-copy-to-clipboard@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz#09aae5ec4c62750ccb2e6421a58725eabc41255c"
+ integrity sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==
+ dependencies:
+ copy-to-clipboard "^3.3.1"
+ prop-types "^15.8.1"
+
react-dev-utils@^11.0.3:
version "11.0.4"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a"
@@ -19177,7 +19313,7 @@ style-to-object@0.3.0, style-to-object@^0.3.0:
dependencies:
inline-style-parser "0.1.1"
-styled-components@^5, styled-components@^5.3.5:
+styled-components@^5, styled-components@^5.3.5, styled-components@^5.3.6:
version "5.3.5"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.5.tgz#a750a398d01f1ca73af16a241dec3da6deae5ec4"
integrity sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==
From 65b0748a28359fedb8695c4999f8cfdd1bfd9443 Mon Sep 17 00:00:00 2001
From: Thomas Jeatt
Date: Mon, 22 May 2023 10:24:18 +0100
Subject: [PATCH 003/220] chore: release v2.32.0
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 1362974961..c87aea2430 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interbtc-ui",
- "version": "2.31.3",
+ "version": "2.32.0",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
From 8ff52df66060022c67842aad20fb1c4b8ca58389 Mon Sep 17 00:00:00 2001
From: ns212 <73105077+ns212@users.noreply.github.com>
Date: Mon, 22 May 2023 13:03:32 +0300
Subject: [PATCH 004/220] Update API healthchecks (#778)
* Chore - add vault healthcheck
* Chore - add vault healthcheck
* Chore - add vault healthcheck
---
api/health.py | 91 +++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 73 insertions(+), 18 deletions(-)
diff --git a/api/health.py b/api/health.py
index 6638f2a4e8..ae977b2b53 100644
--- a/api/health.py
+++ b/api/health.py
@@ -2,7 +2,7 @@
import dateutil.parser
from datetime import datetime
from dateutil.tz import tzutc
-from flask import Flask, jsonify
+from flask import Flask, jsonify, abort
class Oracle:
@@ -80,30 +80,85 @@ def isHealthy(self):
return status["chainHeightDiff"] < 3 and status["secondsDiff"] < 7200 # 2hrs
-KSM_URL = "https://api-kusama.interlay.io/graphql/graphql"
-INTR_URL = "https://api.interlay.io/graphql/graphql"
-
-app = Flask(__name__)
-
+class Vault:
+ def __init__(self, baseUrl) -> None:
+ self.baseUrl = baseUrl
-@app.route("/_health/ksm/oracle", methods=["GET"])
-def get_ksm_oracle_health():
- return jsonify(Oracle(KSM_URL, "KSM").isHealthy())
+ def _latestVaults(self):
+ q = """
+ query MyQuery {
+ vaults(limit: 10, orderBy: registrationBlock_active_DESC) {
+ id
+ registrationTimestamp
+ }
+ }
+ """
+ payload = {"query": q, "variables": None}
+ resp = requests.post(self.baseUrl, json=payload)
+ return resp.json()["data"]["vaults"]
+ def isHealthy(self):
+ vaults = self._latestVaults()
+ return len(self._latestVaults()) > 0
-@app.route("/_health/ksm/relay", methods=["GET"])
-def get_ksm_relayer_health():
- return jsonify(Relayer(KSM_URL).isHealthy())
+KSM_URL = "https://api-kusama.interlay.io/graphql/graphql"
+INTR_URL = "https://api.interlay.io/graphql/graphql"
+TESTNET_INTR = "https://api-testnet.interlay.io/graphql/graphql"
+TESTNET_KINT = "https://api-dev-kintsugi.interlay.io/graphql/graphql"
-@app.route("/_health/intr/oracle", methods=["GET"])
-def get_intr_oracle_health():
- return jsonify(Oracle(INTR_URL, "DOT").isHealthy())
+app = Flask(__name__)
-@app.route("/_health/intr/relay", methods=["GET"])
-def get_intr_relayer_health():
- return jsonify(Relayer(INTR_URL).isHealthy())
+@app.route("/_health//oracle", methods=["GET"])
+def get_oracle_health(chain):
+ def oracle():
+ if chain == "kint":
+ return Oracle(KSM_URL, "KSM")
+ elif chain == "intr":
+ return Oracle(INTR_URL, "DOT")
+ elif chain == "testnet_kint":
+ return Oracle(TESTNET_KINT, "KSM")
+ elif chain == "testnet_intr":
+ return Oracle(TESTNET_INTR, "DOT")
+ else:
+ abort(404)
+
+ return jsonify(oracle().isHealthy())
+
+
+@app.route("/_health//relay", methods=["GET"])
+def get_relay_health(chain):
+ def relay():
+ if chain == "kint":
+ return Relayer(KSM_URL)
+ elif chain == "intr":
+ return Relayer(INTR_URL)
+ elif chain == "testnet_kint":
+ return Relayer(TESTNET_KINT)
+ elif chain == "testnet_intr":
+ return Relayer(TESTNET_INTR)
+ else:
+ abort(404)
+
+ return jsonify(relay().isHealthy())
+
+
+@app.route("/_health//vault", methods=["GET"])
+def get_vault_health(chain):
+ def vault():
+ if chain == "kint":
+ return Vault(KSM_URL)
+ elif chain == "intr":
+ return Vault(INTR_URL)
+ elif chain == "testnet_kint":
+ return Vault(TESTNET_KINT)
+ elif chain == "testnet_intr":
+ return Vault(TESTNET_INTR)
+ else:
+ abort(404)
+
+ return jsonify(vault().isHealthy())
if __name__ == "__main__":
From e9157c74b80263b29d826b112fbf25029e9394c2 Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Mon, 22 May 2023 13:55:41 +0100
Subject: [PATCH 005/220] [earn strategies] placeholder page, nav and feature
flag (#1216)
* chore: bump icons dependency
* feature: earn strategies placeholder page and feature flag
---
.env.dev | 2 +-
package.json | 2 +-
src/App.tsx | 13 ++++++++++---
src/assets/locales/en/translation.json | 1 +
src/pages/EarnStrategies/EarnStrategies.tsx | 14 ++++++++++++++
src/pages/EarnStrategies/index.tsx | 3 +++
.../Sidebar/SidebarContent/Navigation/index.tsx | 16 +++++++++++++++-
src/utils/constants/links.ts | 1 +
src/utils/hooks/use-feature-flag.ts | 6 ++++--
yarn.lock | 8 ++++----
10 files changed, 54 insertions(+), 12 deletions(-)
create mode 100644 src/pages/EarnStrategies/EarnStrategies.tsx
create mode 100644 src/pages/EarnStrategies/index.tsx
diff --git a/.env.dev b/.env.dev
index 11f16713a9..95c64b470e 100644
--- a/.env.dev
+++ b/.env.dev
@@ -4,7 +4,7 @@ REACT_APP_FEATURE_FLAG_LENDING=enabled
REACT_APP_FEATURE_FLAG_AMM=enabled
REACT_APP_FEATURE_FLAG_WALLET=enabled
REACT_APP_FEATURE_FLAG_BANXA=enabled
-
+REACT_APP_FEATURE_FLAG_EARN_STRATEGIES=enabled
/* DEVELOPMENT */
diff --git a/package.json b/package.json
index c87aea2430..7e6cdc4d90 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"dependencies": {
"@craco/craco": "^6.1.1",
"@headlessui/react": "^1.1.1",
- "@heroicons/react": "^2.0.0",
+ "@heroicons/react": "^2.0.18",
"@interlay/bridge": "^0.3.9",
"@interlay/interbtc-api": "2.2.2",
"@interlay/monetary-js": "0.7.2",
diff --git a/src/App.tsx b/src/App.tsx
index 3a16fbf509..463f6f1528 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -26,6 +26,7 @@ import TestnetBanner from './legacy-components/TestnetBanner';
import { FeatureFlags, useFeatureFlag } from './utils/hooks/use-feature-flag';
const Bridge = React.lazy(() => import(/* webpackChunkName: 'bridge' */ '@/pages/Bridge'));
+const EarnStrategies = React.lazy(() => import(/* webpackChunkName: 'earn-strategies' */ '@/pages/EarnStrategies'));
const Transfer = React.lazy(() => import(/* webpackChunkName: 'transfer' */ '@/pages/Transfer'));
const Transactions = React.lazy(() => import(/* webpackChunkName: 'transactions' */ '@/pages/Transactions'));
const TX = React.lazy(() => import(/* webpackChunkName: 'tx' */ '@/pages/TX'));
@@ -35,9 +36,9 @@ const Vaults = React.lazy(() => import(/* webpackChunkName: 'vaults' */ '@/pages
// TODO: last task will be to delete legacy dashboard and rename vault dashboard
const Vault = React.lazy(() => import(/* webpackChunkName: 'vault' */ '@/pages/Vaults/Vault'));
const Loans = React.lazy(() => import(/* webpackChunkName: 'loans' */ '@/pages/Loans'));
-const Swap = React.lazy(() => import(/* webpackChunkName: 'loans' */ '@/pages/AMM'));
-const Pools = React.lazy(() => import(/* webpackChunkName: 'loans' */ '@/pages/AMM/Pools'));
-const Wallet = React.lazy(() => import(/* webpackChunkName: 'loans' */ '@/pages/Wallet'));
+const Swap = React.lazy(() => import(/* webpackChunkName: 'amm' */ '@/pages/AMM'));
+const Pools = React.lazy(() => import(/* webpackChunkName: 'amm/pools' */ '@/pages/AMM/Pools'));
+const Wallet = React.lazy(() => import(/* webpackChunkName: 'wallet' */ '@/pages/Wallet'));
const Actions = React.lazy(() => import(/* webpackChunkName: 'actions' */ '@/pages/Actions'));
const NoMatch = React.lazy(() => import(/* webpackChunkName: 'no-match' */ '@/pages/NoMatch'));
@@ -50,6 +51,7 @@ const App = (): JSX.Element => {
const isLendingEnabled = useFeatureFlag(FeatureFlags.LENDING);
const isAMMEnabled = useFeatureFlag(FeatureFlags.AMM);
const isWalletEnabled = useFeatureFlag(FeatureFlags.WALLET);
+ const isEarnStrategiesEnabled = useFeatureFlag(FeatureFlags.EARN_STRATEGIES);
// Loads the connection to the faucet - only for testnet purposes
const loadFaucet = React.useCallback(async (): Promise => {
@@ -212,6 +214,11 @@ const App = (): JSX.Element => {
)}
+ {isEarnStrategiesEnabled && (
+
+
+
+ )}
diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json
index 2959b1bd8d..e3a0d7164b 100644
--- a/src/assets/locales/en/translation.json
+++ b/src/assets/locales/en/translation.json
@@ -74,6 +74,7 @@
"issue": "Issue",
"redeem": "Redeem",
"nav_bridge": "Bridge",
+ "nav_earn_strategies": "Earn Strategies",
"nav_transfer": "Transfer",
"nav_lending": "Lending",
"nav_swap": "Swap",
diff --git a/src/pages/EarnStrategies/EarnStrategies.tsx b/src/pages/EarnStrategies/EarnStrategies.tsx
new file mode 100644
index 0000000000..84ab2c0d2b
--- /dev/null
+++ b/src/pages/EarnStrategies/EarnStrategies.tsx
@@ -0,0 +1,14 @@
+import { withErrorBoundary } from 'react-error-boundary';
+
+import ErrorFallback from '@/legacy-components/ErrorFallback';
+
+const EarnStrategies = (): JSX.Element => {
+ return Earn Strategies ;
+};
+
+export default withErrorBoundary(EarnStrategies, {
+ FallbackComponent: ErrorFallback,
+ onReset: () => {
+ window.location.reload();
+ }
+});
diff --git a/src/pages/EarnStrategies/index.tsx b/src/pages/EarnStrategies/index.tsx
new file mode 100644
index 0000000000..7a73a2c641
--- /dev/null
+++ b/src/pages/EarnStrategies/index.tsx
@@ -0,0 +1,3 @@
+import EarnStrategies from './EarnStrategies';
+
+export default EarnStrategies;
diff --git a/src/parts/Sidebar/SidebarContent/Navigation/index.tsx b/src/parts/Sidebar/SidebarContent/Navigation/index.tsx
index 83581e5880..54dabf7446 100644
--- a/src/parts/Sidebar/SidebarContent/Navigation/index.tsx
+++ b/src/parts/Sidebar/SidebarContent/Navigation/index.tsx
@@ -70,6 +70,7 @@ const Navigation = ({
const isLendingEnabled = useFeatureFlag(FeatureFlags.LENDING);
const isAMMEnabled = useFeatureFlag(FeatureFlags.AMM);
const isWalletEnabled = useFeatureFlag(FeatureFlags.WALLET);
+ const isEarnStrategiesEnabled = useFeatureFlag(FeatureFlags.EARN_STRATEGIES);
const NAVIGATION_ITEMS = React.useMemo(
() => [
@@ -79,6 +80,12 @@ const Navigation = ({
icon: UserIcon,
disabled: !isWalletEnabled
},
+ {
+ name: 'nav_earn_strategies',
+ link: PAGES.EARN_STRATEGIES,
+ icon: BanknotesIcon,
+ disabled: !isEarnStrategiesEnabled
+ },
{
name: 'nav_bridge',
link: PAGES.BRIDGE,
@@ -193,7 +200,14 @@ const Navigation = ({
}
}
],
- [isWalletEnabled, isLendingEnabled, isAMMEnabled, selectedAccount?.address, vaultClientLoaded]
+ [
+ isWalletEnabled,
+ isEarnStrategiesEnabled,
+ isLendingEnabled,
+ isAMMEnabled,
+ selectedAccount?.address,
+ vaultClientLoaded
+ ]
);
return (
diff --git a/src/utils/constants/links.ts b/src/utils/constants/links.ts
index 7c64f49388..fb189728c4 100644
--- a/src/utils/constants/links.ts
+++ b/src/utils/constants/links.ts
@@ -12,6 +12,7 @@ const URL_PARAMETERS = Object.freeze({
const PAGES = Object.freeze({
HOME: '/',
BRIDGE: '/bridge',
+ EARN_STRATEGIES: '/earn-strategies',
TRANSFER: '/transfer',
TRANSACTIONS: '/transactions',
TX: '/tx',
diff --git a/src/utils/hooks/use-feature-flag.ts b/src/utils/hooks/use-feature-flag.ts
index 9b5676a053..8f9254eab3 100644
--- a/src/utils/hooks/use-feature-flag.ts
+++ b/src/utils/hooks/use-feature-flag.ts
@@ -2,14 +2,16 @@ enum FeatureFlags {
LENDING = 'lending',
AMM = 'amm',
WALLET = 'wallet',
- BANXA = 'banxa'
+ BANXA = 'banxa',
+ EARN_STRATEGIES = 'earn-strategies'
}
const featureFlags: Record = {
[FeatureFlags.LENDING]: process.env.REACT_APP_FEATURE_FLAG_LENDING,
[FeatureFlags.AMM]: process.env.REACT_APP_FEATURE_FLAG_AMM,
[FeatureFlags.WALLET]: process.env.REACT_APP_FEATURE_FLAG_WALLET,
- [FeatureFlags.BANXA]: process.env.REACT_APP_FEATURE_FLAG_BANXA
+ [FeatureFlags.BANXA]: process.env.REACT_APP_FEATURE_FLAG_BANXA,
+ [FeatureFlags.EARN_STRATEGIES]: process.env.REACT_APP_FEATURE_FLAG_EARN_STRATEGIES
};
const useFeatureFlag = (feature: FeatureFlags): boolean => featureFlags[feature] === 'enabled';
diff --git a/yarn.lock b/yarn.lock
index c887f3b4fd..e7e055b8a8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2075,10 +2075,10 @@
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.5.0.tgz#483b44ba2c8b8d4391e1d2c863898d7dd0cc0296"
integrity sha512-aaRnYxBb3MU2FNJf3Ut9RMTUqqU3as0aI1lQhgo2n9Fa67wRu14iOGqx93xB+uMNVfNwZ5B3y/Ndm7qZGuFeMQ==
-"@heroicons/react@^2.0.0":
- version "2.0.12"
- resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.0.12.tgz#7e5a16c82512f89a30266dd36f8b8465b3e3e216"
- integrity sha512-FZxKh3i9aKIDxyALTgIpSF2t6V6/eZfF5mRu41QlwkX3Oxzecdm1u6dpft6PQGxIBwO7TKYWaMAYYL8mp/EaOg==
+"@heroicons/react@^2.0.18":
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.0.18.tgz#f80301907c243df03c7e9fd76c0286e95361f7c1"
+ integrity sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw==
"@humanwhocodes/config-array@^0.5.0":
version "0.5.0"
From b04bedae7b8eb9a8b4a51bcf25c476945095e592 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Sim=C3=A3o?=
Date: Tue, 23 May 2023 10:44:46 +0100
Subject: [PATCH 006/220] feat: add useTransaction (#1189)
---
.../ConfirmedIssueRequest/index.tsx | 37 ++---
.../ManualIssueExecutionUI/index.tsx | 44 +++---
.../components/DepositForm/DepositForm.tsx | 26 +---
.../PoolsInsights/PoolsInsights.tsx | 15 +-
.../components/WithdrawForm/WithdrawForm.tsx | 25 +---
.../AMM/Swap/components/SwapForm/SwapForm.tsx | 55 +++-----
src/pages/Bridge/BurnForm/index.tsx | 9 +-
src/pages/Bridge/IssueForm/index.tsx | 12 +-
src/pages/Bridge/RedeemForm/index.tsx | 27 ++--
.../CollateralModal/CollateralModal.tsx | 48 +++----
.../components/LoanForm/LoanForm.tsx | 59 +++++---
.../LoansInsights/LoansInsights.tsx | 30 ++--
.../Staking/ClaimRewardsButton/index.tsx | 32 ++---
src/pages/Staking/index.tsx | 123 +++++++---------
src/pages/Transfer/TransferForm/index.tsx | 12 +-
.../Vaults/Vault/RequestIssueModal/index.tsx | 11 +-
.../Vaults/Vault/RequestRedeemModal/index.tsx | 10 +-
.../Vault/RequestReplacementModal/index.tsx | 10 +-
.../Vault/UpdateCollateralModal/index.tsx | 8 +-
.../Vault/components/Rewards/Rewards.tsx | 56 ++++----
.../DespositCollateralStep.tsx | 27 ++--
.../hooks/api/loans/use-loan-mutation.tsx | 49 -------
src/utils/hooks/transaction/index.ts | 2 +
src/utils/hooks/transaction/types/amm.ts | 28 ++++
src/utils/hooks/transaction/types/escrow.ts | 46 ++++++
src/utils/hooks/transaction/types/index.ts | 79 +++++++++++
src/utils/hooks/transaction/types/issue.ts | 18 +++
src/utils/hooks/transaction/types/loans.ts | 62 ++++++++
src/utils/hooks/transaction/types/redeem.ts | 23 +++
src/utils/hooks/transaction/types/replace.ts | 13 ++
src/utils/hooks/transaction/types/rewards.ts | 13 ++
src/utils/hooks/transaction/types/tokens.ts | 13 ++
src/utils/hooks/transaction/types/vaults.ts | 23 +++
.../hooks/transaction/use-transaction.ts | 122 ++++++++++++++++
.../hooks/transaction/utils/extrinsic.ts | 133 ++++++++++++++++++
src/utils/hooks/transaction/utils/submit.ts | 107 ++++++++++++++
36 files changed, 960 insertions(+), 447 deletions(-)
delete mode 100644 src/utils/hooks/api/loans/use-loan-mutation.tsx
create mode 100644 src/utils/hooks/transaction/index.ts
create mode 100644 src/utils/hooks/transaction/types/amm.ts
create mode 100644 src/utils/hooks/transaction/types/escrow.ts
create mode 100644 src/utils/hooks/transaction/types/index.ts
create mode 100644 src/utils/hooks/transaction/types/issue.ts
create mode 100644 src/utils/hooks/transaction/types/loans.ts
create mode 100644 src/utils/hooks/transaction/types/redeem.ts
create mode 100644 src/utils/hooks/transaction/types/replace.ts
create mode 100644 src/utils/hooks/transaction/types/rewards.ts
create mode 100644 src/utils/hooks/transaction/types/tokens.ts
create mode 100644 src/utils/hooks/transaction/types/vaults.ts
create mode 100644 src/utils/hooks/transaction/use-transaction.ts
create mode 100644 src/utils/hooks/transaction/utils/extrinsic.ts
create mode 100644 src/utils/hooks/transaction/utils/submit.ts
diff --git a/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx b/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
index 3a758d2989..e76d52fe2d 100644
--- a/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
+++ b/src/legacy-components/IssueUI/IssueRequestStatusUI/ConfirmedIssueRequest/index.tsx
@@ -1,8 +1,7 @@
-import { ISubmittableResult } from '@polkadot/types/types';
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
import { FaCheckCircle } from 'react-icons/fa';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { BTC_EXPLORER_TRANSACTION_API } from '@/config/blockstream-explorer-links';
@@ -16,7 +15,7 @@ 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 { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useQueryParams from '@/utils/hooks/use-query-params';
import ManualIssueExecutionUI from '../ManualIssueExecutionUI';
@@ -34,21 +33,15 @@ const ConfirmedIssueRequest = ({ request }: Props): JSX.Element => {
const selectedPageIndex = selectedPage - 1;
const queryClient = useQueryClient();
- // TODO: should type properly (`Relay`)
- const executeMutation = useMutation(
- (variables: any) => {
- if (!variables.backingPayment.btcTxId) {
- throw new Error('Bitcoin transaction ID not identified yet.');
- }
- return submitExtrinsicPromise(window.bridge.issue.execute(variables.id, variables.backingPayment.btcTxId));
- },
- {
- onSuccess: (_, variables) => {
- queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
- toast.success(t('issue_page.successfully_executed', { id: variables.id }));
- }
+
+ // 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 (
<>
@@ -82,16 +75,14 @@ const ConfirmedIssueRequest = ({ request }: Props): JSX.Element => {
- {executeMutation.isError && executeMutation.error && (
+ {transaction.isError && transaction.error && (
{
- executeMutation.reset();
+ transaction.reset();
}}
title='Error'
- description={
- typeof executeMutation.error === 'string' ? executeMutation.error : executeMutation.error.message
- }
+ 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 5aee169f45..c93aa9aae2 100644
--- a/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
+++ b/src/legacy-components/IssueUI/IssueRequestStatusUI/ManualIssueExecutionUI/index.tsx
@@ -5,10 +5,9 @@ import {
newAccountId,
newMonetaryAmount
} from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
import clsx from 'clsx';
import { useTranslation } from 'react-i18next';
-import { useMutation, useQuery, useQueryClient } from 'react-query';
+import { useQuery, useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { displayMonetaryAmount } from '@/common/utils/utils';
@@ -21,7 +20,7 @@ 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 { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useQueryParams from '@/utils/hooks/use-query-params';
// TODO: issue requests should not be typed here but further above in the app
@@ -57,21 +56,13 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
const queryClient = useQueryClient();
- // TODO: should type properly (`Relay`)
- const executeMutation = useMutation(
- (variables: any) => {
- if (!variables.backingPayment.btcTxId) {
- throw new Error('Bitcoin transaction ID not identified yet.');
- }
- return submitExtrinsicPromise(window.bridge.issue.execute(variables.id, variables.backingPayment.btcTxId));
- },
- {
- onSuccess: (_, variables) => {
- queryClient.invalidateQueries([ISSUES_FETCHER, selectedPageIndex * TABLE_PAGE_LIMIT, TABLE_PAGE_LIMIT]);
- toast.success(t('issue_page.successfully_executed', { id: variables.id }));
- }
+ 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 }));
}
- );
+ });
const { data: vaultCapacity, error: vaultCapacityError } = useQuery({
queryKey: 'vault-capacity',
@@ -91,7 +82,12 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
// TODO: should type properly (`Relay`)
const handleExecute = (request: any) => () => {
- executeMutation.mutate(request);
+ if (!request.backingPayment.btcTxId) {
+ console.error('Bitcoin transaction ID not identified yet.');
+ return;
+ }
+
+ transaction.execute(request.id, request.backingPayment.btcTxId);
};
const backingPaymentAmount = newMonetaryAmount(request.backingPayment.amount, WRAPPED_TOKEN);
@@ -135,7 +131,7 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
)}
@@ -143,16 +139,14 @@ const ManualIssueExecutionUI = ({ request }: Props): JSX.Element => {
wrappedTokenSymbol: WRAPPED_TOKEN_SYMBOL
})}
- {executeMutation.isError && executeMutation.error && (
+ {transaction.isError && transaction.error && (
{
- executeMutation.reset();
+ transaction.reset();
}}
title='Error'
- description={
- typeof executeMutation.error === 'string' ? executeMutation.error : executeMutation.error.message
- }
+ description={typeof transaction.error === 'string' ? transaction.error : transaction.error.message}
/>
)}
diff --git a/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx b/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
index 4a7030bc4c..4baf947a1b 100644
--- a/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
+++ b/src/pages/AMM/Pools/components/DepositForm/DepositForm.tsx
@@ -1,11 +1,8 @@
-import { CurrencyExt, LiquidityPool, newMonetaryAmount, PooledCurrencies } from '@interlay/interbtc-api';
-import { AccountId } from '@polkadot/types/interfaces';
-import { ISubmittableResult } from '@polkadot/types/types';
+import { CurrencyExt, LiquidityPool, newMonetaryAmount } from '@interlay/interbtc-api';
import { mergeProps } from '@react-aria/utils';
import Big from 'big.js';
import { ChangeEventHandler, RefObject, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import { displayMonetaryAmountInUSDFormat, newSafeMonetaryAmount } from '@/common/utils/utils';
@@ -21,10 +18,10 @@ import {
} from '@/lib/form';
import { SlippageManager } from '@/pages/AMM/shared/components';
import { AMM_DEADLINE_INTERVAL } from '@/utils/constants/api';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { PoolName } from '../PoolName';
@@ -35,17 +32,6 @@ import { DepositOutputAssets } from './DepositOutputAssets';
const isCustomAmountsMode = (form: ReturnType) =>
form.dirty && Object.values(form.touched).filter(Boolean).length > 0;
-type DepositData = {
- amounts: PooledCurrencies;
- pool: LiquidityPool;
- slippage: number;
- deadline: number;
- accountId: AccountId;
-};
-
-const mutateDeposit = ({ amounts, pool, slippage, deadline, accountId }: DepositData) =>
- submitExtrinsic(window.bridge.amm.addLiquidity(amounts, pool, slippage, deadline, accountId));
-
type DepositFormProps = {
pool: LiquidityPool;
slippageModalRef: RefObject;
@@ -65,7 +51,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
const governanceBalance = getBalance(GOVERNANCE_TOKEN.ticker)?.free || newMonetaryAmount(0, GOVERNANCE_TOKEN);
- const depositMutation = useMutation(mutateDeposit, {
+ const transaction = useTransaction(Transaction.AMM_ADD_LIQUIDITY, {
onSuccess: () => {
onDeposit?.();
toast.success('Deposit successful');
@@ -85,7 +71,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
const deadline = await window.bridge.system.getFutureBlockNumber(AMM_DEADLINE_INTERVAL);
- return depositMutation.mutate({ amounts, pool, slippage, deadline, accountId });
+ return transaction.execute(amounts, pool, slippage, deadline, accountId);
} catch (err: any) {
toast.error(err.toString());
}
@@ -106,7 +92,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
initialValues: defaultValues,
validationSchema: depositLiquidityPoolSchema({ transactionFee: TRANSACTION_FEE_AMOUNT, governanceBalance, tokens }),
onSubmit: handleSubmit,
- disableValidation: depositMutation.isLoading
+ disableValidation: transaction.isLoading
});
const handleChange: ChangeEventHandler = (e) => {
@@ -203,7 +189,7 @@ const DepositForm = ({ pool, slippageModalRef, onDeposit }: DepositFormProps): J
-
+
{t('amm.pools.add_liquidity')}
diff --git a/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx b/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
index a845df1639..1689c20a32 100644
--- a/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
+++ b/src/pages/AMM/Pools/components/PoolsInsights/PoolsInsights.tsx
@@ -1,16 +1,15 @@
import { LiquidityPool } from '@interlay/interbtc-api';
import Big from 'big.js';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import { formatUSD } from '@/common/utils/utils';
import { Card, Dl, DlGroup } from '@/component-library';
import { AuthCTA } from '@/components';
import { calculateAccountLiquidityUSD, calculateTotalLiquidityUSD } from '@/pages/AMM/shared/utils';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { AccountPoolsData } from '@/utils/hooks/api/amm/use-get-account-pools';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { StyledDd, StyledDt } from './PoolsInsights.style';
import { calculateClaimableFarmingRewardUSD } from './utils';
@@ -55,17 +54,11 @@ const PoolsInsights = ({ pools, accountPoolsData, refetch }: PoolsInsightsProps)
refetch();
};
- const mutateClaimRewards = async () => {
- if (accountPoolsData !== undefined) {
- await submitExtrinsic(window.bridge.amm.claimFarmingRewards(accountPoolsData.claimableRewards));
- }
- };
-
- const claimRewardsMutation = useMutation(mutateClaimRewards, {
+ const transaction = useTransaction(Transaction.AMM_CLAIM_REWARDS, {
onSuccess: handleSuccess
});
- const handleClickClaimRewards = () => claimRewardsMutation.mutate();
+ const handleClickClaimRewards = () => accountPoolsData && transaction.execute(accountPoolsData.claimableRewards);
const hasClaimableRewards = totalClaimableRewardUSD > 0;
return (
@@ -88,7 +81,7 @@ const PoolsInsights = ({ pools, accountPoolsData, refetch }: PoolsInsightsProps)
{formatUSD(totalClaimableRewardUSD, { compact: true })}
{hasClaimableRewards && (
-
+
Claim
)}
diff --git a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
index 3eeb392479..4f2ea60b55 100644
--- a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
+++ b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
@@ -1,11 +1,7 @@
-import { LiquidityPool, LpCurrency, newMonetaryAmount } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { AccountId } from '@polkadot/types/interfaces';
-import { ISubmittableResult } from '@polkadot/types/types';
+import { LiquidityPool, newMonetaryAmount } from '@interlay/interbtc-api';
import Big from 'big.js';
import { RefObject, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import {
@@ -20,27 +16,16 @@ import { isFormDisabled, useForm, WITHDRAW_LIQUIDITY_POOL_FIELD } from '@/lib/fo
import { WithdrawLiquidityPoolFormData, withdrawLiquidityPoolSchema } from '@/lib/form/schemas';
import { SlippageManager } from '@/pages/AMM/shared/components';
import { AMM_DEADLINE_INTERVAL } from '@/utils/constants/api';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { PoolName } from '../PoolName';
import { WithdrawAssets } from './WithdrawAssets';
import { StyledDl } from './WithdrawForm.styles';
-type DepositData = {
- amount: MonetaryAmount;
- pool: LiquidityPool;
- slippage: number;
- deadline: number;
- accountId: AccountId;
-};
-
-const mutateWithdraw = ({ amount, pool, slippage, deadline, accountId }: DepositData) =>
- submitExtrinsic(window.bridge.amm.removeLiquidity(amount, pool, slippage, deadline, accountId));
-
type WithdrawFormProps = {
pool: LiquidityPool;
slippageModalRef: RefObject;
@@ -55,7 +40,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
const prices = useGetPrices();
const { getBalance } = useGetBalances();
- const withdrawMutation = useMutation(mutateWithdraw, {
+ const transaction = useTransaction(Transaction.AMM_REMOVE_LIQUIDITY, {
onSuccess: () => {
onWithdraw?.();
toast.success('Withdraw successful');
@@ -85,7 +70,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
const amount = newMonetaryAmount(data[WITHDRAW_LIQUIDITY_POOL_FIELD] || 0, lpToken, true);
const deadline = await window.bridge.system.getFutureBlockNumber(AMM_DEADLINE_INTERVAL);
- return withdrawMutation.mutate({ amount, pool, deadline, slippage, accountId });
+ return transaction.execute(amount, pool, slippage, deadline, accountId);
} catch (err: any) {
toast.error(err.toString());
}
@@ -157,7 +142,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
-
+
{t('amm.pools.remove_liquidity')}
diff --git a/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx b/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
index 716ca91398..a4d60bbcf6 100644
--- a/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
+++ b/src/pages/AMM/Swap/components/SwapForm/SwapForm.tsx
@@ -1,12 +1,8 @@
import { CurrencyExt, LiquidityPool, newMonetaryAmount, Trade } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { AddressOrPair } from '@polkadot/api/types';
-import { ISubmittableResult } from '@polkadot/types/types';
import { mergeProps } from '@react-aria/utils';
import Big from 'big.js';
import { ChangeEventHandler, Key, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import { useDebounce } from 'react-use';
@@ -26,11 +22,11 @@ import {
import { SlippageManager } from '@/pages/AMM/shared/components';
import { SwapPair } from '@/types/swap';
import { SWAP_PRICE_IMPACT_LIMIT } from '@/utils/constants/swap';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetCurrencies } from '@/utils/hooks/api/use-get-currencies';
import { Prices, useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { PriceImpactModal } from '../PriceImpactModal';
@@ -83,16 +79,6 @@ const getPoolPriceImpact = (trade: Trade | null | undefined, inputAmountUSD: num
: new Big(0)
});
-type SwapData = {
- trade: Trade;
- minimumAmountOut: MonetaryAmount;
- recipient: AddressOrPair;
- deadline: string | number;
-};
-
-const mutateSwap = ({ deadline, minimumAmountOut, recipient, trade }: SwapData) =>
- submitExtrinsic(window.bridge.amm.swap(trade, minimumAmountOut, recipient, deadline));
-
type Props = {
pair: SwapPair;
liquidityPools: LiquidityPool[];
@@ -126,6 +112,18 @@ const SwapForm = ({
const { data: balances, getBalance, getAvailableBalance } = useGetBalances();
const { data: currencies } = useGetCurrencies(bridgeLoaded);
+ const transaction = useTransaction(Transaction.AMM_SWAP, {
+ onSuccess: () => {
+ toast.success('Swap successful');
+ setTrade(undefined);
+ setInputAmount(undefined);
+ onSwap();
+ },
+ onError: (err) => {
+ toast.error(err.message);
+ }
+ });
+
useDebounce(
() => {
if (!pair.input || !pair.output || !inputAmount) {
@@ -141,18 +139,6 @@ const SwapForm = ({
[inputAmount, pair]
);
- const swapMutation = useMutation(mutateSwap, {
- onSuccess: () => {
- toast.success('Swap successful');
- setTrade(undefined);
- setInputAmount(undefined);
- onSwap();
- },
- onError: (err) => {
- toast.error(err.message);
- }
- });
-
const inputBalance = pair.input && getAvailableBalance(pair.input.ticker);
const outputBalance = pair.output && getAvailableBalance(pair.output.ticker);
@@ -174,12 +160,7 @@ const SwapForm = ({
const deadline = await window.bridge.system.getFutureBlockNumber(30 * 60);
- return swapMutation.mutate({
- trade,
- recipient: accountId,
- minimumAmountOut,
- deadline
- });
+ return transaction.execute(trade, minimumAmountOut, accountId, deadline);
} catch (err: any) {
toast.error(err.toString());
}
@@ -212,7 +193,7 @@ const SwapForm = ({
initialValues,
validationSchema: swapSchema({ [SWAP_INPUT_AMOUNT_FIELD]: inputSchemaParams }),
onSubmit: handleSubmit,
- disableValidation: swapMutation.isLoading,
+ disableValidation: transaction.isLoading,
validateOnMount: true
});
@@ -239,11 +220,11 @@ const SwapForm = ({
useEffect(() => {
const isAmountFieldEmpty = form.values[SWAP_INPUT_AMOUNT_FIELD] === '';
- if (isAmountFieldEmpty || !swapMutation.isSuccess) return;
+ if (isAmountFieldEmpty || !transaction.isSuccess) return;
form.setFieldValue(SWAP_INPUT_AMOUNT_FIELD, '');
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [swapMutation.isSuccess]);
+ }, [transaction.isSuccess]);
const handleChangeInput: ChangeEventHandler = (e) => {
setInputAmount(e.target.value);
@@ -341,7 +322,7 @@ const SwapForm = ({
/>
{trade && }
-
+
diff --git a/src/pages/Bridge/BurnForm/index.tsx b/src/pages/Bridge/BurnForm/index.tsx
index 622b842372..2063218a57 100644
--- a/src/pages/Bridge/BurnForm/index.tsx
+++ b/src/pages/Bridge/BurnForm/index.tsx
@@ -25,11 +25,11 @@ import Tokens, { TokenOption } from '@/legacy-components/Tokens';
import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetCollateralCurrencies } from '@/utils/hooks/api/use-get-collateral-currencies';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const WRAPPED_TOKEN_AMOUNT = 'wrapped-token-amount';
@@ -73,6 +73,8 @@ const BurnForm = (): JSX.Element | null => {
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
const [submitError, setSubmitError] = React.useState(null);
+ const transaction = useTransaction(Transaction.REDEEM_BURN);
+
const handleUpdateCollateral = (collateral: TokenOption) => {
const selectedCollateral = burnableCollateral?.find(
(token: BurnableCollateral) => token.currency.ticker === collateral.token.ticker
@@ -150,9 +152,8 @@ const BurnForm = (): JSX.Element | null => {
const onSubmit = async (data: BurnFormData) => {
try {
setSubmitStatus(STATUSES.PENDING);
- await submitExtrinsic(
- window.bridge.redeem.burn(new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT]), selectedCollateral.currency)
- );
+
+ await transaction.executeAsync(new BitcoinAmount(data[WRAPPED_TOKEN_AMOUNT]), selectedCollateral.currency);
setSubmitStatus(STATUSES.RESOLVED);
} catch (error) {
diff --git a/src/pages/Bridge/IssueForm/index.tsx b/src/pages/Bridge/IssueForm/index.tsx
index 6a871b7c1c..2e3b83db45 100644
--- a/src/pages/Bridge/IssueForm/index.tsx
+++ b/src/pages/Bridge/IssueForm/index.tsx
@@ -56,11 +56,11 @@ import genericFetcher, { GENERIC_FETCHER } from '@/services/fetchers/generic-fet
import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import ManualVaultSelectUI from '../ManualVaultSelectUI';
import SubmittedIssueRequestModal from './SubmittedIssueRequestModal';
@@ -142,6 +142,8 @@ const IssueForm = (): JSX.Element | null => {
});
useErrorHandler(requestLimitsError);
+ const transaction = useTransaction(Transaction.ISSUE_REQUEST);
+
React.useEffect(() => {
if (!bridgeLoaded) return;
if (!dispatch) return;
@@ -321,18 +323,14 @@ const IssueForm = (): JSX.Element | null => {
const collateralToken = await currencyIdToMonetaryCurrency(window.bridge.api, vaultId.currencies.collateral);
- const extrinsicData = await window.bridge.issue.request(
+ const result = await transaction.executeAsync(
monetaryBtcAmount,
vaultId.accountId,
collateralToken,
false, // default
vaults
);
- // When requesting an issue, 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
- const finalizedStatus = getExtrinsicStatus('Finalized');
- const extrinsicResult = await submitExtrinsic(extrinsicData, finalizedStatus);
- const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, extrinsicResult);
+ const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, result);
// TODO: handle issue aggregation
const issueRequest = issueRequests[0];
diff --git a/src/pages/Bridge/RedeemForm/index.tsx b/src/pages/Bridge/RedeemForm/index.tsx
index 1248b5167d..357bfcf540 100644
--- a/src/pages/Bridge/RedeemForm/index.tsx
+++ b/src/pages/Bridge/RedeemForm/index.tsx
@@ -1,5 +1,10 @@
-import { CollateralCurrencyExt, InterbtcPrimitivesVaultId, newMonetaryAmount, Redeem } from '@interlay/interbtc-api';
-import { getRedeemRequestsFromExtrinsicResult } from '@interlay/interbtc-api';
+import {
+ CollateralCurrencyExt,
+ getRedeemRequestsFromExtrinsicResult,
+ InterbtcPrimitivesVaultId,
+ newMonetaryAmount,
+ Redeem
+} from '@interlay/interbtc-api';
import { Bitcoin, BitcoinAmount, ExchangeRate } from '@interlay/monetary-js';
import Big from 'big.js';
import clsx from 'clsx';
@@ -44,11 +49,11 @@ import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
import { getColorShade } from '@/utils/helpers/colors';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import ManualVaultSelectUI from '../ManualVaultSelectUI';
import SubmittedRedeemRequestModal from './SubmittedRedeemRequestModal';
@@ -114,6 +119,8 @@ const RedeemForm = (): JSX.Element | null => {
const [selectedVault, setSelectedVault] = React.useState();
+ const transaction = useTransaction(Transaction.REDEEM_REQUEST);
+
React.useEffect(() => {
if (!monetaryWrappedTokenAmount) return;
if (!maxRedeemableCapacity) return;
@@ -295,16 +302,10 @@ const RedeemForm = (): JSX.Element | null => {
const relevantVaults = new Map();
// FIXME: a bit of a dirty workaround with the capacity
relevantVaults.set(vaultId, monetaryWrappedTokenAmount.mul(2));
- const extrinsicData = await window.bridge.redeem.request(
- monetaryWrappedTokenAmount,
- data[BTC_ADDRESS],
- vaultId
- );
- // When requesting a redeem, 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
- const finalizedStatus = getExtrinsicStatus('Finalized');
- const extrinsicResult = await submitExtrinsic(extrinsicData, finalizedStatus);
- const redeemRequests = await getRedeemRequestsFromExtrinsicResult(window.bridge, extrinsicResult);
+
+ const result = await transaction.executeAsync(monetaryWrappedTokenAmount, data[BTC_ADDRESS], vaultId);
+
+ const redeemRequests = await getRedeemRequestsFromExtrinsicResult(window.bridge, result);
// TODO: handle redeem aggregator
const redeemRequest = redeemRequests[0];
diff --git a/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx b/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx
index 9b545e17ff..2ec3d03b04 100644
--- a/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx
+++ b/src/pages/Loans/LoansOverview/components/CollateralModal/CollateralModal.tsx
@@ -1,31 +1,19 @@
-import { CollateralPosition, CurrencyExt, LoanAsset } from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
+import { CollateralPosition, LoanAsset } from '@interlay/interbtc-api';
import { TFunction, useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { toast } from 'react-toastify';
import { Flex, Modal, ModalBody, ModalFooter, ModalHeader, ModalProps, Status } from '@/component-library';
import { AuthCTA } from '@/components';
import ErrorModal from '@/legacy-components/ErrorModal';
-import { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
import { useGetAccountLendingStatistics } from '@/utils/hooks/api/loans/use-get-account-lending-statistics';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useGetLTV } from '../../hooks/use-get-ltv';
import { BorrowLimit } from '../BorrowLimit';
import { LoanActionInfo } from '../LoanActionInfo';
import { StyledDescription } from './CollateralModal.style';
-type ToggleCollateralVariables = { isEnabling: boolean; underlyingCurrency: CurrencyExt };
-
-const toggleCollateral = ({ isEnabling, underlyingCurrency }: ToggleCollateralVariables) => {
- if (isEnabling) {
- return submitExtrinsicPromise(window.bridge.loans.enableAsCollateral(underlyingCurrency));
- } else {
- return submitExtrinsicPromise(window.bridge.loans.disableAsCollateral(underlyingCurrency));
- }
-};
-
type CollateralModalVariant = 'enable' | 'disable' | 'disable-error';
const getContentMap = (t: TFunction, variant: CollateralModalVariant, asset: LoanAsset) =>
@@ -73,14 +61,12 @@ const CollateralModal = ({ asset, position, onClose, ...props }: CollateralModal
const { getLTV } = useGetLTV();
const prices = useGetPrices();
- const handleSuccess = () => {
- toast.success('Successfully toggled collateral');
- onClose?.();
- refetch();
- };
-
- const toggleCollateralMutation = useMutation(toggleCollateral, {
- onSuccess: handleSuccess
+ const transaction = useTransaction({
+ onSuccess: () => {
+ toast.success('Successfully toggled collateral');
+ onClose?.();
+ refetch();
+ }
});
if (!asset || !position) {
@@ -100,9 +86,11 @@ const CollateralModal = ({ asset, position, onClose, ...props }: CollateralModal
return onClose?.();
}
- const isEnabling = variant === 'enable';
-
- return toggleCollateralMutation.mutate({ isEnabling, underlyingCurrency: position.amount.currency });
+ if (variant === 'enable') {
+ return transaction.execute(Transaction.LOANS_ENABLE_COLLATERAL, asset.currency);
+ } else {
+ return transaction.execute(Transaction.LOANS_DISABLE_COLLATERAL, asset.currency);
+ }
};
return (
@@ -117,17 +105,17 @@ const CollateralModal = ({ asset, position, onClose, ...props }: CollateralModal
-
+
{content.buttonLabel}
- {toggleCollateralMutation.isError && (
+ {transaction.isError && (
toggleCollateralMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={toggleCollateralMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx b/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
index bd89f6b57e..edc7763901 100644
--- a/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
+++ b/src/pages/Loans/LoansOverview/components/LoanForm/LoanForm.tsx
@@ -12,8 +12,8 @@ import { AuthCTA } from '@/components';
import { isFormDisabled, LoanFormData, loanSchema, LoanValidationParams, useForm } from '@/lib/form';
import { LoanAction } from '@/types/loans';
import { useGetAccountPositions } from '@/utils/hooks/api/loans/use-get-account-positions';
-import { useLoanMutation } from '@/utils/hooks/api/loans/use-loan-mutation';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useLoanFormData } from '../../hooks/use-loan-form-data';
import { isLendAsset } from '../../utils/is-loan-asset';
@@ -116,18 +116,45 @@ const LoanForm = ({ asset, variant, position, onChangeLoan }: LoanFormProps): JS
[inputAmount]
);
- const handleSuccess = () => {
- toast.success(`Successful ${content.title.toLowerCase()}`);
- onChangeLoan?.();
- refetch();
- };
+ const transaction = useTransaction({
+ onSuccess: () => {
+ toast.success(`Successful ${content.title.toLowerCase()}`);
+ onChangeLoan?.();
+ refetch();
+ },
+ onError: (error: Error) => {
+ toast.error(error.message);
+ }
+ });
- const handleError = (error: Error) => {
- toast.error(error.message);
+ 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 loanMutation = useLoanMutation({ onSuccess: handleSuccess, onError: handleError });
-
const schemaParams: LoanValidationParams = {
governanceBalance,
transactionFee,
@@ -135,16 +162,6 @@ const LoanForm = ({ asset, variant, position, onChangeLoan }: LoanFormProps): JS
maxAmount: assetAmount.available
};
- const handleSubmit = (data: LoanFormData) => {
- try {
- const submittedAmount = data[variant] || 0;
- const submittedMonetaryAmount = newMonetaryAmount(submittedAmount, asset.currency, true);
- loanMutation.mutate({ amount: submittedMonetaryAmount, loanType: variant, isMaxAmount });
- } catch (err: any) {
- toast.error(err.toString());
- }
- };
-
const form = useForm({
initialValues: { [variant]: '' },
validationSchema: loanSchema(variant, schemaParams),
@@ -199,7 +216,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 41bfd6a148..6ad85f8d82 100644
--- a/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
+++ b/src/pages/Loans/LoansOverview/components/LoansInsights/LoansInsights.tsx
@@ -1,20 +1,16 @@
-import { ISubmittableResult } from '@polkadot/types/types';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
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 { submitExtrinsic } from '@/utils/helpers/extrinsic';
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';
import { StyledDd, StyledDt } from './LoansInsights.style';
-const mutateClaimRewards = () => submitExtrinsic(window.bridge.loans.claimAllSubsidyRewards());
-
type LoansInsightsProps = {
statistics?: AccountLendingStatistics;
};
@@ -23,16 +19,14 @@ const LoansInsights = ({ statistics }: LoansInsightsProps): JSX.Element => {
const { t } = useTranslation();
const { data: subsidyRewards, refetch } = useGetAccountSubsidyRewards();
- const handleSuccess = () => {
- toast.success(t('successfully_claimed_rewards'));
- refetch();
- };
-
- const claimRewardsMutation = useMutation(mutateClaimRewards, {
- onSuccess: handleSuccess
+ const transaction = useTransaction(Transaction.LOANS_CLAIM_REWARDS, {
+ onSuccess: () => {
+ toast.success(t('successfully_claimed_rewards'));
+ refetch();
+ }
});
- const handleClickClaimRewards = () => claimRewardsMutation.mutate();
+ const handleClickClaimRewards = () => transaction.execute();
const { supplyAmountUSD, netAPY } = statistics || {};
@@ -76,18 +70,18 @@ const LoansInsights = ({ statistics }: LoansInsightsProps): JSX.Element => {
{subsidyRewardsAmountLabel}
{hasSubsidyRewards && (
-
+
Claim
)}
- {claimRewardsMutation.isError && (
+ {transaction.isError && (
claimRewardsMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={claimRewardsMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Staking/ClaimRewardsButton/index.tsx b/src/pages/Staking/ClaimRewardsButton/index.tsx
index 2ad34879cd..442da162c0 100644
--- a/src/pages/Staking/ClaimRewardsButton/index.tsx
+++ b/src/pages/Staking/ClaimRewardsButton/index.tsx
@@ -1,6 +1,5 @@
-import { ISubmittableResult } from '@polkadot/types/types';
import clsx from 'clsx';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { GOVERNANCE_TOKEN_SYMBOL } from '@/config/relay-chains';
import InterlayDenimOrKintsugiSupernovaContainedButton, {
@@ -9,7 +8,7 @@ import InterlayDenimOrKintsugiSupernovaContainedButton, {
import ErrorModal from '@/legacy-components/ErrorModal';
import { useSubstrateSecureState } from '@/lib/substrate';
import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
interface CustomProps {
claimableRewardAmount: string;
@@ -24,20 +23,15 @@ const ClaimRewardsButton = ({
const queryClient = useQueryClient();
- const claimRewardsMutation = useMutation(
- () => {
- return submitExtrinsic(window.bridge.escrow.withdrawRewards());
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewardEstimate', selectedAccount?.address]);
- queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewards', selectedAccount?.address]);
- }
+ const transaction = useTransaction(Transaction.ESCROW_WITHDRAW_REWARDS, {
+ onSuccess: () => {
+ queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewardEstimate', selectedAccount?.address]);
+ queryClient.invalidateQueries([GENERIC_FETCHER, 'escrow', 'getRewards', selectedAccount?.address]);
}
- );
+ });
const handleClaimRewards = () => {
- claimRewardsMutation.mutate();
+ transaction.execute();
};
return (
@@ -45,19 +39,19 @@ const ClaimRewardsButton = ({
Claim {claimableRewardAmount} {GOVERNANCE_TOKEN_SYMBOL} Rewards
- {claimRewardsMutation.isError && (
+ {transaction.isError && (
{
- claimRewardsMutation.reset();
+ transaction.reset();
}}
title='Error'
- description={claimRewardsMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Staking/index.tsx b/src/pages/Staking/index.tsx
index e7c8dfd505..043d6b1185 100644
--- a/src/pages/Staking/index.tsx
+++ b/src/pages/Staking/index.tsx
@@ -1,5 +1,4 @@
import { newMonetaryAmount } from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
import Big from 'big.js';
import clsx from 'clsx';
import { add, format } from 'date-fns';
@@ -7,7 +6,7 @@ import * as React from 'react';
import { useErrorHandler, withErrorBoundary } from 'react-error-boundary';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
-import { useMutation, useQuery, useQueryClient } from 'react-query';
+import { useQuery, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { StoreType } from '@/common/types/util.types';
@@ -44,10 +43,10 @@ import {
} from '@/services/fetchers/staking-transaction-fee-reserve-fetcher';
import { ZERO_GOVERNANCE_TOKEN_AMOUNT, ZERO_VOTE_GOVERNANCE_TOKEN_AMOUNT } from '@/utils/constants/currency';
import { YEAR_MONTH_DAY_PATTERN } from '@/utils/constants/date-time';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useSignMessage } from '@/utils/hooks/use-sign-message';
import BalancesUI from './BalancesUI';
@@ -100,11 +99,6 @@ interface StakedAmountAndEndBlock {
endBlock: number;
}
-interface LockingAmountAndTime {
- amount: GovernanceTokenMonetaryAmount;
- time: number; // Weeks
-}
-
const Staking = (): JSX.Element => {
const [blockLockTimeExtension, setBlockLockTimeExtension] = React.useState(0);
@@ -248,63 +242,25 @@ const Staking = (): JSX.Element => {
);
useErrorHandler(transactionFeeReserveError);
- const initialStakeMutation = useMutation(
- (variables: LockingAmountAndTime) => {
- if (currentBlockNumber === undefined) {
- throw new Error('Something went wrong!');
- }
- const unlockHeight = currentBlockNumber + convertWeeksToBlockNumbers(variables.time);
-
- return submitExtrinsic(window.bridge.escrow.createLock(variables.amount, unlockHeight));
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
- reset({
- [LOCKING_AMOUNT]: '0.0',
- [LOCK_TIME]: '0'
- });
- }
+ const initialStakeTransaction = useTransaction(Transaction.ESCROW_CREATE_LOCK, {
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
+ reset({
+ [LOCKING_AMOUNT]: '0.0',
+ [LOCK_TIME]: '0'
+ });
}
- );
-
- const moreStakeMutation = useMutation(
- (variables: LockingAmountAndTime) => {
- return (async () => {
- if (stakedAmountAndEndBlock === undefined) {
- throw new Error('Something went wrong!');
- }
+ });
- if (checkIncreaseLockAmountAndExtendLockTime(variables.time, variables.amount)) {
- const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(variables.time);
-
- const txs = [
- window.bridge.api.tx.escrow.increaseAmount(variables.amount.toString(true)),
- window.bridge.api.tx.escrow.increaseUnlockHeight(unlockHeight)
- ];
- const batch = window.bridge.api.tx.utility.batchAll(txs);
- await submitExtrinsic({ extrinsic: batch });
- } else if (checkOnlyIncreaseLockAmount(variables.time, variables.amount)) {
- await submitExtrinsic(window.bridge.escrow.increaseAmount(variables.amount));
- } else if (checkOnlyExtendLockTime(variables.time, variables.amount)) {
- const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(variables.time);
-
- await submitExtrinsic(window.bridge.escrow.increaseUnlockHeight(unlockHeight));
- } else {
- throw new Error('Something went wrong!');
- }
- })();
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
- reset({
- [LOCKING_AMOUNT]: '0.0',
- [LOCK_TIME]: '0'
- });
- }
+ const existingStakeTransaction = useTransaction({
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [GENERIC_FETCHER, 'escrow'] });
+ reset({
+ [LOCKING_AMOUNT]: '0.0',
+ [LOCK_TIME]: '0'
+ });
}
- );
+ });
React.useEffect(() => {
if (isValidating || !isValid || !estimatedRewardAmountAndAPYRefetch) return;
@@ -409,15 +365,30 @@ const Staking = (): JSX.Element => {
const numberTime = parseInt(lockTimeWithFallback);
if (votingBalanceGreaterThanZero) {
- moreStakeMutation.mutate({
- amount: monetaryAmount,
- time: numberTime
- });
+ if (stakedAmountAndEndBlock === undefined) {
+ throw new Error('Something went wrong!');
+ }
+
+ if (checkIncreaseLockAmountAndExtendLockTime(numberTime, monetaryAmount)) {
+ const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(numberTime);
+
+ existingStakeTransaction.execute(
+ Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT,
+ monetaryAmount.toString(true),
+ unlockHeight
+ );
+ } else if (checkOnlyIncreaseLockAmount(numberTime, monetaryAmount)) {
+ existingStakeTransaction.execute(Transaction.ESCROW_INCREASE_LOCKED_AMOUNT, monetaryAmount);
+ } else if (checkOnlyExtendLockTime(numberTime, monetaryAmount)) {
+ const unlockHeight = stakedAmountAndEndBlock.endBlock + convertWeeksToBlockNumbers(numberTime);
+
+ existingStakeTransaction.execute(Transaction.ESCROW_INCREASE_LOCKED_TIME, unlockHeight);
+ } else {
+ throw new Error('Something went wrong!');
+ }
} else {
- initialStakeMutation.mutate({
- amount: monetaryAmount,
- time: numberTime
- });
+ const unlockHeight = currentBlockNumber + convertWeeksToBlockNumbers(numberTime);
+ initialStakeTransaction.execute(monetaryAmount, unlockHeight);
}
};
@@ -856,7 +827,7 @@ const Staking = (): JSX.Element => {
size='large'
type='submit'
disabled={initializing || unlockFirst || !isValid}
- loading={initialStakeMutation.isLoading || moreStakeMutation.isLoading}
+ loading={initialStakeTransaction.isLoading || existingStakeTransaction.isLoading}
>
{submitButtonLabel}{' '}
{unlockFirst ? (
@@ -866,15 +837,15 @@ const Staking = (): JSX.Element => {
- {(initialStakeMutation.isError || moreStakeMutation.isError) && (
+ {(initialStakeTransaction.isError || existingStakeTransaction.isError) && (
{
- initialStakeMutation.reset();
- moreStakeMutation.reset();
+ initialStakeTransaction.reset();
+ existingStakeTransaction.reset();
}}
title='Error'
- description={initialStakeMutation.error?.message || moreStakeMutation.error?.message || ''}
+ description={initialStakeTransaction.error?.message || existingStakeTransaction.error?.message || ''}
/>
)}
>
diff --git a/src/pages/Transfer/TransferForm/index.tsx b/src/pages/Transfer/TransferForm/index.tsx
index e588456dc1..a488cd288f 100644
--- a/src/pages/Transfer/TransferForm/index.tsx
+++ b/src/pages/Transfer/TransferForm/index.tsx
@@ -18,8 +18,8 @@ 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 { submitExtrinsic } from '@/utils/helpers/extrinsic';
import isValidPolkadotAddress from '@/utils/helpers/is-valid-polkadot-address';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import TokenAmountField from '../TokenAmountField';
@@ -50,6 +50,8 @@ const TransferForm = (): JSX.Element => {
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
const [submitError, setSubmitError] = React.useState(null);
+ const transaction = useTransaction(Transaction.TOKENS_TRANSFER);
+
const onSubmit = async (data: TransferFormData) => {
if (!activeToken) return;
if (data[TRANSFER_AMOUNT] === undefined) return;
@@ -57,11 +59,9 @@ const TransferForm = (): JSX.Element => {
try {
setSubmitStatus(STATUSES.PENDING);
- await submitExtrinsic(
- window.bridge.tokens.transfer(
- data[RECIPIENT_ADDRESS],
- newMonetaryAmount(data[TRANSFER_AMOUNT], activeToken.token, true)
- )
+ await transaction.executeAsync(
+ data[RECIPIENT_ADDRESS],
+ newMonetaryAmount(data[TRANSFER_AMOUNT], activeToken.token, true)
);
setSubmitStatus(STATUSES.RESOLVED);
diff --git a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
index 91c08d48cb..4e9215ce82 100644
--- a/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestIssueModal/index.tsx
@@ -44,11 +44,11 @@ import SubmittedIssueRequestModal from '@/pages/Bridge/IssueForm/SubmittedIssueR
import { ForeignAssetIdLiteral } from '@/types/currency';
import { KUSAMA, POLKADOT } from '@/utils/constants/relay-chain-names';
import STATUSES from '@/utils/constants/statuses';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
const WRAPPED_TOKEN_AMOUNT = 'amount';
@@ -108,6 +108,8 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
const vaultAccountId = useAccountId(vaultAddress);
+ const transaction = useTransaction(Transaction.ISSUE_REQUEST);
+
React.useEffect(() => {
if (!bridgeLoaded) return;
if (!handleError) return;
@@ -180,17 +182,14 @@ const RequestIssueModal = ({ onClose, open, collateralToken, vaultAddress }: Pro
const vaults = await window.bridge.vaults.getVaultsWithIssuableTokens();
- const extrinsicData = await window.bridge.issue.request(
+ const extrinsicResult = await transaction.executeAsync(
wrappedTokenAmount,
vaultAccountId,
collateralToken,
false, // default
vaults
);
- // When requesting an issue, 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
- const finalizedStatus = getExtrinsicStatus('Finalized');
- const extrinsicResult = await submitExtrinsic(extrinsicData, finalizedStatus);
+
const issueRequests = await getIssueRequestsFromExtrinsicResult(window.bridge, extrinsicResult);
// TODO: handle issue aggregation
diff --git a/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx b/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx
index 300211d7ec..dde21974e5 100644
--- a/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestRedeemModal/index.tsx
@@ -17,7 +17,7 @@ import ErrorMessage from '@/legacy-components/ErrorMessage';
import NumberInput from '@/legacy-components/NumberInput';
import TextField from '@/legacy-components/TextField';
import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const WRAPPED_TOKEN_AMOUNT = 'amount';
const BTC_ADDRESS = 'btc-address';
@@ -47,6 +47,8 @@ const RequestRedeemModal = ({ onClose, open, collateralToken, vaultAddress, lock
const { t } = useTranslation();
const focusRef = React.useRef(null);
+ const transaction = useTransaction(Transaction.REDEEM_REQUEST);
+
const onSubmit = handleSubmit(async (data) => {
setRequestPending(true);
try {
@@ -61,11 +63,7 @@ const RequestRedeemModal = ({ onClose, open, collateralToken, vaultAddress, lock
}
const vaultId = newVaultId(window.bridge.api, vaultAddress, collateralToken, WRAPPED_TOKEN);
- const extrinsicData = await window.bridge.redeem.request(amountPolkaBtc, data[BTC_ADDRESS], vaultId);
- // When requesting a redeem, 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
- const finalizedStatus = getExtrinsicStatus('Finalized');
- await submitExtrinsic(extrinsicData, finalizedStatus);
+ await transaction.executeAsync(amountPolkaBtc, data[BTC_ADDRESS], vaultId);
queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
diff --git a/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx b/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
index b296f04bf0..a92acc73b2 100644
--- a/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
+++ b/src/pages/Vaults/Vault/RequestReplacementModal/index.tsx
@@ -25,9 +25,9 @@ import PrimaryColorEllipsisLoader from '@/legacy-components/PrimaryColorEllipsis
import InterlayModal, { InterlayModalInnerWrapper, InterlayModalTitle } from '@/legacy-components/UI/InterlayModal';
import { GENERIC_FETCHER } from '@/services/fetchers/generic-fetcher';
import STATUSES from '@/utils/constants/statuses';
-import { getExtrinsicStatus, submitExtrinsic } from '@/utils/helpers/extrinsic';
import { getExchangeRate } from '@/utils/helpers/oracle';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
const AMOUNT = 'amount';
@@ -78,6 +78,8 @@ const RequestReplacementModal = ({
);
const [submitStatus, setSubmitStatus] = React.useState(STATUSES.IDLE);
+ const transaction = useTransaction(Transaction.REPLACE_REQUEST);
+
useEffect(() => {
if (!bridgeLoaded) return;
if (!handleError) return;
@@ -105,10 +107,8 @@ const RequestReplacementModal = ({
try {
setSubmitStatus(STATUSES.PENDING);
const amountPolkaBtc = new BitcoinAmount(data[AMOUNT]);
- // 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
- const finalizedStatus = getExtrinsicStatus('Finalized');
- submitExtrinsic(window.bridge.replace.request(amountPolkaBtc, collateralToken), finalizedStatus);
+
+ await transaction.executeAsync(amountPolkaBtc, collateralToken);
const vaultId = window.bridge.api.createType(ACCOUNT_ID_TYPE_NAME, vaultAddress);
queryClient.invalidateQueries([GENERIC_FETCHER, 'mapReplaceRequests', vaultId]);
diff --git a/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx b/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
index 772fa93e3d..dad669da97 100644
--- a/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
+++ b/src/pages/Vaults/Vault/UpdateCollateralModal/index.tsx
@@ -21,10 +21,10 @@ 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 { submitExtrinsic, submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
enum CollateralUpdateStatus {
Close,
@@ -129,6 +129,8 @@ const UpdateCollateralModal = ({
);
useErrorHandler(vaultCollateralizationError);
+ const transaction = useTransaction();
+
const handleClose = chain(() => resetField(COLLATERAL_TOKEN_AMOUNT), onClose);
const onSubmit = async (data: UpdateCollateralFormData) => {
@@ -142,9 +144,9 @@ const UpdateCollateralModal = ({
true
) as MonetaryAmount;
if (collateralUpdateStatus === CollateralUpdateStatus.Deposit) {
- await submitExtrinsic(window.bridge.vaults.depositCollateral(collateralTokenAmount));
+ await transaction.executeAsync(Transaction.VAULTS_DEPOSIT_COLLATERAL, collateralTokenAmount);
} else if (collateralUpdateStatus === CollateralUpdateStatus.Withdraw) {
- await submitExtrinsicPromise(window.bridge.vaults.withdrawCollateral(collateralTokenAmount));
+ await transaction.executeAsync(Transaction.VAULTS_WITHDRAW_COLLATERAL, collateralTokenAmount);
} else {
throw new Error('Something went wrong!');
}
diff --git a/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx b/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
index e2bc840f77..2b8cedbe6b 100644
--- a/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
+++ b/src/pages/Vaults/Vault/components/Rewards/Rewards.tsx
@@ -1,7 +1,6 @@
import { CollateralCurrencyExt, newVaultId, WrappedCurrency, WrappedIdLiteral } from '@interlay/interbtc-api';
-import { ISubmittableResult } from '@polkadot/types/types';
import Big from 'big.js';
-import { useMutation, useQueryClient } from 'react-query';
+import { useQueryClient } from 'react-query';
import { toast } from 'react-toastify';
import { formatNumber, formatUSD } from '@/common/utils/utils';
@@ -10,8 +9,8 @@ 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 { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
import { VaultData } from '@/utils/hooks/api/vaults/get-vault-data';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { InsightListItem, InsightsList } from '../InsightsList';
@@ -49,31 +48,24 @@ const Rewards = ({
const queryClient = useQueryClient();
const vaultAccountId = useAccountId(vaultAddress);
- const claimRewardsMutation = useMutation(
- () => {
- if (vaultAccountId === undefined) {
- throw new Error('Something went wrong!');
- }
-
- const vaultId = newVaultId(
- window.bridge.api,
- vaultAccountId.toString(),
- collateralToken,
- WRAPPED_TOKEN as WrappedCurrency
- );
-
- return submitExtrinsicPromise(window.bridge.rewards.withdrawRewards(vaultId));
- },
- {
- onSuccess: () => {
- queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
- toast.success('Your rewards were successfully withdrawn.');
- }
+ const transaction = useTransaction(Transaction.REWARDS_WITHDRAW, {
+ onSuccess: () => {
+ queryClient.invalidateQueries(['vaultsOverview', vaultAddress, collateralToken.ticker]);
+ toast.success('Your rewards were successfully withdrawn.');
}
- );
+ });
const handleClickWithdrawRewards = () => {
- claimRewardsMutation.mutate();
+ if (vaultAccountId === undefined) return;
+
+ const vaultId = newVaultId(
+ window.bridge.api,
+ vaultAccountId.toString(),
+ collateralToken,
+ WRAPPED_TOKEN as WrappedCurrency
+ );
+
+ transaction.execute(vaultId);
};
const hasWithdrawableRewards =
@@ -87,11 +79,11 @@ const Rewards = ({
size='small'
variant='outlined'
onClick={handleClickWithdrawRewards}
- disabled={!hasWithdrawableRewards || claimRewardsMutation.isLoading}
- $loading={claimRewardsMutation.isLoading}
+ disabled={!hasWithdrawableRewards || transaction.isLoading}
+ $loading={transaction.isLoading}
>
{/* TODO: temporary approach. Loading spinner should be added to the CTA itself */}
- {claimRewardsMutation.isLoading && (
+ {transaction.isLoading && (
@@ -99,12 +91,12 @@ const Rewards = ({
Withdraw all rewards
)}
- {claimRewardsMutation.isError && (
+ {transaction.isError && (
claimRewardsMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={claimRewardsMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
diff --git a/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx b/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
index 32f28166d1..4fbe4efd89 100644
--- a/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
+++ b/src/pages/Vaults/VaultsOverview/components/CreateVaultWizard/DespositCollateralStep.tsx
@@ -1,9 +1,7 @@
import { CollateralCurrencyExt, newMonetaryAmount } from '@interlay/interbtc-api';
import { MonetaryAmount } from '@interlay/monetary-js';
-import { ISubmittableResult } from '@polkadot/types/types';
import { useId } from '@react-aria/utils';
import { useTranslation } from 'react-i18next';
-import { useMutation } from 'react-query';
import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
import { CTA, ModalBody, ModalDivider, ModalFooter, ModalHeader, Span, Stack, TokenInput } from '@/component-library';
@@ -16,8 +14,8 @@ import {
isFormDisabled,
useForm
} from '@/lib/form';
-import { submitExtrinsic } from '@/utils/helpers/extrinsic';
import { StepComponentProps, withStep } from '@/utils/hocs/step';
+import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import { useDepositCollateral } from '../../utils/use-deposit-collateral';
import { StyledDd, StyledDItem, StyledDl, StyledDt, StyledHr } from './CreateVaultWizard.styles';
@@ -39,6 +37,10 @@ const DepositCollateralStep = ({
const { t } = useTranslation();
const { collateral, fee, governance } = useDepositCollateral(collateralCurrency, minCollateralAmount);
+ const transaction = useTransaction(Transaction.VAULTS_REGISTER_NEW_COLLATERAL, {
+ onSuccess: onSuccessfulDeposit
+ });
+
const validationParams = {
minAmount: collateral.min.raw,
maxAmount: collateral.balance.raw,
@@ -50,7 +52,7 @@ const DepositCollateralStep = ({
if (!data.deposit) return;
const amount = newMonetaryAmount(data.deposit || 0, collateral.currency, true);
- registerNewVaultMutation.mutate(amount);
+ transaction.execute(amount);
};
const form = useForm({
@@ -59,13 +61,6 @@ const DepositCollateralStep = ({
onSubmit: handleSubmit
});
- const registerNewVaultMutation = useMutation>(
- (collateralAmount) => submitExtrinsic(window.bridge.vaults.registerNewCollateralVault(collateralAmount)),
- {
- onSuccess: onSuccessfulDeposit
- }
- );
-
const inputCollateralAmount = newSafeMonetaryAmount(form.values.deposit || 0, collateral.currency, true);
const isBtnDisabled = isFormDisabled(form);
@@ -108,17 +103,17 @@ const DepositCollateralStep = ({
-
+
{t('vault.deposit_collateral')}
- {registerNewVaultMutation.isError && (
+ {transaction.isError && (
registerNewVaultMutation.reset()}
+ open={transaction.isError}
+ onClose={() => transaction.reset()}
title='Error'
- description={registerNewVaultMutation.error?.message || ''}
+ description={transaction.error?.message || ''}
/>
)}
>
diff --git a/src/utils/hooks/api/loans/use-loan-mutation.tsx b/src/utils/hooks/api/loans/use-loan-mutation.tsx
deleted file mode 100644
index 0057369b36..0000000000
--- a/src/utils/hooks/api/loans/use-loan-mutation.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { CurrencyExt } from '@interlay/interbtc-api';
-import { MonetaryAmount } from '@interlay/monetary-js';
-import { ISubmittableResult } from '@polkadot/types/types';
-import { useMutation, UseMutationResult } from 'react-query';
-
-import { LoanAction } from '@/types/loans';
-import { submitExtrinsicPromise } from '@/utils/helpers/extrinsic';
-
-type CreateLoanVariables = { loanType: LoanAction; amount: MonetaryAmount; isMaxAmount: boolean };
-
-const mutateLoan = ({ loanType, amount, isMaxAmount }: CreateLoanVariables) => {
- const extrinsicData = (() => {
- switch (loanType) {
- case 'lend':
- return window.bridge.loans.lend(amount.currency, amount);
- case 'withdraw':
- if (isMaxAmount) {
- return window.bridge.loans.withdrawAll(amount.currency);
- } else {
- return window.bridge.loans.withdraw(amount.currency, amount);
- }
- case 'borrow':
- return window.bridge.loans.borrow(amount.currency, amount);
- case 'repay':
- if (isMaxAmount) {
- return window.bridge.loans.repayAll(amount.currency);
- } else {
- return window.bridge.loans.repay(amount.currency, amount);
- }
- }
- })();
-
- return submitExtrinsicPromise(extrinsicData);
-};
-
-type UseLoanMutation = { onSuccess: () => void; onError: (error: Error) => void };
-
-const useLoanMutation = ({
- onSuccess,
- onError
-}: UseLoanMutation): UseMutationResult => {
- return useMutation(mutateLoan, {
- onSuccess,
- onError
- });
-};
-
-export { useLoanMutation };
-export type { UseLoanMutation };
diff --git a/src/utils/hooks/transaction/index.ts b/src/utils/hooks/transaction/index.ts
new file mode 100644
index 0000000000..3a845f06ae
--- /dev/null
+++ b/src/utils/hooks/transaction/index.ts
@@ -0,0 +1,2 @@
+export { Transaction } from './types';
+export { useTransaction } from './use-transaction';
diff --git a/src/utils/hooks/transaction/types/amm.ts b/src/utils/hooks/transaction/types/amm.ts
new file mode 100644
index 0000000000..7b6cc56af9
--- /dev/null
+++ b/src/utils/hooks/transaction/types/amm.ts
@@ -0,0 +1,28 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface SwapAction extends TransactionAction {
+ type: Transaction.AMM_SWAP;
+ args: Parameters;
+}
+
+interface PoolAddLiquidityAction extends TransactionAction {
+ type: Transaction.AMM_ADD_LIQUIDITY;
+ args: Parameters;
+}
+
+interface PoolRemoveLiquidityAction extends TransactionAction {
+ type: Transaction.AMM_REMOVE_LIQUIDITY;
+ args: Parameters;
+}
+
+interface PoolClaimRewardsAction extends TransactionAction {
+ type: Transaction.AMM_CLAIM_REWARDS;
+ args: Parameters;
+}
+
+type AMMActions = SwapAction | PoolAddLiquidityAction | PoolRemoveLiquidityAction | PoolClaimRewardsAction;
+
+export type { AMMActions };
diff --git a/src/utils/hooks/transaction/types/escrow.ts b/src/utils/hooks/transaction/types/escrow.ts
new file mode 100644
index 0000000000..7003d1f796
--- /dev/null
+++ b/src/utils/hooks/transaction/types/escrow.ts
@@ -0,0 +1,46 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface EscrowCreateLockAction extends TransactionAction {
+ type: Transaction.ESCROW_CREATE_LOCK;
+ args: Parameters;
+}
+
+interface EscrowInscreaseLookedTimeAndAmountAction extends TransactionAction {
+ type: Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT;
+ args: [
+ ...Parameters,
+ ...Parameters
+ ];
+}
+interface EscrowIncreaseLockAmountAction extends TransactionAction {
+ type: Transaction.ESCROW_INCREASE_LOCKED_AMOUNT;
+ args: Parameters;
+}
+
+interface EscrowIncreaseLockTimeAction extends TransactionAction {
+ type: Transaction.ESCROW_INCREASE_LOCKED_TIME;
+ args: Parameters;
+}
+
+interface EscrowWithdrawRewardsAction extends TransactionAction {
+ type: Transaction.ESCROW_WITHDRAW_REWARDS;
+ args: Parameters;
+}
+
+interface EscrowWithdrawAction extends TransactionAction {
+ type: Transaction.ESCROW_WITHDRAW;
+ args: Parameters;
+}
+
+type EscrowActions =
+ | EscrowCreateLockAction
+ | EscrowInscreaseLookedTimeAndAmountAction
+ | EscrowIncreaseLockAmountAction
+ | EscrowIncreaseLockTimeAction
+ | EscrowWithdrawRewardsAction
+ | EscrowWithdrawAction;
+
+export type { EscrowActions };
diff --git a/src/utils/hooks/transaction/types/index.ts b/src/utils/hooks/transaction/types/index.ts
new file mode 100644
index 0000000000..538f820678
--- /dev/null
+++ b/src/utils/hooks/transaction/types/index.ts
@@ -0,0 +1,79 @@
+import { ExtrinsicStatus } from '@polkadot/types/interfaces';
+
+import { AMMActions } from './amm';
+import { EscrowActions } from './escrow';
+import { IssueActions } from './issue';
+import { LoansActions } from './loans';
+import { RedeemActions } from './redeem';
+import { ReplaceActions } from './replace';
+import { RewardsActions } from './rewards';
+import { TokensActions } from './tokens';
+import { VaultsActions } from './vaults';
+
+enum Transaction {
+ // Issue
+ ISSUE_REQUEST = 'ISSUE_REQUEST',
+ ISSUE_EXECUTE = 'ISSUE_EXECUTE',
+ // Redeem
+ REDEEM_REQUEST = 'REDEEM_REQUEST',
+ REDEEM_CANCEL = 'REDEEM_CANCEL',
+ REDEEM_BURN = 'REDEEM_BURN',
+ // Replace
+ REPLACE_REQUEST = 'REPLACE_REQUEST',
+ // Escrow
+ ESCROW_CREATE_LOCK = 'ESCROW_CREATE_LOCK',
+ ESCROW_INCREASE_LOCKED_TIME = 'ESCROW_INCREASE_LOCKED_TIME',
+ ESCROW_INCREASE_LOCKED_AMOUNT = 'ESCROW_INCREASE_LOCKED_AMOUNT',
+ ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT = 'ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT',
+ ESCROW_WITHDRAW_REWARDS = 'ESCROW_WITHDRAW_REWARDS',
+ ESCROW_WITHDRAW = 'ESCROW_WITHDRAW',
+ // Tokens
+ TOKENS_TRANSFER = 'TOKENS_TRANSFER',
+ // Vaults
+ VAULTS_DEPOSIT_COLLATERAL = 'VAULTS_DEPOSIT_COLLATERAL',
+ VAULTS_WITHDRAW_COLLATERAL = 'VAULTS_WITHDRAW_COLLATERAL',
+ VAULTS_REGISTER_NEW_COLLATERAL = 'VAULTS_REGISTER_NEW_COLLATERAL',
+ // Rewards
+ REWARDS_WITHDRAW = 'REWARDS_WITHDRAW',
+ // Loans
+ LOANS_CLAIM_REWARDS = 'LOANS_CLAIM_REWARDS',
+ LOANS_ENABLE_COLLATERAL = 'LOANS_ENABLE_COLLATERAL',
+ LOANS_DISABLE_COLLATERAL = 'LOANS_DISABLE_COLLATERAL',
+ LOANS_LEND = 'LOANS_LEND',
+ LOANS_WITHDRAW = 'LOANS_WITHDRAW',
+ LOANS_WITHDRAW_ALL = 'LOANS_WITHDRAW_ALL',
+ LOANS_BORROW = 'LOANS_BORROW',
+ LOANS_REPAY = 'LOANS_REPAY',
+ LOANS_REPAY_ALL = 'LOANS_REPAY_ALL',
+ // AMM
+ AMM_SWAP = 'AMM_SWAP',
+ AMM_ADD_LIQUIDITY = 'AMM_ADD_LIQUIDITY',
+ AMM_REMOVE_LIQUIDITY = 'AMM_REMOVE_LIQUIDITY',
+ AMM_CLAIM_REWARDS = 'AMM_CLAIM_REWARDS'
+}
+
+type TransactionEvents = {
+ onReady?: () => void;
+};
+
+interface TransactionAction {
+ accountAddress: string;
+ events: TransactionEvents;
+ customStatus?: ExtrinsicStatus['type'];
+}
+
+type TransactionActions =
+ | EscrowActions
+ | IssueActions
+ | RedeemActions
+ | ReplaceActions
+ | TokensActions
+ | LoansActions
+ | AMMActions
+ | VaultsActions
+ | RewardsActions;
+
+type TransactionArgs = Extract['args'];
+
+export { Transaction };
+export type { TransactionAction, TransactionActions, TransactionArgs, TransactionEvents };
diff --git a/src/utils/hooks/transaction/types/issue.ts b/src/utils/hooks/transaction/types/issue.ts
new file mode 100644
index 0000000000..dfa3b9d5a3
--- /dev/null
+++ b/src/utils/hooks/transaction/types/issue.ts
@@ -0,0 +1,18 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface IssueRequestAction extends TransactionAction {
+ type: Transaction.ISSUE_REQUEST;
+ args: Parameters;
+}
+
+interface IssueExecuteAction extends TransactionAction {
+ type: Transaction.ISSUE_EXECUTE;
+ args: Parameters;
+}
+
+type IssueActions = IssueRequestAction | IssueExecuteAction;
+
+export type { IssueActions };
diff --git a/src/utils/hooks/transaction/types/loans.ts b/src/utils/hooks/transaction/types/loans.ts
new file mode 100644
index 0000000000..27797c68d9
--- /dev/null
+++ b/src/utils/hooks/transaction/types/loans.ts
@@ -0,0 +1,62 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface LoansClaimRewardsAction extends TransactionAction {
+ type: Transaction.LOANS_CLAIM_REWARDS;
+ args: Parameters;
+}
+
+interface LoansEnabledCollateralAction extends TransactionAction {
+ type: Transaction.LOANS_ENABLE_COLLATERAL;
+ args: Parameters;
+}
+
+interface LoansDisabledCollateralAction extends TransactionAction {
+ type: Transaction.LOANS_DISABLE_COLLATERAL;
+ args: Parameters;
+}
+
+interface LoansLendAction extends TransactionAction {
+ type: Transaction.LOANS_LEND;
+ args: Parameters;
+}
+
+interface LoansWithdrawAction extends TransactionAction {
+ type: Transaction.LOANS_WITHDRAW;
+ args: Parameters;
+}
+
+interface LoansWithdrawAllAction extends TransactionAction {
+ type: Transaction.LOANS_WITHDRAW_ALL;
+ args: Parameters;
+}
+
+interface LoansBorrowAction extends TransactionAction {
+ type: Transaction.LOANS_BORROW;
+ args: Parameters;
+}
+
+interface LoansRepayAction extends TransactionAction {
+ type: Transaction.LOANS_REPAY;
+ args: Parameters;
+}
+
+interface LoansRepayAllAction extends TransactionAction {
+ type: Transaction.LOANS_REPAY_ALL;
+ args: Parameters;
+}
+
+type LoansActions =
+ | LoansClaimRewardsAction
+ | LoansEnabledCollateralAction
+ | LoansDisabledCollateralAction
+ | LoansLendAction
+ | LoansWithdrawAction
+ | LoansWithdrawAllAction
+ | LoansBorrowAction
+ | LoansRepayAction
+ | LoansRepayAllAction;
+
+export type { LoansActions };
diff --git a/src/utils/hooks/transaction/types/redeem.ts b/src/utils/hooks/transaction/types/redeem.ts
new file mode 100644
index 0000000000..1282278693
--- /dev/null
+++ b/src/utils/hooks/transaction/types/redeem.ts
@@ -0,0 +1,23 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface RedeemCancelAction extends TransactionAction {
+ type: Transaction.REDEEM_CANCEL;
+ args: Parameters;
+}
+
+interface RedeemBurnAction extends TransactionAction {
+ type: Transaction.REDEEM_BURN;
+ args: Parameters;
+}
+
+interface RedeemRequestAction extends TransactionAction {
+ type: Transaction.REDEEM_REQUEST;
+ args: Parameters;
+}
+
+type RedeemActions = RedeemRequestAction | RedeemCancelAction | RedeemBurnAction;
+
+export type { RedeemActions };
diff --git a/src/utils/hooks/transaction/types/replace.ts b/src/utils/hooks/transaction/types/replace.ts
new file mode 100644
index 0000000000..4fab08e0e7
--- /dev/null
+++ b/src/utils/hooks/transaction/types/replace.ts
@@ -0,0 +1,13 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface ReplaceRequestAction extends TransactionAction {
+ type: Transaction.REPLACE_REQUEST;
+ args: Parameters;
+}
+
+type ReplaceActions = ReplaceRequestAction;
+
+export type { ReplaceActions };
diff --git a/src/utils/hooks/transaction/types/rewards.ts b/src/utils/hooks/transaction/types/rewards.ts
new file mode 100644
index 0000000000..f77f61f7c4
--- /dev/null
+++ b/src/utils/hooks/transaction/types/rewards.ts
@@ -0,0 +1,13 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '.';
+import { TransactionAction } from '.';
+
+interface RewardsWithdrawAction extends TransactionAction {
+ type: Transaction.REWARDS_WITHDRAW;
+ args: Parameters;
+}
+
+type RewardsActions = RewardsWithdrawAction;
+
+export type { RewardsActions };
diff --git a/src/utils/hooks/transaction/types/tokens.ts b/src/utils/hooks/transaction/types/tokens.ts
new file mode 100644
index 0000000000..a1c1e0da64
--- /dev/null
+++ b/src/utils/hooks/transaction/types/tokens.ts
@@ -0,0 +1,13 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface TokensTransferAction extends TransactionAction {
+ type: Transaction.TOKENS_TRANSFER;
+ args: Parameters;
+}
+
+type TokensActions = TokensTransferAction;
+
+export type { TokensActions };
diff --git a/src/utils/hooks/transaction/types/vaults.ts b/src/utils/hooks/transaction/types/vaults.ts
new file mode 100644
index 0000000000..1c4040fd17
--- /dev/null
+++ b/src/utils/hooks/transaction/types/vaults.ts
@@ -0,0 +1,23 @@
+import { InterBtcApi } from '@interlay/interbtc-api';
+
+import { Transaction } from '../types';
+import { TransactionAction } from '.';
+
+interface VaultsDepositCollateralAction extends TransactionAction {
+ type: Transaction.VAULTS_DEPOSIT_COLLATERAL;
+ args: Parameters;
+}
+
+interface VaultsWithdrawCollateralAction extends TransactionAction {
+ type: Transaction.VAULTS_WITHDRAW_COLLATERAL;
+ args: Parameters;
+}
+
+interface VaultsRegisterNewCollateralAction extends TransactionAction {
+ type: Transaction.VAULTS_REGISTER_NEW_COLLATERAL;
+ args: Parameters;
+}
+
+type VaultsActions = VaultsDepositCollateralAction | VaultsWithdrawCollateralAction | VaultsRegisterNewCollateralAction;
+
+export type { VaultsActions };
diff --git a/src/utils/hooks/transaction/use-transaction.ts b/src/utils/hooks/transaction/use-transaction.ts
new file mode 100644
index 0000000000..d18291f94c
--- /dev/null
+++ b/src/utils/hooks/transaction/use-transaction.ts
@@ -0,0 +1,122 @@
+import { ExtrinsicStatus } from '@polkadot/types/interfaces';
+import { ISubmittableResult } from '@polkadot/types/types';
+import { useCallback } from 'react';
+import { MutationFunction, useMutation, UseMutationOptions, UseMutationResult } from 'react-query';
+
+import { useSubstrate } from '@/lib/substrate';
+
+import { Transaction, TransactionActions, TransactionArgs } from './types';
+import { getExtrinsic, getStatus } from './utils/extrinsic';
+import { submitTransaction } from './utils/submit';
+
+type UseTransactionOptions = Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> & {
+ customStatus?: ExtrinsicStatus['type'];
+};
+
+// 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;
+};
+
+// TODO: add feeEstimate and feeEstimateAsync
+type ExecuteTypeArgs = {
+ execute(type: D, ...args: TransactionArgs): void;
+ executeAsync(type: D, ...args: TransactionArgs): Promise;
+};
+
+type InheritAttrs = Omit<
+ UseMutationResult,
+ 'mutate' | 'mutateAsync'
+>;
+
+type UseTransactionResult = InheritAttrs & (ExecuteArgs | ExecuteTypeArgs);
+
+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);
+};
+
+// The three declared functions are use to infer types on diferent implementations
+// TODO: missing xcm transaction
+function useTransaction(
+ type: T,
+ options?: UseTransactionOptions
+): Exclude, ExecuteTypeArgs>;
+function useTransaction(
+ options?: UseTransactionOptions
+): Exclude, ExecuteArgs>;
+function useTransaction(
+ typeOrOptions?: T | UseTransactionOptions,
+ options?: UseTransactionOptions
+): UseTransactionResult {
+ const { state } = useSubstrate();
+
+ const hasOnlyOptions = typeof typeOrOptions !== 'string';
+
+ const { mutate, mutateAsync, ...transactionMutation } = useMutation(
+ mutateTransaction,
+ (hasOnlyOptions ? typeOrOptions : options) as UseTransactionOptions
+ );
+
+ // Handles params for both type of implementations
+ const getParams = useCallback(
+ (args: Parameters['execute']>) => {
+ let params = {};
+
+ // Assign correct params for when transaction type is declared on hook params
+ if (typeof typeOrOptions === 'string') {
+ params = { type: typeOrOptions, args };
+ } else {
+ // Assign correct params for when transaction type is declared on execution level
+ const [type, ...restArgs] = args;
+ params = { type, args: restArgs };
+ }
+
+ // Execution should only ran when authenticated
+ const accountAddress = state.selectedAccount?.address;
+
+ // TODO: add event `onReady`
+ return {
+ ...params,
+ accountAddress,
+ customStatus: options?.customStatus
+ } as TransactionActions;
+ },
+ [options?.customStatus, state.selectedAccount?.address, typeOrOptions]
+ );
+
+ const handleExecute = useCallback(
+ (...args: Parameters['execute']>) => {
+ const params = getParams(args);
+
+ return mutate(params);
+ },
+ [getParams, mutate]
+ );
+
+ const handleExecuteAsync = useCallback(
+ (...args: Parameters['executeAsync']>) => {
+ const params = getParams(args);
+
+ return mutateAsync(params);
+ },
+ [getParams, mutateAsync]
+ );
+
+ return {
+ ...transactionMutation,
+ execute: handleExecute,
+ executeAsync: handleExecuteAsync
+ };
+}
+
+export { useTransaction };
+export type { UseTransactionResult };
diff --git a/src/utils/hooks/transaction/utils/extrinsic.ts b/src/utils/hooks/transaction/utils/extrinsic.ts
new file mode 100644
index 0000000000..23346819db
--- /dev/null
+++ b/src/utils/hooks/transaction/utils/extrinsic.ts
@@ -0,0 +1,133 @@
+import { ExtrinsicData } from '@interlay/interbtc-api';
+import { ExtrinsicStatus } from '@polkadot/types/interfaces';
+
+import { Transaction, TransactionActions } 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 => {
+ switch (params.type) {
+ /* START - AMM */
+ case Transaction.AMM_SWAP:
+ return window.bridge.amm.swap(...params.args);
+ case Transaction.AMM_ADD_LIQUIDITY:
+ return window.bridge.amm.addLiquidity(...params.args);
+ case Transaction.AMM_REMOVE_LIQUIDITY:
+ return window.bridge.amm.removeLiquidity(...params.args);
+ case Transaction.AMM_CLAIM_REWARDS:
+ return window.bridge.amm.claimFarmingRewards(...params.args);
+ /* END - AMM */
+
+ /* START - ISSUE */
+ case Transaction.ISSUE_REQUEST:
+ return window.bridge.issue.request(...params.args);
+ case Transaction.ISSUE_EXECUTE:
+ return window.bridge.issue.execute(...params.args);
+ /* END - ISSUE */
+
+ /* START - REDEEM */
+ case Transaction.REDEEM_CANCEL:
+ return window.bridge.redeem.cancel(...params.args);
+ case Transaction.REDEEM_BURN:
+ return window.bridge.redeem.burn(...params.args);
+ case Transaction.REDEEM_REQUEST:
+ return window.bridge.redeem.request(...params.args);
+ /* END - REDEEM */
+
+ /* START - REPLACE */
+ case Transaction.REPLACE_REQUEST:
+ return window.bridge.replace.request(...params.args);
+ /* END - REPLACE */
+
+ /* START - TOKENS */
+ case Transaction.TOKENS_TRANSFER:
+ return window.bridge.tokens.transfer(...params.args);
+ /* END - TOKENS */
+
+ /* START - LOANS */
+ case Transaction.LOANS_CLAIM_REWARDS:
+ return window.bridge.loans.claimAllSubsidyRewards();
+ case Transaction.LOANS_BORROW:
+ return window.bridge.loans.borrow(...params.args);
+ case Transaction.LOANS_LEND:
+ return window.bridge.loans.lend(...params.args);
+ case Transaction.LOANS_REPAY:
+ return window.bridge.loans.repay(...params.args);
+ case Transaction.LOANS_REPAY_ALL:
+ return window.bridge.loans.repayAll(...params.args);
+ case Transaction.LOANS_WITHDRAW:
+ return window.bridge.loans.withdraw(...params.args);
+ case Transaction.LOANS_WITHDRAW_ALL:
+ return window.bridge.loans.withdrawAll(...params.args);
+ case Transaction.LOANS_DISABLE_COLLATERAL:
+ return window.bridge.loans.disableAsCollateral(...params.args);
+ case Transaction.LOANS_ENABLE_COLLATERAL:
+ return window.bridge.loans.enableAsCollateral(...params.args);
+ /* END - LOANS */
+
+ /* START - LOANS */
+ 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);
+ /* START - REWARDS */
+ case Transaction.REWARDS_WITHDRAW:
+ return window.bridge.rewards.withdrawRewards(...params.args);
+ /* START - REWARDS */
+ /* END - LOANS */
+
+ /* START - ESCROW */
+ case Transaction.ESCROW_CREATE_LOCK:
+ return window.bridge.escrow.createLock(...params.args);
+ case Transaction.ESCROW_INCREASE_LOCKED_AMOUNT:
+ return window.bridge.escrow.increaseAmount(...params.args);
+ case Transaction.ESCROW_INCREASE_LOCKED_TIME:
+ return window.bridge.escrow.increaseUnlockHeight(...params.args);
+ case Transaction.ESCROW_WITHDRAW:
+ return window.bridge.escrow.withdraw(...params.args);
+ case Transaction.ESCROW_WITHDRAW_REWARDS:
+ return window.bridge.escrow.withdrawRewards(...params.args);
+ case Transaction.ESCROW_INCREASE_LOOKED_TIME_AND_AMOUNT: {
+ const [amount, unlockHeight] = params.args;
+ const txs = [
+ window.bridge.api.tx.escrow.increaseAmount(amount),
+ window.bridge.api.tx.escrow.increaseUnlockHeight(unlockHeight)
+ ];
+ const batch = window.bridge.api.tx.utility.batchAll(txs);
+
+ 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';
+ }
+};
+
+export { getExtrinsic, getStatus };
diff --git a/src/utils/hooks/transaction/utils/submit.ts b/src/utils/hooks/transaction/utils/submit.ts
new file mode 100644
index 0000000000..d1c832b023
--- /dev/null
+++ b/src/utils/hooks/transaction/utils/submit.ts
@@ -0,0 +1,107 @@
+import { ExtrinsicData } from '@interlay/interbtc-api';
+import { ApiPromise } from '@polkadot/api';
+import { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types';
+import { DispatchError } from '@polkadot/types/interfaces';
+import { ExtrinsicStatus } from '@polkadot/types/interfaces/author';
+import { ISubmittableResult } from '@polkadot/types/types';
+
+import { TransactionEvents } from '../types';
+
+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,
+ expectedStatus?: ExtrinsicStatus['type'],
+ callbacks?: TransactionEvents
+) => {
+ let isComplete = false;
+
+ // Extrinsic status
+ let isReady = false;
+
+ return new Promise((resolve, reject) => {
+ let unsubscribe: () => void;
+
+ (extrinsicData.extrinsic as SubmittableExtrinsic<'promise'>)
+ .signAndSend(account, transactionOptions, callback)
+ .then((unsub) => (unsubscribe = unsub))
+ .catch((error) => reject(error));
+
+ function callback(result: ISubmittableResult): void {
+ const { onReady } = callbacks || {};
+
+ if (!isReady && result.status.isReady) {
+ onReady?.();
+ isReady = true;
+ }
+
+ if (!isComplete) {
+ isComplete = expectedStatus === result.status.type;
+ }
+
+ if (isComplete) {
+ resolve({ unsubscribe, result });
+ }
+ }
+ });
+};
+
+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(' ')}`);
+ }
+
+ // Bad origin
+ if (isBadOrigin) {
+ return message.concat(
+ ` The error is caused by using an incorrect account. The error code is BadOrigin ${dispatchError}.`
+ );
+ }
+
+ return message.concat(` The error is ${dispatchError}.`);
+};
+
+/**
+ * Handles transaction submittion and error
+ * @param {ApiPromise} api polkadot api wrapper
+ * @param {AddressOrPair} account account address
+ * @param {ExtrinsicData} extrinsicData transaction extrinsic data
+ * @param {ExtrinsicStatus.type} expectedStatus status where the transaction is counted as fulfilled
+ * @param {TransactionEvents} callbacks a set of events emitted accross the lifecycle of the transaction (i.e Bro)
+ * @return {Promise} transaction data that also can contain meta data in case of error
+ */
+const submitTransaction = async (
+ api: ApiPromise,
+ account: AddressOrPair,
+ extrinsicData: ExtrinsicData,
+ expectedStatus?: ExtrinsicStatus['type'],
+ callbacks?: TransactionEvents
+): Promise => {
+ const { result, unsubscribe } = await handleTransaction(account, extrinsicData, expectedStatus, callbacks);
+
+ unsubscribe();
+
+ const { dispatchError } = result;
+
+ if (dispatchError) {
+ const message = getErrorMessage(api, dispatchError);
+ throw new Error(message);
+ }
+
+ return result;
+};
+
+export { submitTransaction };
From e1587986d4a720cf33c7b2833bc7fd1c53cc3586 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peter=20Slan=C3=BD?=
<47864599+peterslany@users.noreply.github.com>
Date: Tue, 23 May 2023 12:07:37 +0200
Subject: [PATCH 007/220] chore: update monetary to latest 0.7.3 (#1214)
* chore: update monetary to latest 0.7.3
* chore: update lib
---
package.json | 4 ++--
yarn.lock | 18 +++++++++---------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/package.json b/package.json
index 7e6cdc4d90..e00e21f043 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,8 @@
"@headlessui/react": "^1.1.1",
"@heroicons/react": "^2.0.18",
"@interlay/bridge": "^0.3.9",
- "@interlay/interbtc-api": "2.2.2",
- "@interlay/monetary-js": "0.7.2",
+ "@interlay/interbtc-api": "2.2.3",
+ "@interlay/monetary-js": "0.7.3",
"@polkadot/api": "9.14.2",
"@polkadot/extension-dapp": "0.44.1",
"@polkadot/react-identicon": "^2.11.1",
diff --git a/yarn.lock b/yarn.lock
index e7e055b8a8..086d9aed21 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2120,14 +2120,14 @@
dependencies:
axios "^0.21.1"
-"@interlay/interbtc-api@2.2.2":
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/@interlay/interbtc-api/-/interbtc-api-2.2.2.tgz#4803a80244abc9ef404dddefd265b9ece7ca859f"
- integrity sha512-NuRjjIqeUPkt+aOTTmjMhx3TKAsL4id8kubiQsrAcyhsZnsnv/1bCXECzAWaZHVSi+XcxzfuoNLOxqrrx2+ISw==
+"@interlay/interbtc-api@2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@interlay/interbtc-api/-/interbtc-api-2.2.3.tgz#570937750701bf359eb3301d4cb72d2cfa6bbcbc"
+ integrity sha512-W2ldNJkhG/87PWlUpLplSiBpEDlxgDQY6scZwAxZKILuVfTD1IhIlLCnB9BC8wrOG2ihFHhWK0sKTr92Am/VKw==
dependencies:
"@interlay/esplora-btc-api" "0.4.0"
"@interlay/interbtc-types" "1.12.0"
- "@interlay/monetary-js" "0.7.2"
+ "@interlay/monetary-js" "0.7.3"
"@polkadot/api" "9.14.2"
big.js "6.1.1"
bitcoin-core "^3.0.0"
@@ -2147,10 +2147,10 @@
resolved "https://registry.yarnpkg.com/@interlay/interbtc-types/-/interbtc-types-1.12.0.tgz#07dc8e15690292387124dbc2bbb7bf5bc8b68001"
integrity sha512-ELJa2ftIbe8Ds2ejS7kO5HumN9EB5l2OBi3Qsy5iHJsHKq2HtXfFoKnW38HarM6hADrWG+e/yNGHSKJIJzEZuA==
-"@interlay/monetary-js@0.7.2":
- version "0.7.2"
- resolved "https://registry.yarnpkg.com/@interlay/monetary-js/-/monetary-js-0.7.2.tgz#a54a315b60be12f5b1a9c31f0d71d5e8ee7ba174"
- integrity sha512-SqNKJKBEstXuLnzqWi+ON+9ImrNVlKqZpXfiTtT3bcvxqh4jnVsGbYVe2I0FqDWtilCWq6/1RjKkpaSG2dvJhA==
+"@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"
From eee628c6d0810559c04751b7a5c51c56bd10d6da Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Tue, 23 May 2023 14:24:15 +0100
Subject: [PATCH 008/220] chore: bump lib and bridge (#1219)
---
package.json | 4 ++--
yarn.lock | 16 ++++++++--------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/package.json b/package.json
index e00e21f043..a07b2124a3 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,8 @@
"@craco/craco": "^6.1.1",
"@headlessui/react": "^1.1.1",
"@heroicons/react": "^2.0.18",
- "@interlay/bridge": "^0.3.9",
- "@interlay/interbtc-api": "2.2.3",
+ "@interlay/bridge": "^0.3.10",
+ "@interlay/interbtc-api": "2.2.4",
"@interlay/monetary-js": "0.7.3",
"@polkadot/api": "9.14.2",
"@polkadot/extension-dapp": "0.44.1",
diff --git a/yarn.lock b/yarn.lock
index 086d9aed21..269fc4fdba 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.9":
- version "0.3.9"
- resolved "https://registry.yarnpkg.com/@interlay/bridge/-/bridge-0.3.9.tgz#fc39c64708eab2f55cb0bbb970f2f0e72583cb37"
- integrity sha512-QCeTux1f3LwLJ/dcfHmOTOuF8ocfpo9WDNV7Z1GWTHX/I8lspidj4xh8c/g2+jNZnHMiINXCSvHGPPr05lTnQg==
+"@interlay/bridge@^0.3.10":
+ version "0.3.10"
+ resolved "https://registry.yarnpkg.com/@interlay/bridge/-/bridge-0.3.10.tgz#d686b83af91f99a1f62b72cfbb8d127c60557200"
+ integrity sha512-8yDfhvDNIeaW07Kbfvjp37YUNpsKsSXooT5JVfC1AdQs9ej51fbuhINUK31JNncI0xCWhTBu72Wq0ZFIQNm8RA==
dependencies:
"@acala-network/api" "4.1.8-13"
"@acala-network/sdk" "4.1.8-13"
@@ -2120,10 +2120,10 @@
dependencies:
axios "^0.21.1"
-"@interlay/interbtc-api@2.2.3":
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/@interlay/interbtc-api/-/interbtc-api-2.2.3.tgz#570937750701bf359eb3301d4cb72d2cfa6bbcbc"
- integrity sha512-W2ldNJkhG/87PWlUpLplSiBpEDlxgDQY6scZwAxZKILuVfTD1IhIlLCnB9BC8wrOG2ihFHhWK0sKTr92Am/VKw==
+"@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==
dependencies:
"@interlay/esplora-btc-api" "0.4.0"
"@interlay/interbtc-types" "1.12.0"
From 63b059aa5159581e2c2deb219f935b9de5fe14b9 Mon Sep 17 00:00:00 2001
From: Thomas Jeatt
Date: Wed, 24 May 2023 09:28:43 +0100
Subject: [PATCH 009/220] chore: release v2.32.1
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index a07b2124a3..d650a2e77e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interbtc-ui",
- "version": "2.32.0",
+ "version": "2.32.1",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
From 65d0a009915d1e2bd2b785bccf72ce4407c255d1 Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Wed, 24 May 2023 11:25:44 +0100
Subject: [PATCH 010/220] fix: add missing icons and remove erroring RPC
(#1222)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 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>
---
.../components/ChainIcon/ChainIcon.tsx | 19 ++-
.../components/ChainIcon/icons/Acala.tsx | 147 ++++++++++++++++++
.../components/ChainIcon/icons/Astar.tsx | 58 +++++++
.../components/ChainIcon/icons/Parallel.tsx | 47 ++++++
.../components/ChainIcon/icons/index.ts | 3 +
src/utils/hooks/api/xcm/xcm-endpoints.ts | 7 +-
6 files changed, 274 insertions(+), 7 deletions(-)
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Acala.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Astar.tsx
create mode 100644 src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Parallel.tsx
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx
index 339e83d336..dd99e9e509 100644
--- a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/ChainIcon.tsx
@@ -3,11 +3,27 @@ import { forwardRef, ForwardRefExoticComponent, RefAttributes } from 'react';
import { IconProps } from '@/component-library/Icon';
import { StyledFallbackIcon } from './ChainIcon.style';
-import { BIFROST, HEIKO, HYDRA, INTERLAY, KARURA, KINTSUGI, KUSAMA, POLKADOT, STATEMINE, STATEMINT } from './icons';
+import {
+ ACALA,
+ ASTAR,
+ BIFROST,
+ HEIKO,
+ HYDRA,
+ INTERLAY,
+ KARURA,
+ KINTSUGI,
+ KUSAMA,
+ PARALLEL,
+ POLKADOT,
+ STATEMINE,
+ STATEMINT
+} from './icons';
type ChainComponent = ForwardRefExoticComponent>;
const chainsIcon: Record = {
+ ACALA,
+ ASTAR,
BIFROST,
HEIKO,
HYDRA,
@@ -15,6 +31,7 @@ const chainsIcon: Record = {
KARURA,
KINTSUGI,
KUSAMA,
+ PARALLEL,
POLKADOT,
STATEMINE,
STATEMINT
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Acala.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Acala.tsx
new file mode 100644
index 0000000000..137ffa2a35
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Acala.tsx
@@ -0,0 +1,147 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const ACALA = forwardRef((props, ref) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+));
+
+ACALA.displayName = 'ACALA';
+
+export { ACALA };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Astar.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Astar.tsx
new file mode 100644
index 0000000000..61601ee25d
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Astar.tsx
@@ -0,0 +1,58 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const ASTAR = forwardRef((props, ref) => (
+
+ ASTAR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+));
+
+ASTAR.displayName = 'ASTAR';
+
+export { ASTAR };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Parallel.tsx b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Parallel.tsx
new file mode 100644
index 0000000000..b6fc644b49
--- /dev/null
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/Parallel.tsx
@@ -0,0 +1,47 @@
+import { forwardRef } from 'react';
+
+import { Icon, IconProps } from '@/component-library/Icon';
+
+const PARALLEL = forwardRef((props, ref) => (
+
+ PARALLEL
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+));
+
+PARALLEL.displayName = 'PARALLEL';
+
+export { PARALLEL };
diff --git a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts
index e5d13a7014..d3c471eb7a 100644
--- a/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts
+++ b/src/pages/Transfer/CrossChainTransferForm/components/ChainIcon/icons/index.ts
@@ -1,3 +1,5 @@
+export { ACALA } from './Acala';
+export { ASTAR } from './Astar';
export { BIFROST } from './Bifrost';
export { HEIKO } from './Heiko';
export { HYDRA } from './Hydra';
@@ -5,6 +7,7 @@ export { INTERLAY } from './Interlay';
export { KARURA } from './Karura';
export { KINTSUGI } from './Kintsugi';
export { KUSAMA } from './Kusama';
+export { PARALLEL } from './Parallel';
export { POLKADOT } from './Polkadot';
export { STATEMINE } from './Statemine';
export { STATEMINT } from './Statemint';
diff --git a/src/utils/hooks/api/xcm/xcm-endpoints.ts b/src/utils/hooks/api/xcm/xcm-endpoints.ts
index 6b8407c0df..fe751d615b 100644
--- a/src/utils/hooks/api/xcm/xcm-endpoints.ts
+++ b/src/utils/hooks/api/xcm/xcm-endpoints.ts
@@ -3,12 +3,7 @@ import { ChainName } from '@interlay/bridge';
type XCMEndpointsRecord = Record;
const XCMEndpoints: XCMEndpointsRecord = {
- acala: [
- 'wss://acala-rpc-0.aca-api.network',
- 'wss://acala-rpc-1.aca-api.network',
- 'wss://acala-rpc-3.aca-api.network/ws',
- 'wss://acala-rpc.dwellir.com'
- ],
+ acala: ['wss://acala-rpc-1.aca-api.network', 'wss://acala-rpc-3.aca-api.network/ws', 'wss://acala-rpc.dwellir.com'],
astar: ['wss://rpc.astar.network', 'wss://astar-rpc.dwellir.com'],
bifrost: ['wss://bifrost-rpc.dwellir.com'],
heiko: ['wss://heiko-rpc.parallel.fi'],
From 250f7225f2e7912c90b5eeb59fd36f6b3d7aa636 Mon Sep 17 00:00:00 2001
From: Thomas Jeatt
Date: Wed, 24 May 2023 11:34:07 +0100
Subject: [PATCH 011/220] chore: release v2.32.2
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index d650a2e77e..389305f0a0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interbtc-ui",
- "version": "2.32.1",
+ "version": "2.32.2",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
From c000243e5094b36bc2a2b9c758706be9929de1a9 Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Wed, 24 May 2023 13:24:09 +0100
Subject: [PATCH 012/220] fix: compare input configs with method not operator
(#1225)
---
src/utils/hooks/api/xcm/use-xcm-bridge.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/hooks/api/xcm/use-xcm-bridge.ts b/src/utils/hooks/api/xcm/use-xcm-bridge.ts
index 32a6845d77..491a8931d3 100644
--- a/src/utils/hooks/api/xcm/use-xcm-bridge.ts
+++ b/src/utils/hooks/api/xcm/use-xcm-bridge.ts
@@ -111,7 +111,7 @@ const useXCMBridge = (): UseXCMBridge => {
const minInputToBig = Big(inputConfig.minInput.toString());
// Never show less than zero
- const transferableBalance = inputConfig.maxInput < inputConfig.minInput ? 0 : maxInputToBig;
+ const transferableBalance = inputConfig.maxInput.isLessThan(inputConfig.minInput) ? 0 : maxInputToBig;
const currency = XCMBridge.findAdapter(from).getToken(token, from);
const nativeToken = originAdapter.getNativeToken();
From 17251a3b5c76ae716da4c7f1e0de61bb15c484e8 Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Wed, 24 May 2023 14:46:32 +0100
Subject: [PATCH 013/220] refactor: reset selected account on account change
(#1226)
---
.../CrossChainTransferForm/CrossChainTransferForm.tsx | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
index 85b5cd0eb1..1e7a864185 100644
--- a/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
+++ b/src/pages/Transfer/CrossChainTransferForm/CrossChainTransferForm.tsx
@@ -212,6 +212,14 @@ const CrossChainTransferForm = (): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [accountId, destinationChains]);
+ // TODO: When we refactor account select this should be handled there so
+ // that it's consitent across the application
+ useEffect(() => {
+ if (!accountId) return;
+ form.setFieldValue(CROSS_CHAIN_TRANSFER_TO_ACCOUNT_FIELD, accountId?.toString());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [accountId]);
+
if (!originatingChains || !destinationChains || !transferableTokens.length) {
return (
From 0d81dc58af298232cb0b66924d08ee134174bfb8 Mon Sep 17 00:00:00 2001
From: Thomas Jeatt
Date: Wed, 24 May 2023 15:59:14 +0100
Subject: [PATCH 014/220] chore: release v2.32.3
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 389305f0a0..312ec05883 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interbtc-ui",
- "version": "2.32.2",
+ "version": "2.32.3",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
From 63487d09bd6bf3b55ae58b7cdc3da3d2657d4011 Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Thu, 25 May 2023 10:49:47 +0100
Subject: [PATCH 015/220] feature: add geoblock feature flag (#1230)
---
src/components/Geoblock/Geoblock.tsx | 2 +-
src/utils/hooks/use-feature-flag.ts | 6 ++++--
src/utils/hooks/use-geoblocking.ts | 7 ++++++-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/components/Geoblock/Geoblock.tsx b/src/components/Geoblock/Geoblock.tsx
index 43493562d3..0a308d5619 100644
--- a/src/components/Geoblock/Geoblock.tsx
+++ b/src/components/Geoblock/Geoblock.tsx
@@ -1,4 +1,4 @@
-import React, { ReactNode } from 'react';
+import { ReactNode } from 'react';
import { useGeoblocking } from '@/utils/hooks/use-geoblocking';
diff --git a/src/utils/hooks/use-feature-flag.ts b/src/utils/hooks/use-feature-flag.ts
index 8f9254eab3..917bd3b3c8 100644
--- a/src/utils/hooks/use-feature-flag.ts
+++ b/src/utils/hooks/use-feature-flag.ts
@@ -3,7 +3,8 @@ enum FeatureFlags {
AMM = 'amm',
WALLET = 'wallet',
BANXA = 'banxa',
- EARN_STRATEGIES = 'earn-strategies'
+ EARN_STRATEGIES = 'earn-strategies',
+ GEOBLOCK = 'geoblock'
}
const featureFlags: Record = {
@@ -11,7 +12,8 @@ const featureFlags: Record = {
[FeatureFlags.AMM]: process.env.REACT_APP_FEATURE_FLAG_AMM,
[FeatureFlags.WALLET]: process.env.REACT_APP_FEATURE_FLAG_WALLET,
[FeatureFlags.BANXA]: process.env.REACT_APP_FEATURE_FLAG_BANXA,
- [FeatureFlags.EARN_STRATEGIES]: process.env.REACT_APP_FEATURE_FLAG_EARN_STRATEGIES
+ [FeatureFlags.EARN_STRATEGIES]: process.env.REACT_APP_FEATURE_FLAG_EARN_STRATEGIES,
+ [FeatureFlags.GEOBLOCK]: process.env.REACT_APP_FEATURE_FLAG_GEOBLOCK
};
const useFeatureFlag = (feature: FeatureFlags): boolean => featureFlags[feature] === 'enabled';
diff --git a/src/utils/hooks/use-geoblocking.ts b/src/utils/hooks/use-geoblocking.ts
index 6ca37d3a02..09805c8398 100644
--- a/src/utils/hooks/use-geoblocking.ts
+++ b/src/utils/hooks/use-geoblocking.ts
@@ -1,9 +1,14 @@
import { useEffect } from 'react';
import { GEOBLOCK_API_ENDPOINT, GEOBLOCK_REDIRECTION_LINK } from '@/config/links';
+import { FeatureFlags, useFeatureFlag } from '@/utils/hooks/use-feature-flag';
const useGeoblocking = (): void => {
+ const isGeoblockEnabled = useFeatureFlag(FeatureFlags.GEOBLOCK);
+
useEffect(() => {
+ if (!isGeoblockEnabled) return;
+
const checkCountry = async () => {
try {
const response = await fetch(GEOBLOCK_API_ENDPOINT);
@@ -16,7 +21,7 @@ const useGeoblocking = (): void => {
}
};
checkCountry();
- }, []);
+ }, [isGeoblockEnabled]);
};
export { useGeoblocking };
From d6db39b3ab172082e251cac0b6310a8cdb38bb7c Mon Sep 17 00:00:00 2001
From: Thomas Jeatt
Date: Thu, 25 May 2023 11:09:49 +0100
Subject: [PATCH 016/220] chore: release v2.32.4
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 312ec05883..735d72e184 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interbtc-ui",
- "version": "2.32.3",
+ "version": "2.32.4",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
From 5f0dcb8525c12e415381ac7b3737f11490af049e Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Fri, 26 May 2023 10:49:12 +0100
Subject: [PATCH 017/220] chore: bump bridge (#1233)
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 735d72e184..271ee73fe1 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"@craco/craco": "^6.1.1",
"@headlessui/react": "^1.1.1",
"@heroicons/react": "^2.0.18",
- "@interlay/bridge": "^0.3.10",
+ "@interlay/bridge": "^0.3.11",
"@interlay/interbtc-api": "2.2.4",
"@interlay/monetary-js": "0.7.3",
"@polkadot/api": "9.14.2",
diff --git a/yarn.lock b/yarn.lock
index 269fc4fdba..77273acb3a 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.10":
- version "0.3.10"
- resolved "https://registry.yarnpkg.com/@interlay/bridge/-/bridge-0.3.10.tgz#d686b83af91f99a1f62b72cfbb8d127c60557200"
- integrity sha512-8yDfhvDNIeaW07Kbfvjp37YUNpsKsSXooT5JVfC1AdQs9ej51fbuhINUK31JNncI0xCWhTBu72Wq0ZFIQNm8RA==
+"@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==
dependencies:
"@acala-network/api" "4.1.8-13"
"@acala-network/sdk" "4.1.8-13"
From 891de67e0e61d386299d11ecc476e89843a56673 Mon Sep 17 00:00:00 2001
From: Thomas Jeatt
Date: Fri, 26 May 2023 10:51:33 +0100
Subject: [PATCH 018/220] chore: release v2.32.5
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 271ee73fe1..3b6ae00edf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interbtc-ui",
- "version": "2.32.4",
+ "version": "2.32.5",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
From 918e944ed434716071a74e80e73bc6a43e9b9a00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peter=20Slan=C3=BD?=
<47864599+peterslany@users.noreply.github.com>
Date: Tue, 30 May 2023 16:23:02 +0200
Subject: [PATCH 019/220] 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
---
src/assets/locales/en/translation.json | 7 +-
.../ReceivableAssets/index.tsx} | 14 +--
src/components/index.tsx | 1 +
src/lib/form/schemas/earn-strategy.ts | 21 ++++
src/lib/form/schemas/index.ts | 1 +
.../components/WithdrawForm/WithdrawForm.tsx | 5 +-
.../EarnStrategies/EarnStrategies.style.tsx | 13 +++
src/pages/EarnStrategies/EarnStrategies.tsx | 9 +-
.../EarnStrategyDepositForm.tsx | 66 +++++++++++
.../EarnStrategyDepositForm/index.ts | 1 +
.../EarnStrategyForm.style.tsx | 34 ++++++
.../EarnStrategyForm/EarnStrategyForm.tsx | 61 ++++++++++
.../EarnStrategyForm/EarnStrategyFormFees.tsx | 35 ++++++
.../EarnStrategyWithdrawalForm.tsx | 104 ++++++++++++++++++
.../EarnStrategyWithdrawalForm/index.ts | 1 +
.../components/EarnStrategyForm/index.ts | 1 +
src/pages/EarnStrategies/components/index.ts | 1 +
src/pages/EarnStrategies/types/form.ts | 18 +++
yarn.lock | 9 ++
19 files changed, 390 insertions(+), 12 deletions(-)
rename src/{pages/AMM/Pools/components/WithdrawForm/WithdrawAssets.tsx => components/ReceivableAssets/index.tsx} (82%)
create mode 100644 src/lib/form/schemas/earn-strategy.ts
create mode 100644 src/pages/EarnStrategies/EarnStrategies.style.tsx
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts
create mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/index.ts
create mode 100644 src/pages/EarnStrategies/components/index.ts
create mode 100644 src/pages/EarnStrategies/types/form.ts
diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json
index e3a0d7164b..defab36d41 100644
--- a/src/assets/locales/en/translation.json
+++ b/src/assets/locales/en/translation.json
@@ -20,6 +20,7 @@
"reimbursed": "Reimbursed",
"online": "Online",
"offline": "Offline",
+ "available": "Available",
"unavailable": "Unavailable",
"ok": "OK",
"pending": "Pending",
@@ -154,6 +155,7 @@
"unlocks": "Unlocks",
"staked": "Staked",
"sign_t&cs": "Sign T&Cs",
+ "receivable_assets": "Receivable Assets",
"redeem_page": {
"maximum_in_single_request": "Max redeemable in single request",
"redeem": "Redeem",
@@ -586,7 +588,6 @@
"pool_name": "Pool Name",
"add_liquidity": "Add Liquidity",
"remove_liquidity": "Remove Liquidity",
- "receivable_assets": "Receivable Assets",
"initial_rate_warning": "Note: You are setting the initial exchange rate of this pool. Make sure it reflects the exchange rate on other markets, please."
},
"swap": "Swap",
@@ -631,5 +632,9 @@
"total_governance_locked": "Total {{token}} Locked",
"available_to_stake": "Available to stake",
"voting_power_governance": "Voting Power {{token}}"
+ },
+ "earn_strategy": {
+ "withdraw_rewards_in_wrapped": "Withdraw rewards in {{wrappedCurrencySymbol}}:",
+ "update_position": "Update position"
}
}
diff --git a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawAssets.tsx b/src/components/ReceivableAssets/index.tsx
similarity index 82%
rename from src/pages/AMM/Pools/components/WithdrawForm/WithdrawAssets.tsx
rename to src/components/ReceivableAssets/index.tsx
index 0558749d28..9c4fecef5f 100644
--- a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawAssets.tsx
+++ b/src/components/ReceivableAssets/index.tsx
@@ -7,18 +7,18 @@ import { CoinIcon, Dd, Dl, DlGroup, Dt, Flex, P } from '@/component-library';
import { getTokenPrice } from '@/utils/helpers/prices';
import { Prices } from '@/utils/hooks/api/use-get-prices';
-type WithdrawAssetsProps = {
- pooledAmounts: MonetaryAmount[];
+type ReceivableAssetsProps = {
+ assetAmounts: MonetaryAmount[];
prices?: Prices;
};
-const WithdrawAssets = ({ pooledAmounts, prices }: WithdrawAssetsProps): JSX.Element => {
+const ReceivableAssets = ({ assetAmounts: pooledAmounts, prices }: ReceivableAssetsProps): JSX.Element => {
const { t } = useTranslation();
return (
- {t('amm.pools.receivable_assets')}
+ {t('receivable_assets')}
{pooledAmounts.map((amount) => {
@@ -50,7 +50,7 @@ const WithdrawAssets = ({ pooledAmounts, prices }: WithdrawAssetsProps): JSX.Ele
);
};
-WithdrawAssets.displayName = 'WithdrawAssets';
+ReceivableAssets.displayName = 'ReceivableAssets';
-export { WithdrawAssets };
-export type { WithdrawAssetsProps };
+export { ReceivableAssets };
+export type { ReceivableAssetsProps };
diff --git a/src/components/index.tsx b/src/components/index.tsx
index 5149bf3220..83fc0ca6aa 100644
--- a/src/components/index.tsx
+++ b/src/components/index.tsx
@@ -12,3 +12,4 @@ export type { LoanPositionsTableProps } from './LoanPositionsTable';
export { LoanPositionsTable } from './LoanPositionsTable';
export type { PoolsTableProps } from './PoolsTable';
export { PoolsTable } from './PoolsTable';
+export { ReceivableAssets } from './ReceivableAssets';
diff --git a/src/lib/form/schemas/earn-strategy.ts b/src/lib/form/schemas/earn-strategy.ts
new file mode 100644
index 0000000000..75dd1f1b15
--- /dev/null
+++ b/src/lib/form/schemas/earn-strategy.ts
@@ -0,0 +1,21 @@
+import { EarnStrategyFormType } from '@/pages/EarnStrategies/types/form';
+
+import yup, { MaxAmountValidationParams, MinAmountValidationParams } from '../yup.custom';
+
+type EarnStrategyValidationParams = MaxAmountValidationParams & MinAmountValidationParams;
+
+const earnStrategySchema = (
+ earnStrategyFormType: EarnStrategyFormType,
+ params: EarnStrategyValidationParams
+): yup.ObjectSchema => {
+ return yup.object().shape({
+ [earnStrategyFormType]: yup
+ .string()
+ .requiredAmount(earnStrategyFormType)
+ .maxAmount(params)
+ .minAmount(params, earnStrategyFormType)
+ });
+};
+
+export { earnStrategySchema };
+export type { EarnStrategyValidationParams };
diff --git a/src/lib/form/schemas/index.ts b/src/lib/form/schemas/index.ts
index a792ef0fbe..87a172bab2 100644
--- a/src/lib/form/schemas/index.ts
+++ b/src/lib/form/schemas/index.ts
@@ -5,6 +5,7 @@ export type {
WithdrawLiquidityPoolValidationParams
} from './amm';
export { depositLiquidityPoolSchema, WITHDRAW_LIQUIDITY_POOL_FIELD, withdrawLiquidityPoolSchema } from './amm';
+export { earnStrategySchema } from './earn-strategy';
export type { LoanFormData, LoanValidationParams } from './loans';
export { loanSchema } from './loans';
export type { SwapFormData, SwapValidationParams } from './swap';
diff --git a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
index 4f2ea60b55..2d74a356af 100644
--- a/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
+++ b/src/pages/AMM/Pools/components/WithdrawForm/WithdrawForm.tsx
@@ -10,7 +10,7 @@ import {
newSafeMonetaryAmount
} from '@/common/utils/utils';
import { Dd, DlGroup, Dt, Flex, TokenInput } from '@/component-library';
-import { AuthCTA } from '@/components';
+import { AuthCTA, ReceivableAssets } from '@/components';
import { GOVERNANCE_TOKEN, TRANSACTION_FEE_AMOUNT } from '@/config/relay-chains';
import { isFormDisabled, useForm, WITHDRAW_LIQUIDITY_POOL_FIELD } from '@/lib/form';
import { WithdrawLiquidityPoolFormData, withdrawLiquidityPoolSchema } from '@/lib/form/schemas';
@@ -23,7 +23,6 @@ import { Transaction, useTransaction } from '@/utils/hooks/transaction';
import useAccountId from '@/utils/hooks/use-account-id';
import { PoolName } from '../PoolName';
-import { WithdrawAssets } from './WithdrawAssets';
import { StyledDl } from './WithdrawForm.styles';
type WithdrawFormProps = {
@@ -126,7 +125,7 @@ const WithdrawForm = ({ pool, slippageModalRef, onWithdraw }: WithdrawFormProps)
{...form.getFieldProps(WITHDRAW_LIQUIDITY_POOL_FIELD)}
/>
-
+
diff --git a/src/pages/EarnStrategies/EarnStrategies.style.tsx b/src/pages/EarnStrategies/EarnStrategies.style.tsx
new file mode 100644
index 0000000000..8bc1b94263
--- /dev/null
+++ b/src/pages/EarnStrategies/EarnStrategies.style.tsx
@@ -0,0 +1,13 @@
+import styled from 'styled-components';
+
+import { theme } from '@/component-library';
+const StyledEarnStrategiesLayout = styled.div`
+ display: grid;
+ gap: ${theme.spacing.spacing6};
+ @media (min-width: 80em) {
+ grid-template-columns: 1fr 1fr;
+ }
+ padding: ${theme.spacing.spacing6};
+`;
+
+export { StyledEarnStrategiesLayout };
diff --git a/src/pages/EarnStrategies/EarnStrategies.tsx b/src/pages/EarnStrategies/EarnStrategies.tsx
index 84ab2c0d2b..bd1eac8fdc 100644
--- a/src/pages/EarnStrategies/EarnStrategies.tsx
+++ b/src/pages/EarnStrategies/EarnStrategies.tsx
@@ -2,8 +2,15 @@ import { withErrorBoundary } from 'react-error-boundary';
import ErrorFallback from '@/legacy-components/ErrorFallback';
+import { EarnStrategyForm } from './components/EarnStrategyForm';
+import { StyledEarnStrategiesLayout } from './EarnStrategies.style';
+
const EarnStrategies = (): JSX.Element => {
- return Earn Strategies ;
+ return (
+
+
+
+ );
};
export default withErrorBoundary(EarnStrategies, {
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx
new file mode 100644
index 0000000000..b0939fd093
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx
@@ -0,0 +1,66 @@
+import { newMonetaryAmount } from '@interlay/interbtc-api';
+import { mergeProps } from '@react-aria/utils';
+import { useTranslation } from 'react-i18next';
+
+import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
+import { TokenInput } from '@/component-library';
+import { AuthCTA } from '@/components';
+import { TRANSACTION_FEE_AMOUNT, WRAPPED_TOKEN, WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains';
+import { earnStrategySchema, isFormDisabled, useForm } from '@/lib/form';
+import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
+import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { useTransaction } from '@/utils/hooks/transaction';
+
+import { EarnStrategyDepositFormData } from '../../../types/form';
+import { EarnStrategyFormBaseProps } from '../EarnStrategyForm';
+import { StyledEarnStrategyFormContent } from '../EarnStrategyForm.style';
+import { EarnStrategyFormFees } from '../EarnStrategyFormFees';
+
+const EarnStrategyDepositForm = ({ riskVariant, hasActiveStrategy }: EarnStrategyFormBaseProps): JSX.Element => {
+ const { getAvailableBalance } = useGetBalances();
+ const prices = useGetPrices();
+ const { t } = useTranslation();
+ // TODO: add transaction
+ const transaction = useTransaction();
+
+ const handleSubmit = (data: EarnStrategyDepositFormData) => {
+ // TODO: Execute transaction with params
+ // transaction.execute();
+ console.log(`transaction should be executed with parameters: ${data}, ${riskVariant}`);
+ };
+
+ const minAmount = newMonetaryAmount(1, WRAPPED_TOKEN);
+ const maxDepositAmount = getAvailableBalance(WRAPPED_TOKEN_SYMBOL) || newMonetaryAmount(0, WRAPPED_TOKEN);
+
+ const form = useForm({
+ initialValues: { deposit: '' },
+ validationSchema: earnStrategySchema('deposit', { maxAmount: maxDepositAmount, minAmount }),
+ onSubmit: handleSubmit
+ });
+
+ const inputMonetaryAmount = newSafeMonetaryAmount(form.values['deposit'] || 0, WRAPPED_TOKEN, true);
+ const inputUSDValue = convertMonetaryAmountToValueInUSD(inputMonetaryAmount, prices?.[WRAPPED_TOKEN_SYMBOL].usd);
+ const isSubmitButtonDisabled = isFormDisabled(form);
+
+ return (
+
+ );
+};
+
+export { EarnStrategyDepositForm };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts
new file mode 100644
index 0000000000..aaedb7d4bb
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts
@@ -0,0 +1 @@
+export { EarnStrategyDepositForm } from './EarnStrategyDepositForm';
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx
new file mode 100644
index 0000000000..4179c1e7a4
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx
@@ -0,0 +1,34 @@
+import styled from 'styled-components';
+
+import { Dl, Flex, theme } from '@/component-library';
+
+const StyledEarnStrategyForm = styled(Flex)`
+ margin-top: ${theme.spacing.spacing8};
+ background: ${theme.colors.bgPrimary};
+ padding: ${theme.spacing.spacing6};
+ border-radius: ${theme.rounded.md};
+`;
+
+const StyledDl = styled(Dl)`
+ background-color: ${theme.card.bg.secondary};
+ padding: ${theme.spacing.spacing4};
+ font-size: ${theme.text.xs};
+ border-radius: ${theme.rounded.rg};
+`;
+
+const StyledEarnStrategyFormContent = styled(Flex)`
+ margin-top: ${theme.spacing.spacing8};
+ flex-direction: column;
+ gap: ${theme.spacing.spacing8};
+`;
+
+const StyledSwitchLabel = styled('label')`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ font-weight: ${theme.fontWeight.bold};
+`;
+
+export { StyledDl, StyledEarnStrategyForm, StyledEarnStrategyFormContent, StyledSwitchLabel };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx
new file mode 100644
index 0000000000..7f4ba377d5
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx
@@ -0,0 +1,61 @@
+import { newMonetaryAmount } from '@interlay/interbtc-api';
+
+import { Tabs, TabsItem } from '@/component-library';
+import { WRAPPED_TOKEN } from '@/config/relay-chains';
+
+import { EarnStrategyFormType, EarnStrategyRiskVariant } from '../../types/form';
+import { EarnStrategyDepositForm } from './EarnStrategyDepositForm';
+import { StyledEarnStrategyForm } from './EarnStrategyForm.style';
+import { EarnStrategyWithdrawalForm } from './EarnStrategyWithdrawalForm';
+
+interface EarnStrategyFormProps {
+ riskVariant: EarnStrategyRiskVariant;
+}
+
+interface EarnStrategyFormBaseProps extends EarnStrategyFormProps {
+ hasActiveStrategy: boolean | undefined;
+}
+
+type TabData = { type: EarnStrategyFormType; title: string };
+
+const tabs: Array = [
+ {
+ type: 'deposit',
+ title: 'Deposit'
+ },
+ {
+ type: 'withdraw',
+ title: 'Withdraw'
+ }
+];
+
+const EarnStrategyForm = ({ riskVariant }: EarnStrategyFormProps): JSX.Element => {
+ // TODO: replace with actually withdrawable amount once we know how to get that information,
+ // for now it's statically set for display purposes
+ const maxWithdrawableAmount = newMonetaryAmount(1.337, WRAPPED_TOKEN, true);
+ const hasActiveStrategy = maxWithdrawableAmount && !maxWithdrawableAmount.isZero();
+
+ return (
+
+
+ {tabs.map(({ type, title }) => (
+
+ {type === 'deposit' ? (
+
+ ) : (
+
+ )}
+
+ ))}
+
+
+ );
+};
+
+export { EarnStrategyForm };
+export type { EarnStrategyFormBaseProps, EarnStrategyFormProps };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx
new file mode 100644
index 0000000000..007867f302
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx
@@ -0,0 +1,35 @@
+import { GovernanceCurrency } from '@interlay/interbtc-api';
+import { MonetaryAmount } from '@interlay/monetary-js';
+import { useTranslation } from 'react-i18next';
+
+import { displayMonetaryAmountInUSDFormat } from '@/common/utils/utils';
+import { Dd, DlGroup, Dt } from '@/component-library';
+import { getTokenPrice } from '@/utils/helpers/prices';
+import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+
+import { StyledDl } from './EarnStrategyForm.style';
+
+interface EarnStrategyFormFeesProps {
+ amount: MonetaryAmount;
+}
+
+const EarnStrategyFormFees = ({ amount }: EarnStrategyFormFeesProps): JSX.Element => {
+ const prices = useGetPrices();
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ {t('fees')}
+
+
+ {amount.toHuman()} {amount.currency.ticker} (
+ {displayMonetaryAmountInUSDFormat(amount, getTokenPrice(prices, amount.currency.ticker)?.usd)})
+
+
+
+ );
+};
+
+export { EarnStrategyFormFees };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx
new file mode 100644
index 0000000000..606fec950e
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx
@@ -0,0 +1,104 @@
+import { CurrencyExt, newMonetaryAmount, WrappedCurrency } from '@interlay/interbtc-api';
+import { MonetaryAmount } from '@interlay/monetary-js';
+import { mergeProps } from '@react-aria/utils';
+import { useTranslation } from 'react-i18next';
+
+import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/common/utils/utils';
+import { Switch, TokenInput } from '@/component-library';
+import { AuthCTA, ReceivableAssets } from '@/components';
+import {
+ RELAY_CHAIN_NATIVE_TOKEN,
+ TRANSACTION_FEE_AMOUNT,
+ WRAPPED_TOKEN,
+ WRAPPED_TOKEN_SYMBOL
+} from '@/config/relay-chains';
+import { earnStrategySchema, isFormDisabled, useForm } from '@/lib/form';
+import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
+import { useTransaction } from '@/utils/hooks/transaction';
+
+import { EarnStrategyWithdrawalFormData } from '../../../types/form';
+import { EarnStrategyFormBaseProps } from '../EarnStrategyForm';
+import { StyledEarnStrategyFormContent, StyledSwitchLabel } from '../EarnStrategyForm.style';
+import { EarnStrategyFormFees } from '../EarnStrategyFormFees';
+
+interface EarnStrategyWithdrawalFormProps extends EarnStrategyFormBaseProps {
+ maxWithdrawableAmount: MonetaryAmount | undefined;
+}
+
+const calculateReceivableAssets = (
+ amountToWithdraw: MonetaryAmount,
+ withdrawInWrapped: boolean
+): Array> => {
+ if (withdrawInWrapped) {
+ return [amountToWithdraw];
+ }
+ // TODO: do some magic calculation to get the receivable assets based on input amount here,
+ // or better move this computation to earn-strategy hook
+ const mockedReceivableAssets = [
+ amountToWithdraw.div(1.2),
+ newMonetaryAmount(amountToWithdraw.toBig().mul(213.2), RELAY_CHAIN_NATIVE_TOKEN, true)
+ ];
+
+ return mockedReceivableAssets;
+};
+
+const EarnStrategyWithdrawalForm = ({
+ riskVariant,
+ hasActiveStrategy,
+ maxWithdrawableAmount
+}: EarnStrategyWithdrawalFormProps): JSX.Element => {
+ const { t } = useTranslation();
+ const prices = useGetPrices();
+ // TODO: add transaction
+ const transaction = useTransaction();
+
+ const handleSubmit = (data: EarnStrategyWithdrawalFormData) => {
+ // TODO: Execute transaction with params
+ // transaction.execute()
+ console.log(data, riskVariant);
+ };
+
+ const minAmount = newMonetaryAmount(1, WRAPPED_TOKEN);
+
+ const form = useForm({
+ initialValues: { withdraw: '', withdrawAsWrapped: true },
+ validationSchema: earnStrategySchema('withdraw', {
+ maxAmount: maxWithdrawableAmount || newMonetaryAmount(0, WRAPPED_TOKEN),
+ minAmount
+ }),
+ onSubmit: handleSubmit
+ });
+
+ const inputMonetaryAmount = newSafeMonetaryAmount(form.values['withdraw'] || 0, WRAPPED_TOKEN, true);
+ const inputUSDValue = convertMonetaryAmountToValueInUSD(inputMonetaryAmount, prices?.[WRAPPED_TOKEN_SYMBOL].usd);
+ const receivableAssets = calculateReceivableAssets(inputMonetaryAmount, !!form.values['withdrawAsWrapped']);
+ const isSubmitButtonDisabled = isFormDisabled(form);
+
+ return (
+
+ );
+};
+
+export { EarnStrategyWithdrawalForm };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts
new file mode 100644
index 0000000000..aa5c13a062
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts
@@ -0,0 +1 @@
+export { EarnStrategyWithdrawalForm } from './EarnStrategyWithdrawalForm';
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/index.ts b/src/pages/EarnStrategies/components/EarnStrategyForm/index.ts
new file mode 100644
index 0000000000..dd8d107bb2
--- /dev/null
+++ b/src/pages/EarnStrategies/components/EarnStrategyForm/index.ts
@@ -0,0 +1 @@
+export { EarnStrategyForm } from './EarnStrategyForm';
diff --git a/src/pages/EarnStrategies/components/index.ts b/src/pages/EarnStrategies/components/index.ts
new file mode 100644
index 0000000000..dd8d107bb2
--- /dev/null
+++ b/src/pages/EarnStrategies/components/index.ts
@@ -0,0 +1 @@
+export { EarnStrategyForm } from './EarnStrategyForm';
diff --git a/src/pages/EarnStrategies/types/form.ts b/src/pages/EarnStrategies/types/form.ts
new file mode 100644
index 0000000000..c912d7e549
--- /dev/null
+++ b/src/pages/EarnStrategies/types/form.ts
@@ -0,0 +1,18 @@
+type EarnStrategyFormType = 'deposit' | 'withdraw';
+type EarnStrategyRiskVariant = 'low' | 'high';
+
+interface EarnStrategyDepositFormData {
+ deposit?: string;
+}
+
+interface EarnStrategyWithdrawalFormData {
+ withdraw?: string;
+ withdrawAsWrapped?: boolean;
+}
+
+export type {
+ EarnStrategyDepositFormData,
+ EarnStrategyFormType,
+ EarnStrategyRiskVariant,
+ EarnStrategyWithdrawalFormData
+};
diff --git a/yarn.lock b/yarn.lock
index 77273acb3a..b05034e254 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2156,6 +2156,15 @@
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"
From aca4e7dc88ddd1dfb29e77dba1b2b49892c19973 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Sim=C3=A3o?=
Date: Tue, 30 May 2023 15:30:52 +0100
Subject: [PATCH 020/220] feat: add Popover, Underlay and ProgressBar. Changes
to Dialog, Modal and Overlay. (#1236)
---
package.json | 6 +-
.../Dialog/Dialog.stories.tsx | 208 ++++++++++++++++++
src/component-library/Dialog/Dialog.style.tsx | 53 +++++
src/component-library/Dialog/Dialog.tsx | 52 +++++
src/component-library/Dialog/DialogBody.tsx | 14 ++
.../Dialog/DialogContext.tsx | 18 ++
.../Dialog/DialogDivider.tsx | 14 ++
src/component-library/Dialog/DialogFooter.tsx | 16 ++
src/component-library/Dialog/DialogHeader.tsx | 34 +++
src/component-library/Dialog/index.tsx | 10 +
.../Divider/Divider.style.tsx | 5 +-
src/component-library/Divider/Divider.tsx | 8 +-
src/component-library/Modal/Dialog.tsx | 46 ----
src/component-library/Modal/Modal.style.tsx | 80 +------
src/component-library/Modal/Modal.tsx | 60 +++--
src/component-library/Modal/ModalBody.tsx | 17 +-
src/component-library/Modal/ModalContext.tsx | 2 -
src/component-library/Modal/ModalDivider.tsx | 9 +-
src/component-library/Modal/ModalFooter.tsx | 9 +-
src/component-library/Modal/ModalHeader.tsx | 42 +---
src/component-library/Modal/ModalWrapper.tsx | 11 +-
.../Overlay/Overlay.style.tsx | 25 ++-
src/component-library/Overlay/Overlay.tsx | 5 +-
src/component-library/Overlay/Underlay.tsx | 19 ++
src/component-library/Overlay/index.tsx | 2 +
.../Popover/Popover.stories.tsx | 40 ++++
.../Popover/Popover.style.tsx | 30 +++
src/component-library/Popover/Popover.tsx | 54 +++++
src/component-library/Popover/PopoverBody.tsx | 8 +
.../Popover/PopoverContent.tsx | 40 ++++
.../Popover/PopoverContentWrapper.tsx | 74 +++++++
.../Popover/PopoverContext.tsx | 22 ++
.../Popover/PopoverFooter.tsx | 10 +
.../Popover/PopoverHeader.tsx | 12 +
.../Popover/PopoverTrigger.tsx | 38 ++++
src/component-library/Popover/index.tsx | 12 +
.../ProgressBar/ProgressBar.stories.tsx | 18 ++
.../ProgressBar/ProgressBar.style.tsx | 27 +++
.../ProgressBar/ProgressBar.tsx | 51 +++++
src/component-library/ProgressBar/index.tsx | 2 +
src/component-library/Select/Select.style.tsx | 3 +-
src/component-library/Text/style.tsx | 17 +-
src/component-library/Text/types.ts | 1 +
src/component-library/Text/utils.ts | 6 +-
.../TextLink/TextLink.style.tsx | 23 +-
src/component-library/TextLink/TextLink.tsx | 25 ++-
.../TokenInput/TokenInput.style.tsx | 4 +-
src/component-library/Tooltip/Tooltip.tsx | 3 +-
src/component-library/index.tsx | 11 +
src/component-library/theme/theme.base.css | 1 +
src/component-library/theme/theme.ts | 103 +++++++--
src/component-library/utils/prop-types.ts | 5 +-
src/component-library/utils/theme.ts | 6 +
.../AccountSelect/AccountSelect.style.tsx | 3 +-
.../FundWallet/FundWallet.style.tsx | 2 +-
.../components/PoolModal/PoolModal.style.tsx | 2 +-
.../components/LoanModal/LoanModal.style.tsx | 2 +-
yarn.lock | 140 +++++++++++-
58 files changed, 1302 insertions(+), 258 deletions(-)
create mode 100644 src/component-library/Dialog/Dialog.stories.tsx
create mode 100644 src/component-library/Dialog/Dialog.style.tsx
create mode 100644 src/component-library/Dialog/Dialog.tsx
create mode 100644 src/component-library/Dialog/DialogBody.tsx
create mode 100644 src/component-library/Dialog/DialogContext.tsx
create mode 100644 src/component-library/Dialog/DialogDivider.tsx
create mode 100644 src/component-library/Dialog/DialogFooter.tsx
create mode 100644 src/component-library/Dialog/DialogHeader.tsx
create mode 100644 src/component-library/Dialog/index.tsx
delete mode 100644 src/component-library/Modal/Dialog.tsx
create mode 100644 src/component-library/Overlay/Underlay.tsx
create mode 100644 src/component-library/Popover/Popover.stories.tsx
create mode 100644 src/component-library/Popover/Popover.style.tsx
create mode 100644 src/component-library/Popover/Popover.tsx
create mode 100644 src/component-library/Popover/PopoverBody.tsx
create mode 100644 src/component-library/Popover/PopoverContent.tsx
create mode 100644 src/component-library/Popover/PopoverContentWrapper.tsx
create mode 100644 src/component-library/Popover/PopoverContext.tsx
create mode 100644 src/component-library/Popover/PopoverFooter.tsx
create mode 100644 src/component-library/Popover/PopoverHeader.tsx
create mode 100644 src/component-library/Popover/PopoverTrigger.tsx
create mode 100644 src/component-library/Popover/index.tsx
create mode 100644 src/component-library/ProgressBar/ProgressBar.stories.tsx
create mode 100644 src/component-library/ProgressBar/ProgressBar.style.tsx
create mode 100644 src/component-library/ProgressBar/ProgressBar.tsx
create mode 100644 src/component-library/ProgressBar/index.tsx
diff --git a/package.json b/package.json
index 3b6ae00edf..af4e4260e6 100644
--- a/package.json
+++ b/package.json
@@ -24,8 +24,8 @@
"@react-aria/link": "^3.4.0",
"@react-aria/listbox": "^3.8.1",
"@react-aria/meter": "^3.2.1",
- "@react-aria/overlays": "^3.12.0",
- "@react-aria/progress": "^3.3.0",
+ "@react-aria/overlays": "^3.14.0",
+ "@react-aria/progress": "^3.4.1",
"@react-aria/select": "^3.9.0",
"@react-aria/separator": "^3.2.5",
"@react-aria/switch": "^3.2.4",
@@ -37,7 +37,7 @@
"@react-aria/visually-hidden": "^3.6.1",
"@react-stately/collections": "^3.4.1",
"@react-stately/list": "^3.6.1",
- "@react-stately/overlays": "^3.4.3",
+ "@react-stately/overlays": "^3.5.1",
"@react-stately/select": "^3.4.0",
"@react-stately/table": "^3.3.0",
"@react-stately/tabs": "^3.4.0",
diff --git a/src/component-library/Dialog/Dialog.stories.tsx b/src/component-library/Dialog/Dialog.stories.tsx
new file mode 100644
index 0000000000..86b2a655a5
--- /dev/null
+++ b/src/component-library/Dialog/Dialog.stories.tsx
@@ -0,0 +1,208 @@
+import { Meta, Story } from '@storybook/react';
+
+import { CTA } from '../CTA';
+import { Dialog, DialogBody, DialogDivider, DialogFooter, DialogHeader, DialogProps } from '.';
+
+const Template: Story = ({
+ children,
+ hasFooter,
+ hasTitle,
+ ...args
+}) => {
+ return (
+ <>
+
+ {hasTitle && (
+ <>
+
+ Title
+
+
+ >
+ )}
+ {children}
+ {hasFooter && (
+
+ Procced
+
+ )}
+
+ >
+ );
+};
+
+const Default = Template.bind({});
+Default.args = {
+ children: (
+ <>
+ Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam.
+ Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ >
+ )
+};
+
+const WithTitle = Template.bind({});
+WithTitle.args = {
+ hasTitle: true,
+ children: (
+ <>
+ Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam.
+ Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ >
+ )
+};
+
+const WithFooter = Template.bind({});
+WithFooter.args = {
+ hasFooter: true,
+ children: (
+ <>
+ Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam.
+ Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ >
+ )
+};
+
+const LargeContent = Template.bind({});
+LargeContent.args = {
+ hasFooter: true,
+ hasTitle: true,
+ children: (
+ <>
+ Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam.
+ Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac
+ facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo
+ cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo
+ odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
+ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet
+ fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac consectetur ac,
+ vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras mattis consectetur
+ purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi leo risus, porta ac
+ consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Cras
+ mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Morbi
+ leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel scelerisque nisl
+ consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
+ egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna,
+ vel scelerisque nisl consectetur et.
+ >
+ )
+};
+
+export { Default, LargeContent, WithFooter, WithTitle };
+
+export default {
+ title: 'Overlays/Dialog',
+ component: Dialog
+} as Meta;
diff --git a/src/component-library/Dialog/Dialog.style.tsx b/src/component-library/Dialog/Dialog.style.tsx
new file mode 100644
index 0000000000..5f482bc6c5
--- /dev/null
+++ b/src/component-library/Dialog/Dialog.style.tsx
@@ -0,0 +1,53 @@
+import styled from 'styled-components';
+
+import { CTA } from '../CTA';
+import { Divider } from '../Divider';
+import { Flex } from '../Flex';
+import { H3 } from '../Text';
+import { theme } from '../theme';
+import { Sizes } from '../utils/prop-types';
+
+type StyledDialogProps = {
+ $size: Sizes;
+};
+
+const StyledDialog = styled.section`
+ background: ${theme.colors.bgPrimary};
+ border: ${theme.border.default};
+ border-radius: ${theme.rounded.md};
+ color: ${theme.colors.textPrimary};
+ width: ${({ $size }) => theme.dialog[$size].width};
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ outline: none;
+`;
+
+const StyledCloseCTA = styled(CTA)`
+ position: absolute;
+ top: ${theme.spacing.spacing2};
+ right: ${theme.spacing.spacing2};
+ z-index: ${theme.dialog.closeBtn.zIndex};
+`;
+
+const StyledDialogHeader = styled(H3)`
+ padding: ${({ $size }) => theme.dialog[$size].header.padding};
+ overflow: hidden;
+ flex-shrink: 0;
+`;
+
+const StyledDialogDivider = styled(Divider)`
+ margin: ${({ $size }) => `0 ${theme.dialog[$size].divider.marginX} ${theme.dialog[$size].divider.marginBottom}`};
+ flex-shrink: 0;
+`;
+
+const StyledDialogBody = styled(Flex)`
+ padding: ${({ $size }) => `${theme.dialog[$size].body.paddingY} ${theme.dialog[$size].body.paddingX}`};
+ flex: 1 1 auto;
+`;
+
+const StyledDialogFooter = styled(Flex)`
+ padding: ${({ $size }) => theme.dialog[$size].footer.padding};
+`;
+
+export { StyledCloseCTA, StyledDialog, StyledDialogBody, StyledDialogDivider, StyledDialogFooter, StyledDialogHeader };
diff --git a/src/component-library/Dialog/Dialog.tsx b/src/component-library/Dialog/Dialog.tsx
new file mode 100644
index 0000000000..cf706540e5
--- /dev/null
+++ b/src/component-library/Dialog/Dialog.tsx
@@ -0,0 +1,52 @@
+import { AriaDialogProps, useDialog } from '@react-aria/dialog';
+import { mergeProps } from '@react-aria/utils';
+import { PressEvent } from '@react-types/shared';
+import { forwardRef, ReactNode } from 'react';
+
+import { XMark } from '@/assets/icons';
+
+import { useDOMRef } from '../utils/dom';
+import { CTASizes, Sizes } from '../utils/prop-types';
+import { StyledCloseCTA, StyledDialog } from './Dialog.style';
+import { DialogContext } from './DialogContext';
+
+const closeCTASizeMap: Record = { small: 'x-small', medium: 'small', large: 'small' };
+
+type Props = {
+ children?: ReactNode;
+ onClose?: (e: PressEvent) => void;
+ size?: Sizes;
+};
+
+type InheritAttrs = Omit;
+
+type DialogProps = Props & InheritAttrs;
+
+const Dialog = forwardRef(
+ ({ children, onClose, size = 'medium', ...props }, ref): JSX.Element => {
+ const dialogRef = useDOMRef(ref);
+
+ // Get props for the dialog and its title
+ const { dialogProps, titleProps } = useDialog(props, dialogRef);
+
+ const closeCTASize = closeCTASizeMap[size];
+
+ return (
+
+
+ {onClose && (
+
+
+
+ )}
+ {children}
+
+
+ );
+ }
+);
+
+Dialog.displayName = 'Dialog';
+
+export { Dialog };
+export type { DialogProps };
diff --git a/src/component-library/Dialog/DialogBody.tsx b/src/component-library/Dialog/DialogBody.tsx
new file mode 100644
index 0000000000..0c7a36d260
--- /dev/null
+++ b/src/component-library/Dialog/DialogBody.tsx
@@ -0,0 +1,14 @@
+import { FlexProps } from '../Flex';
+import { StyledDialogBody } from './Dialog.style';
+import { useDialogContext } from './DialogContext';
+
+type DialogBodyProps = FlexProps;
+
+const DialogBody = ({ direction = 'column', ...props }: DialogBodyProps): JSX.Element => {
+ const { size } = useDialogContext();
+
+ return ;
+};
+
+export { DialogBody };
+export type { DialogBodyProps };
diff --git a/src/component-library/Dialog/DialogContext.tsx b/src/component-library/Dialog/DialogContext.tsx
new file mode 100644
index 0000000000..ca351cb5b4
--- /dev/null
+++ b/src/component-library/Dialog/DialogContext.tsx
@@ -0,0 +1,18 @@
+import { DOMAttributes } from '@react-types/shared';
+import React from 'react';
+
+import { Sizes } from '../utils/prop-types';
+
+interface DialogConfig {
+ titleProps?: DOMAttributes;
+ size: Sizes;
+}
+
+const defaultContext: DialogConfig = { size: 'medium' };
+
+const DialogContext = React.createContext(defaultContext);
+
+const useDialogContext = (): DialogConfig => React.useContext(DialogContext);
+
+export { DialogContext, useDialogContext };
+export type { DialogConfig };
diff --git a/src/component-library/Dialog/DialogDivider.tsx b/src/component-library/Dialog/DialogDivider.tsx
new file mode 100644
index 0000000000..e724cd4293
--- /dev/null
+++ b/src/component-library/Dialog/DialogDivider.tsx
@@ -0,0 +1,14 @@
+import { DividerProps } from '../Divider';
+import { StyledDialogDivider } from './Dialog.style';
+import { useDialogContext } from './DialogContext';
+
+type DialogDividerProps = Omit;
+
+const DialogDivider = (props: DialogDividerProps): JSX.Element => {
+ const { size } = useDialogContext();
+
+ return ;
+};
+
+export { DialogDivider };
+export type { DialogDividerProps };
diff --git a/src/component-library/Dialog/DialogFooter.tsx b/src/component-library/Dialog/DialogFooter.tsx
new file mode 100644
index 0000000000..a9212d5a6f
--- /dev/null
+++ b/src/component-library/Dialog/DialogFooter.tsx
@@ -0,0 +1,16 @@
+import { FlexProps } from '../Flex';
+import { StyledDialogFooter } from './Dialog.style';
+import { useDialogContext } from './DialogContext';
+
+type InheritAttrs = FlexProps;
+
+type DialogFooterProps = InheritAttrs;
+
+const DialogFooter = (props: DialogFooterProps): JSX.Element => {
+ const { size } = useDialogContext();
+
+ return ;
+};
+
+export { DialogFooter };
+export type { DialogFooterProps };
diff --git a/src/component-library/Dialog/DialogHeader.tsx b/src/component-library/Dialog/DialogHeader.tsx
new file mode 100644
index 0000000000..4130cb70b3
--- /dev/null
+++ b/src/component-library/Dialog/DialogHeader.tsx
@@ -0,0 +1,34 @@
+import { mergeProps } from '@react-aria/utils';
+import { ElementType } from 'react';
+
+import { TextProps } from '../Text';
+import { FontSize, Sizes } from '../utils/prop-types';
+import { StyledDialogHeader } from './Dialog.style';
+import { useDialogContext } from './DialogContext';
+
+const sizeMap: Record = {
+ small: 'base',
+ medium: 'xl',
+ large: 'xl'
+};
+
+type Props = {
+ elementType?: ElementType;
+};
+
+type InheritAttrs = Omit;
+
+type DialogHeaderProps = Props & InheritAttrs;
+
+const DialogHeader = ({ elementType, children, ...props }: DialogHeaderProps): JSX.Element => {
+ const { titleProps, size } = useDialogContext();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export { DialogHeader };
+export type { DialogHeaderProps };
diff --git a/src/component-library/Dialog/index.tsx b/src/component-library/Dialog/index.tsx
new file mode 100644
index 0000000000..b5980d46cd
--- /dev/null
+++ b/src/component-library/Dialog/index.tsx
@@ -0,0 +1,10 @@
+export type { DialogProps } from './Dialog';
+export { Dialog } from './Dialog';
+export type { DialogBodyProps } from './DialogBody';
+export { DialogBody } from './DialogBody';
+export type { DialogDividerProps } from './DialogDivider';
+export { DialogDivider } from './DialogDivider';
+export type { DialogFooterProps } from './DialogFooter';
+export { DialogFooter } from './DialogFooter';
+export type { DialogHeaderProps } from './DialogHeader';
+export { DialogHeader } from './DialogHeader';
diff --git a/src/component-library/Divider/Divider.style.tsx b/src/component-library/Divider/Divider.style.tsx
index 28d8b361bc..2cf4de19ef 100644
--- a/src/component-library/Divider/Divider.style.tsx
+++ b/src/component-library/Divider/Divider.style.tsx
@@ -1,5 +1,6 @@
import styled from 'styled-components';
+import { marginCSS, StyledMarginProps } from '../css/margin';
import { theme } from '../theme';
import { DividerVariants, Orientation, Sizes } from '../utils/prop-types';
import { resolveColor } from '../utils/theme';
@@ -8,7 +9,7 @@ type StyledDividerProps = {
$color: DividerVariants;
$orientation: Orientation;
$size: Sizes;
-};
+} & StyledMarginProps;
const StyledDivider = styled.hr`
background-color: ${({ $color }) => ($color === 'default' ? 'var(--colors-border)' : resolveColor($color))};
@@ -17,6 +18,8 @@ const StyledDivider = styled.hr`
border: 0;
margin: 0;
align-self: stretch;
+ flex-shrink: 0;
+ ${(props) => marginCSS(props)};
`;
export { StyledDivider };
diff --git a/src/component-library/Divider/Divider.tsx b/src/component-library/Divider/Divider.tsx
index 6d1e3cebfa..6b71b7a1e2 100644
--- a/src/component-library/Divider/Divider.tsx
+++ b/src/component-library/Divider/Divider.tsx
@@ -2,7 +2,8 @@ import { useSeparator } from '@react-aria/separator';
import { mergeProps } from '@react-aria/utils';
import { forwardRef, HTMLAttributes } from 'react';
-import { DividerVariants, ElementTypeProp, Orientation, Sizes } from '../utils/prop-types';
+import { DividerVariants, ElementTypeProp, MarginProps, Orientation, Sizes } from '../utils/prop-types';
+import { useStyleProps } from '../utils/use-style-props';
import { StyledDivider } from './Divider.style';
type Props = {
@@ -13,7 +14,7 @@ type Props = {
type NativeAttrs = Omit, keyof Props>;
-type DividerProps = Props & NativeAttrs & ElementTypeProp;
+type DividerProps = Props & NativeAttrs & ElementTypeProp & MarginProps;
const Divider = forwardRef(
(
@@ -26,6 +27,7 @@ const Divider = forwardRef(
...props,
elementType
});
+ const { styleProps, componentProps } = useStyleProps(props);
return (
(
$color={color}
$orientation={orientation}
$size={size}
- {...mergeProps(separatorProps, props)}
+ {...mergeProps(separatorProps, styleProps, componentProps)}
/>
);
}
diff --git a/src/component-library/Modal/Dialog.tsx b/src/component-library/Modal/Dialog.tsx
deleted file mode 100644
index ecd782729a..0000000000
--- a/src/component-library/Modal/Dialog.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { AriaDialogProps, useDialog } from '@react-aria/dialog';
-import { forwardRef, ReactNode } from 'react';
-
-import { XMark } from '@/assets/icons';
-
-import { useDOMRef } from '../utils/dom';
-import { StyledCloseCTA, StyledDialog } from './Modal.style';
-import { ModalContext } from './ModalContext';
-
-type Props = {
- children: ReactNode;
- align?: 'top' | 'center';
- hasMaxHeight?: boolean;
- onClose: () => void;
-};
-
-type InheritAttrs = Omit;
-
-type DialogProps = Props & InheritAttrs;
-
-const Dialog = forwardRef(
- ({ children, align = 'center', hasMaxHeight, onClose, ...props }, ref): JSX.Element | null => {
- const dialogRef = useDOMRef(ref);
-
- // Get props for the dialog and its title
- const { dialogProps, titleProps } = useDialog(props, dialogRef);
-
- const isCentered = align === 'center';
-
- return (
-
-
-
-
-
- {children}
-
-
- );
- }
-);
-
-Dialog.displayName = 'Dialog';
-
-export { Dialog };
-export type { DialogProps };
diff --git a/src/component-library/Modal/Modal.style.tsx b/src/component-library/Modal/Modal.style.tsx
index 1dc9da67af..9a222b8a7f 100644
--- a/src/component-library/Modal/Modal.style.tsx
+++ b/src/component-library/Modal/Modal.style.tsx
@@ -1,10 +1,7 @@
import styled from 'styled-components';
import { overlayCSS } from '../css/overlay';
-import { CTA } from '../CTA';
-import { Divider } from '../Divider';
-import { Flex } from '../Flex';
-import { H3 } from '../Text';
+import { Dialog, DialogBody } from '../Dialog';
import { theme } from '../theme';
import { Overflow } from '../utils/prop-types';
@@ -23,26 +20,6 @@ type StyledModalBodyProps = {
$noPadding?: boolean;
};
-type StyledUnderlayProps = {
- $isOpen: boolean;
- $isCentered?: boolean;
-};
-
-const StyledUnderlay = styled.div`
- position: fixed;
- z-index: ${theme.modal.underlay.zIndex};
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
-
- background: ${theme.modal.underlay.bg};
-
- ${({ $isOpen }) => overlayCSS($isOpen)}
- transition: ${({ $isOpen }) =>
- $isOpen ? theme.modal.underlay.transition.entering : theme.modal.underlay.transition.exiting};
-`;
-
const StyledWrapper = styled.div`
position: fixed;
top: 0;
@@ -59,8 +36,6 @@ const StyledWrapper = styled.div`
`;
const StyledModal = styled.div`
- width: 100%;
- max-width: ${theme.modal.maxWidth};
max-height: ${({ $isCentered }) => $isCentered && theme.modal.maxHeight};
margin: ${({ $isCentered }) => ($isCentered ? 0 : theme.spacing.spacing16)} ${theme.spacing.spacing6};
@@ -77,60 +52,15 @@ const StyledModal = styled.div`
outline: none;
`;
-const StyledDialog = styled.section`
- background: ${theme.colors.bgPrimary};
- border: ${theme.border.default};
- border-radius: ${theme.rounded.md};
- color: ${theme.colors.textPrimary};
-
- width: 100%;
+const StyledDialog = styled(Dialog)`
max-height: ${({ $hasMaxHeight }) => $hasMaxHeight && '560px'};
overflow: ${({ $isCentered }) => $isCentered && 'hidden'};
-
- display: flex;
- flex-direction: column;
- position: relative;
-
- outline: none;
`;
-const StyledCloseCTA = styled(CTA)`
- position: absolute;
- top: ${theme.spacing.spacing2};
- right: ${theme.spacing.spacing2};
- z-index: ${theme.modal.closeBtn.zIndex};
-`;
-
-const StyledModalHeader = styled(H3)`
- padding: ${theme.modal.header.paddingY} ${theme.modal.header.paddingRight} ${theme.modal.header.paddingY}
- ${theme.modal.header.paddingX};
- flex-shrink: 0;
-`;
-
-const StyledModalDivider = styled(Divider)`
- margin: 0 ${theme.modal.divider.marginX} ${theme.modal.divider.marginBottom};
- flex-shrink: 0;
-`;
-
-const StyledModalBody = styled(Flex)`
- flex: 1 1 auto;
+const StyledDialogBody = styled(DialogBody)`
overflow-y: ${({ $overflow }) => $overflow};
position: relative;
- padding: ${({ $noPadding }) => !$noPadding && `${theme.modal.body.paddingY} ${theme.modal.body.paddingX}`};
+ padding: ${({ $noPadding }) => $noPadding && 0};
`;
-const StyledModalFooter = styled(Flex)`
- padding: ${theme.modal.footer.paddingTop} ${theme.modal.footer.paddingX} ${theme.modal.footer.paddingBottom};
-`;
-
-export {
- StyledCloseCTA,
- StyledDialog,
- StyledModal,
- StyledModalBody,
- StyledModalDivider,
- StyledModalFooter,
- StyledModalHeader,
- StyledUnderlay,
- StyledWrapper
-};
+export { StyledDialog, StyledDialogBody, StyledModal, StyledWrapper };
diff --git a/src/component-library/Modal/Modal.tsx b/src/component-library/Modal/Modal.tsx
index 9bff5876a4..54e1723365 100644
--- a/src/component-library/Modal/Modal.tsx
+++ b/src/component-library/Modal/Modal.tsx
@@ -1,17 +1,27 @@
-import { forwardRef } from 'react';
+import { forwardRef, useRef } from 'react';
+import { DialogProps } from '../Dialog';
import { Overlay } from '../Overlay';
import { useDOMRef } from '../utils/dom';
-import { Dialog, DialogProps } from './Dialog';
+import { StyledDialog } from './Modal.style';
+import { ModalContext } from './ModalContext';
import { ModalWrapper, ModalWrapperProps } from './ModalWrapper';
+const isInteractingWithToasts = (element: Element) => {
+ const toastsContainer = document.querySelector('.Toastify');
+
+ if (!toastsContainer) return false;
+
+ return toastsContainer.contains(element);
+};
+
type Props = {
container?: Element;
hasMaxHeight?: boolean;
align?: 'top' | 'center';
};
-type InheritAttrs = Omit;
+type InheritAttrs = Omit;
type ModalProps = Props & InheritAttrs;
@@ -32,24 +42,36 @@ const Modal = forwardRef(
): JSX.Element | null => {
const domRef = useDOMRef(ref);
const { isOpen, onClose } = props;
+ const wrapperRef = useRef(null);
+
+ const isCentered = align === 'center';
+
+ // Does not allow the modal to close when clicking on toasts
+ const handleShouldCloseOnInteractOutside = (element: Element) =>
+ shouldCloseOnInteractOutside
+ ? shouldCloseOnInteractOutside?.(element) && !isInteractingWithToasts(element)
+ : !isInteractingWithToasts(element);
return (
-
-
-
- {children}
-
-
-
+
+
+
+
+ {children}
+
+
+
+
);
}
);
diff --git a/src/component-library/Modal/ModalBody.tsx b/src/component-library/Modal/ModalBody.tsx
index b888725293..7f949b254c 100644
--- a/src/component-library/Modal/ModalBody.tsx
+++ b/src/component-library/Modal/ModalBody.tsx
@@ -1,6 +1,6 @@
-import { FlexProps } from '../Flex';
+import { DialogBodyProps } from '../Dialog';
import { Overflow } from '../utils/prop-types';
-import { StyledModalBody } from './Modal.style';
+import { StyledDialogBody } from './Modal.style';
import { useModalContext } from './ModalContext';
type Props = {
@@ -8,21 +8,14 @@ type Props = {
noPadding?: boolean;
};
-type InheritAttrs = Omit;
+type InheritAttrs = Omit;
type ModalBodyProps = Props & InheritAttrs;
-const ModalBody = ({ overflow, noPadding, direction = 'column', ...props }: ModalBodyProps): JSX.Element => {
+const ModalBody = ({ overflow, noPadding, ...props }: ModalBodyProps): JSX.Element => {
const { bodyProps } = useModalContext();
- return (
-
- );
+ return ;
};
export { ModalBody };
diff --git a/src/component-library/Modal/ModalContext.tsx b/src/component-library/Modal/ModalContext.tsx
index 4c890537fe..42353cbe0e 100644
--- a/src/component-library/Modal/ModalContext.tsx
+++ b/src/component-library/Modal/ModalContext.tsx
@@ -1,10 +1,8 @@
-import { DOMAttributes } from '@react-types/shared';
import React from 'react';
import { ModalBodyProps } from './ModalBody';
interface ModalConfig {
- titleProps?: DOMAttributes;
bodyProps?: ModalBodyProps;
}
diff --git a/src/component-library/Modal/ModalDivider.tsx b/src/component-library/Modal/ModalDivider.tsx
index 7159d73208..1e9630a48e 100644
--- a/src/component-library/Modal/ModalDivider.tsx
+++ b/src/component-library/Modal/ModalDivider.tsx
@@ -1,11 +1,8 @@
-import { DividerProps } from '../Divider';
-import { StyledModalDivider } from './Modal.style';
+import { DialogDivider, DialogDividerProps } from '../Dialog';
-type ModalDividerProps = Omit;
+type ModalDividerProps = DialogDividerProps;
-const ModalDivider = (props: ModalDividerProps): JSX.Element => (
-
-);
+const ModalDivider = (props: ModalDividerProps): JSX.Element => ;
export { ModalDivider };
export type { ModalDividerProps };
diff --git a/src/component-library/Modal/ModalFooter.tsx b/src/component-library/Modal/ModalFooter.tsx
index bd66ff2c3d..655a8d5d20 100644
--- a/src/component-library/Modal/ModalFooter.tsx
+++ b/src/component-library/Modal/ModalFooter.tsx
@@ -1,12 +1,9 @@
-import { FlexProps } from '../Flex';
-import { StyledModalFooter } from './Modal.style';
+import { DialogFooter, DialogFooterProps } from '../Dialog';
-type InheritAttrs = FlexProps;
-
-type ModalFooterProps = InheritAttrs;
+type ModalFooterProps = DialogFooterProps;
const ModalFooter = ({ direction = 'column', gap = 'spacing4', ...props }: ModalFooterProps): JSX.Element => (
-
+
);
export { ModalFooter };
diff --git a/src/component-library/Modal/ModalHeader.tsx b/src/component-library/Modal/ModalHeader.tsx
index 38545726fc..decf2059e3 100644
--- a/src/component-library/Modal/ModalHeader.tsx
+++ b/src/component-library/Modal/ModalHeader.tsx
@@ -1,40 +1,12 @@
-import { mergeProps } from '@react-aria/utils';
-import { ElementType } from 'react';
+import { DialogHeader, DialogHeaderProps } from '../Dialog';
-import { TextProps } from '../Text';
-import { StyledModalHeader } from './Modal.style';
-import { useModalContext } from './ModalContext';
+type ModalHeaderProps = DialogHeaderProps;
-type Props = {
- elementType?: ElementType;
-};
-
-type InheritAttrs = Omit;
-
-type ModalHeaderProps = Props & InheritAttrs;
-
-const ModalHeader = ({
- align = 'center',
- size = 'xl',
- weight = 'semibold',
- elementType,
- children,
- ...props
-}: ModalHeaderProps): JSX.Element => {
- const { titleProps } = useModalContext();
-
- return (
-
- {children}
-
- );
-};
+const ModalHeader = ({ align = 'center', children, ...props }: ModalHeaderProps): JSX.Element => (
+
+ {children}
+
+);
export { ModalHeader };
export type { ModalHeaderProps };
diff --git a/src/component-library/Modal/ModalWrapper.tsx b/src/component-library/Modal/ModalWrapper.tsx
index 3bb7dab4c9..8ce98068e6 100644
--- a/src/component-library/Modal/ModalWrapper.tsx
+++ b/src/component-library/Modal/ModalWrapper.tsx
@@ -3,13 +3,15 @@ import { mergeProps } from '@react-aria/utils';
import { OverlayTriggerState } from '@react-stately/overlays';
import { forwardRef, ReactNode, RefObject } from 'react';
-import { StyledModal, StyledUnderlay, StyledWrapper } from './Modal.style';
+import { Underlay } from '../Overlay';
+import { StyledModal, StyledWrapper } from './Modal.style';
type Props = {
children: ReactNode;
align?: 'top' | 'center';
isOpen?: boolean;
onClose: () => void;
+ wrapperRef: RefObject;
};
type InheritAttrs = Omit;
@@ -27,6 +29,7 @@ const ModalWrapper = forwardRef(
isOpen,
shouldCloseOnInteractOutside,
shouldCloseOnBlur,
+ wrapperRef,
...props
},
ref
@@ -49,14 +52,14 @@ const ModalWrapper = forwardRef(
const isCentered = align === 'center';
return (
- <>
-
+
+
{children}
- >
+
);
}
);
diff --git a/src/component-library/Overlay/Overlay.style.tsx b/src/component-library/Overlay/Overlay.style.tsx
index 770f816067..d348e5594f 100644
--- a/src/component-library/Overlay/Overlay.style.tsx
+++ b/src/component-library/Overlay/Overlay.style.tsx
@@ -1,8 +1,31 @@
import styled from 'styled-components';
+import { overlayCSS } from '../css/overlay';
+import { theme } from '../theme';
+
+type StyledUnderlayProps = {
+ $isOpen: boolean;
+ $isTransparent: boolean;
+};
+
const StyledOverlayWrapper = styled.div`
isolation: isolate;
background: transparent;
`;
-export { StyledOverlayWrapper };
+const StyledUnderlay = styled.div`
+ position: fixed;
+ z-index: ${theme.modal.underlay.zIndex};
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+
+ background: ${({ $isTransparent }) => ($isTransparent ? 'transparent' : theme.modal.underlay.bg)};
+
+ ${({ $isOpen }) => overlayCSS($isOpen)}
+ transition: ${({ $isOpen }) =>
+ $isOpen ? theme.modal.underlay.transition.entering : theme.modal.underlay.transition.exiting};
+`;
+
+export { StyledOverlayWrapper, StyledUnderlay };
diff --git a/src/component-library/Overlay/Overlay.tsx b/src/component-library/Overlay/Overlay.tsx
index 45cafff805..3e92902704 100644
--- a/src/component-library/Overlay/Overlay.tsx
+++ b/src/component-library/Overlay/Overlay.tsx
@@ -1,11 +1,12 @@
import { Overlay as AriaOverlay } from '@react-aria/overlays';
-import { ReactNode, useCallback, useState } from 'react';
+import { ReactNode, RefObject, useCallback, useState } from 'react';
import { OpenTransition } from './OpenTransition';
import { StyledOverlayWrapper } from './Overlay.style';
type OverlayProps = {
children: ReactNode;
+ nodeRef: RefObject;
isOpen?: boolean;
container?: Element;
onEnter?: () => void;
@@ -18,6 +19,7 @@ type OverlayProps = {
const Overlay = ({
children,
+ nodeRef,
isOpen,
container,
onEnter,
@@ -64,6 +66,7 @@ const Overlay = ({
onEnter={onEnter}
onEntering={onEntering}
onEntered={handleEntered}
+ nodeRef={nodeRef}
>
{children}
diff --git a/src/component-library/Overlay/Underlay.tsx b/src/component-library/Overlay/Underlay.tsx
new file mode 100644
index 0000000000..9dfcbebc8f
--- /dev/null
+++ b/src/component-library/Overlay/Underlay.tsx
@@ -0,0 +1,19 @@
+import { HTMLAttributes } from 'react';
+
+import { StyledUnderlay } from './Overlay.style';
+
+type Props = {
+ isTransparent?: boolean;
+ isOpen?: boolean;
+};
+
+type NativeAttrs = Omit, keyof Props>;
+
+type UnderlayProps = Props & NativeAttrs;
+
+const Underlay = ({ isTransparent = false, isOpen = false, ...props }: UnderlayProps): JSX.Element => (
+
+);
+
+export { Underlay };
+export type { UnderlayProps };
diff --git a/src/component-library/Overlay/index.tsx b/src/component-library/Overlay/index.tsx
index 9c047f467d..ac912a2e2b 100644
--- a/src/component-library/Overlay/index.tsx
+++ b/src/component-library/Overlay/index.tsx
@@ -1,2 +1,4 @@
export type { OverlayProps } from './Overlay';
export { Overlay } from './Overlay';
+export type { UnderlayProps } from './Underlay';
+export { Underlay } from './Underlay';
diff --git a/src/component-library/Popover/Popover.stories.tsx b/src/component-library/Popover/Popover.stories.tsx
new file mode 100644
index 0000000000..68ef806297
--- /dev/null
+++ b/src/component-library/Popover/Popover.stories.tsx
@@ -0,0 +1,40 @@
+import { Meta, Story } from '@storybook/react';
+
+import { CTA } from '../CTA';
+import { P } from '../Text';
+import { Placement } from '../utils/prop-types';
+import { Popover, PopoverBody, PopoverContent, PopoverFooter, PopoverHeader, PopoverProps, PopoverTrigger } from '.';
+
+const Template: Story = ({ placement, ...args }) => {
+ return (
+
+
+ Open Popover
+
+
+ Popover Header
+
+
+ Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in, egestas eget
+ quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna, vel
+ scelerisque nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus
+ ac facilisis in, egestas eget quam.
+
+
+
+ Confirm
+
+
+
+ );
+};
+
+const Default = Template.bind({});
+Default.args = { placement: 'right' };
+
+export { Default };
+
+export default {
+ title: 'Overlays/Popover',
+ component: Popover
+} as Meta;
diff --git a/src/component-library/Popover/Popover.style.tsx b/src/component-library/Popover/Popover.style.tsx
new file mode 100644
index 0000000000..40db6c8b91
--- /dev/null
+++ b/src/component-library/Popover/Popover.style.tsx
@@ -0,0 +1,30 @@
+import styled from 'styled-components';
+
+import { getOverlayPlacementCSS, overlayCSS } from '../css/overlay';
+import { Placement } from '../utils/prop-types';
+
+type StyledPopoverProps = {
+ $placement?: Placement | 'center';
+ $isOpen: boolean;
+};
+
+const StyledPopover = styled.div`
+ display: inline-flex;
+ flex-direction: column;
+ box-sizing: border-box;
+
+ min-width: 32px;
+ min-height: 32px;
+
+ position: absolute;
+
+ outline: none; /* Hide focus outline */
+ box-sizing: border-box;
+
+ ${({ $isOpen }) => overlayCSS(!!$isOpen)}
+ ${({ $placement }) => $placement && getOverlayPlacementCSS($placement as any)}
+
+ transition: transform 100ms ease-in-out, opacity 100ms ease-in-out, visibility 0s linear 100ms;
+`;
+
+export { StyledPopover };
diff --git a/src/component-library/Popover/Popover.tsx b/src/component-library/Popover/Popover.tsx
new file mode 100644
index 0000000000..0d974f04af
--- /dev/null
+++ b/src/component-library/Popover/Popover.tsx
@@ -0,0 +1,54 @@
+import { useOverlayTrigger } from '@react-aria/overlays';
+import { OverlayTriggerProps, useOverlayTriggerState } from '@react-stately/overlays';
+import { ReactNode, useRef } from 'react';
+
+import { Placement } from '../utils/prop-types';
+import { PopoverContext } from './PopoverContext';
+
+type Props = {
+ children?: ReactNode;
+ placement?: Placement;
+ offset?: number;
+ crossOffset?: number;
+ /* usePopover attempts to flip popovers on the main axis */
+ /* overrides usePopover flip */
+ shouldFlip?: boolean;
+ /* Control the minimum padding required between the popover and the surrounding container. */
+ /* Affects the popover flip */
+ containerPadding?: number;
+};
+
+type InheritAttrs = Omit;
+
+type PopoverProps = Props & InheritAttrs;
+
+const Popover = ({
+ children,
+ placement,
+ offset,
+ crossOffset,
+ shouldFlip,
+ containerPadding,
+ ...props
+}: PopoverProps): JSX.Element | null => {
+ const triggerRef = useRef(null);
+ const state = useOverlayTriggerState(props);
+ const { triggerProps, overlayProps } = useOverlayTrigger({ type: 'dialog' }, state, triggerRef);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export { Popover };
+export type { PopoverProps };
diff --git a/src/component-library/Popover/PopoverBody.tsx b/src/component-library/Popover/PopoverBody.tsx
new file mode 100644
index 0000000000..d39dd0fcd0
--- /dev/null
+++ b/src/component-library/Popover/PopoverBody.tsx
@@ -0,0 +1,8 @@
+import { DialogBody, DialogBodyProps } from '../Dialog';
+
+type PopoverBodyProps = DialogBodyProps;
+
+const PopoverBody = (props: PopoverBodyProps): JSX.Element => ;
+
+export { PopoverBody };
+export type { PopoverBodyProps };
diff --git a/src/component-library/Popover/PopoverContent.tsx b/src/component-library/Popover/PopoverContent.tsx
new file mode 100644
index 0000000000..8becd5f989
--- /dev/null
+++ b/src/component-library/Popover/PopoverContent.tsx
@@ -0,0 +1,40 @@
+import { forwardRef, ReactNode, useRef } from 'react';
+
+import { Overlay } from '../Overlay';
+import { useDOMRef } from '../utils/dom';
+import { PopoverContentWrapper } from './PopoverContentWrapper';
+import { usePopoverContext } from './PopoverContext';
+
+type Props = { children?: ReactNode };
+
+type PopoverContentProps = Props;
+
+const PopoverContent = forwardRef(
+ (props, ref): JSX.Element => {
+ const { children, ...otherProps } = props;
+ const domRef = useDOMRef(ref);
+ const wrapperRef = useRef(null);
+ const { state, triggerRef, dialogProps, popoverProps } = usePopoverContext();
+
+ return (
+
+ }
+ wrapperRef={wrapperRef}
+ >
+ {children}
+
+
+ );
+ }
+);
+
+PopoverContent.displayName = 'PopoverContent';
+
+export { PopoverContent };
+export type { PopoverContentProps };
diff --git a/src/component-library/Popover/PopoverContentWrapper.tsx b/src/component-library/Popover/PopoverContentWrapper.tsx
new file mode 100644
index 0000000000..7287366a9f
--- /dev/null
+++ b/src/component-library/Popover/PopoverContentWrapper.tsx
@@ -0,0 +1,74 @@
+import { AriaPopoverProps, DismissButton, usePopover } from '@react-aria/overlays';
+import { OverlayTriggerState } from '@react-stately/overlays';
+import { DOMProps } from '@react-types/shared';
+import { forwardRef, HTMLAttributes, RefObject } from 'react';
+
+import { Dialog } from '../Dialog';
+import { Underlay } from '../Overlay';
+import { StyledPopover } from './Popover.style';
+
+type Props = {
+ state: OverlayTriggerState;
+ wrapperRef: RefObject;
+ isOpen?: boolean;
+ dialogProps?: DOMProps;
+ popoverProps?: Partial;
+};
+
+type InheritAttrs = Omit;
+
+type NativeAttrs = Omit, keyof Props & InheritAttrs>;
+
+type PopoverContentWrapperProps = Props & InheritAttrs & NativeAttrs;
+
+const PopoverContentWrapper = forwardRef(
+ (props, ref): JSX.Element | null => {
+ const {
+ children,
+ wrapperRef,
+ state,
+ isOpen,
+ className,
+ style,
+ isNonModal,
+ dialogProps,
+ popoverProps: popoverPropsProp
+ } = props;
+
+ const { popoverProps, underlayProps, placement } = usePopover(
+ {
+ ...props,
+ popoverRef: ref as RefObject,
+ ...popoverPropsProp
+ },
+ state
+ );
+
+ return (
+
+ {!isNonModal && }
+
+ {!isNonModal && }
+
+ {children}
+
+
+
+
+ );
+ }
+);
+
+PopoverContentWrapper.displayName = 'PopoverContentWrapper';
+
+export { PopoverContentWrapper };
+export type { PopoverContentWrapperProps };
diff --git a/src/component-library/Popover/PopoverContext.tsx b/src/component-library/Popover/PopoverContext.tsx
new file mode 100644
index 0000000000..37c34dc9fc
--- /dev/null
+++ b/src/component-library/Popover/PopoverContext.tsx
@@ -0,0 +1,22 @@
+import { AriaButtonProps } from '@react-aria/button';
+import { AriaPopoverProps } from '@react-aria/overlays';
+import { OverlayTriggerState } from '@react-stately/overlays';
+import { DOMProps } from '@react-types/shared';
+import React, { RefObject } from 'react';
+
+interface PopoverConfig {
+ state: OverlayTriggerState;
+ triggerRef?: RefObject;
+ triggerProps?: AriaButtonProps<'button'>;
+ dialogProps?: DOMProps;
+ popoverProps?: Partial;
+}
+
+const defaultContext = { state: { isOpen: false } as OverlayTriggerState };
+
+const PopoverContext = React.createContext(defaultContext);
+
+const usePopoverContext = (): PopoverConfig => React.useContext(PopoverContext);
+
+export { PopoverContext, usePopoverContext };
+export type { PopoverConfig };
diff --git a/src/component-library/Popover/PopoverFooter.tsx b/src/component-library/Popover/PopoverFooter.tsx
new file mode 100644
index 0000000000..83a07a5524
--- /dev/null
+++ b/src/component-library/Popover/PopoverFooter.tsx
@@ -0,0 +1,10 @@
+import { DialogFooter, DialogFooterProps } from '../Dialog';
+
+type PopoverFooterProps = DialogFooterProps;
+
+const PopoverFooter = ({ justifyContent = 'flex-end', ...props }: PopoverFooterProps): JSX.Element => (
+
+);
+
+export { PopoverFooter };
+export type { PopoverFooterProps };
diff --git a/src/component-library/Popover/PopoverHeader.tsx b/src/component-library/Popover/PopoverHeader.tsx
new file mode 100644
index 0000000000..04ea84f09e
--- /dev/null
+++ b/src/component-library/Popover/PopoverHeader.tsx
@@ -0,0 +1,12 @@
+import { DialogHeader, DialogHeaderProps } from '../Dialog';
+
+type PopoverHeaderProps = DialogHeaderProps;
+
+const PopoverHeader = ({ size = 'base', weight = 'semibold', children, ...props }: PopoverHeaderProps): JSX.Element => (
+
+ {children}
+
+);
+
+export { PopoverHeader };
+export type { PopoverHeaderProps };
diff --git a/src/component-library/Popover/PopoverTrigger.tsx b/src/component-library/Popover/PopoverTrigger.tsx
new file mode 100644
index 0000000000..812b6b0516
--- /dev/null
+++ b/src/component-library/Popover/PopoverTrigger.tsx
@@ -0,0 +1,38 @@
+import { useButton } from '@react-aria/button';
+import { mergeProps } from '@react-aria/utils';
+import React, { Children, cloneElement, ElementType, ReactNode, RefObject } from 'react';
+
+import { useDOMRef } from '../utils/dom';
+import { usePopoverContext } from './PopoverContext';
+
+type Props = {
+ children?: ReactNode;
+};
+
+type PopoverTriggerProps = Props;
+
+const PopoverTrigger = ({ children }: PopoverTriggerProps): JSX.Element => {
+ const { triggerRef, triggerProps: { onPress, ...triggerAriaProps } = {} } = usePopoverContext();
+ const ref = useDOMRef(triggerRef as RefObject);
+
+ // MEMO: Ensure tooltip has only one child node
+ const child = Children.only(children) as React.ReactElement & {
+ ref?: React.Ref;
+ };
+
+ const elementType = ref.current?.tagName.toLowerCase() as ElementType;
+
+ const { buttonProps } = useButton({ onPress, elementType, isDisabled: elementType === 'button' } || {}, ref);
+
+ const triggerProps =
+ elementType === 'button'
+ ? mergeProps(child.props, triggerAriaProps, { onPress })
+ : mergeProps(child.props, triggerAriaProps, buttonProps);
+
+ const trigger = cloneElement(child, mergeProps(triggerProps, { ref }));
+
+ return trigger;
+};
+
+export { PopoverTrigger };
+export type { PopoverTriggerProps };
diff --git a/src/component-library/Popover/index.tsx b/src/component-library/Popover/index.tsx
new file mode 100644
index 0000000000..a69a739789
--- /dev/null
+++ b/src/component-library/Popover/index.tsx
@@ -0,0 +1,12 @@
+export type { PopoverProps } from './Popover';
+export { Popover } from './Popover';
+export type { PopoverBodyProps } from './PopoverBody';
+export { PopoverBody } from './PopoverBody';
+export type { PopoverContentProps } from './PopoverContent';
+export { PopoverContent } from './PopoverContent';
+export type { PopoverFooterProps } from './PopoverFooter';
+export { PopoverFooter } from './PopoverFooter';
+export type { PopoverHeaderProps } from './PopoverHeader';
+export { PopoverHeader } from './PopoverHeader';
+export type { PopoverTriggerProps } from './PopoverTrigger';
+export { PopoverTrigger } from './PopoverTrigger';
diff --git a/src/component-library/ProgressBar/ProgressBar.stories.tsx b/src/component-library/ProgressBar/ProgressBar.stories.tsx
new file mode 100644
index 0000000000..d0047ef340
--- /dev/null
+++ b/src/component-library/ProgressBar/ProgressBar.stories.tsx
@@ -0,0 +1,18 @@
+import { Meta, Story } from '@storybook/react';
+
+import { ProgressBar, ProgressBarProps } from '.';
+
+const Template: Story = (args) => ;
+
+const Default = Template.bind({});
+Default.args = {
+ value: 20,
+ label: 'Loading...'
+};
+
+export { Default };
+
+export default {
+ title: 'Elements/ProgressBar',
+ component: ProgressBar
+} as Meta;
diff --git a/src/component-library/ProgressBar/ProgressBar.style.tsx b/src/component-library/ProgressBar/ProgressBar.style.tsx
new file mode 100644
index 0000000000..feeff94120
--- /dev/null
+++ b/src/component-library/ProgressBar/ProgressBar.style.tsx
@@ -0,0 +1,27 @@
+import styled from 'styled-components';
+
+import { theme } from '../theme';
+import { ProgressBarColors } from '../utils/prop-types';
+
+type StyledFillProps = {
+ $color: ProgressBarColors;
+};
+
+const StyledTrack = styled.div`
+ overflow: hidden;
+ z-index: 1;
+ width: 100%;
+ min-width: ${theme.spacing.spacing6};
+ background-color: ${theme.progressBar.bg};
+ height: 1px;
+`;
+
+const StyledFill = styled.div`
+ background-color: ${({ $color }) => ($color === 'red' ? theme.alert.status.error : theme.colors.textSecondary)};
+ height: 1px;
+ border: none;
+ transition: width ${theme.transition.duration.duration100}ms;
+ will-change: width;
+`;
+
+export { StyledFill, StyledTrack };
diff --git a/src/component-library/ProgressBar/ProgressBar.tsx b/src/component-library/ProgressBar/ProgressBar.tsx
new file mode 100644
index 0000000000..a7eaf508ff
--- /dev/null
+++ b/src/component-library/ProgressBar/ProgressBar.tsx
@@ -0,0 +1,51 @@
+import { AriaProgressBarProps, useProgressBar } from '@react-aria/progress';
+import { CSSProperties } from 'react';
+
+import { Flex, FlexProps } from '../Flex';
+import { Span } from '../Text';
+import { ProgressBarColors } from '../utils/prop-types';
+import { StyledFill, StyledTrack } from './ProgressBar.style';
+
+type Props = { color?: ProgressBarColors; showValueLabel?: boolean };
+
+type AriaAttrs = Omit;
+
+type InheritAttrs = Omit;
+
+type ProgressBarProps = Props & InheritAttrs & AriaAttrs;
+
+const ProgressBar = (props: ProgressBarProps): JSX.Element => {
+ const { progressBarProps, labelProps } = useProgressBar(props);
+
+ const {
+ value = 0,
+ minValue = 0,
+ maxValue = 100,
+ color = 'default',
+ showValueLabel,
+ label,
+ className,
+ style,
+ hidden
+ } = props;
+
+ const percentage = (value - minValue) / (maxValue - minValue);
+ const barStyle: CSSProperties = { width: `${Math.round(percentage * 100)}%` };
+
+ return (
+
+ {(label || showValueLabel) && (
+
+ {label && {label} }
+ {showValueLabel && {progressBarProps['aria-valuetext']} }
+
+ )}
+
+
+
+
+ );
+};
+
+export { ProgressBar };
+export type { ProgressBarProps };
diff --git a/src/component-library/ProgressBar/index.tsx b/src/component-library/ProgressBar/index.tsx
new file mode 100644
index 0000000000..098ea9d4bb
--- /dev/null
+++ b/src/component-library/ProgressBar/index.tsx
@@ -0,0 +1,2 @@
+export type { ProgressBarProps } from './ProgressBar';
+export { ProgressBar } from './ProgressBar';
diff --git a/src/component-library/Select/Select.style.tsx b/src/component-library/Select/Select.style.tsx
index d79d62f111..03a766d386 100644
--- a/src/component-library/Select/Select.style.tsx
+++ b/src/component-library/Select/Select.style.tsx
@@ -66,7 +66,8 @@ const StyledTriggerValue = styled(Span)`
const StyledList = styled(List)`
overflow: auto;
- padding: 0 ${theme.modal.body.paddingX} ${theme.modal.body.paddingY} ${theme.modal.body.paddingX};
+ padding: 0 ${theme.dialog.medium.body.paddingX} ${theme.dialog.medium.body.paddingY}
+ ${theme.dialog.medium.body.paddingX};
`;
const StyledChevronDown = styled(ChevronDown)`
diff --git a/src/component-library/Text/style.tsx b/src/component-library/Text/style.tsx
index abf26946fa..2299c8592c 100644
--- a/src/component-library/Text/style.tsx
+++ b/src/component-library/Text/style.tsx
@@ -1,4 +1,4 @@
-import styled from 'styled-components';
+import styled, { css } from 'styled-components';
import { theme } from '../theme';
import { Colors, FontSize, FontWeight, NormalAlignments } from '../utils/prop-types';
@@ -9,6 +9,7 @@ type StyledTextProps = {
$size?: FontSize;
$align?: NormalAlignments;
$weight?: FontWeight;
+ $rows?: number;
};
const Text = styled.p`
@@ -17,6 +18,20 @@ const Text = styled.p`
line-height: ${({ $size }) => resolveHeight($size)};
font-weight: ${({ $weight }) => $weight && theme.fontWeight[$weight]};
text-align: ${({ $align }) => $align};
+
+ ${({ $rows }) => {
+ return (
+ $rows &&
+ css`
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ line-clamp: ${$rows};
+ -webkit-line-clamp: ${$rows};
+ -webkit-box-orient: vertical;
+ `
+ );
+ }}
`;
export { Text };
diff --git a/src/component-library/Text/types.ts b/src/component-library/Text/types.ts
index 812bcc7def..56f047f723 100644
--- a/src/component-library/Text/types.ts
+++ b/src/component-library/Text/types.ts
@@ -7,6 +7,7 @@ type Props = {
size?: FontSize;
align?: NormalAlignments;
weight?: FontWeight;
+ rows?: number;
};
type NativeAttrs = Omit, keyof Props>;
diff --git a/src/component-library/Text/utils.ts b/src/component-library/Text/utils.ts
index ea9034044d..fa16b5d428 100644
--- a/src/component-library/Text/utils.ts
+++ b/src/component-library/Text/utils.ts
@@ -6,13 +6,15 @@ const mapTextProps = ({
size,
align,
weight,
+ rows,
...props
-}: T): Omit & StyledTextProps => ({
+}: T): Omit & StyledTextProps => ({
...props,
$color: color,
$size: size,
$weight: weight,
- $align: align
+ $align: align,
+ $rows: rows
});
export { mapTextProps };
diff --git a/src/component-library/TextLink/TextLink.style.tsx b/src/component-library/TextLink/TextLink.style.tsx
index 4fdb3ccce7..e3bdf78580 100644
--- a/src/component-library/TextLink/TextLink.style.tsx
+++ b/src/component-library/TextLink/TextLink.style.tsx
@@ -1,15 +1,25 @@
import styled from 'styled-components';
-import { Colors } from '../utils/prop-types';
-import { resolveColor } from '../utils/theme';
+import { ArrowTopRightOnSquare } from '@/assets/icons';
+
+import { theme } from '../theme';
+import { Colors, FontSize, FontWeight } from '../utils/prop-types';
+import { resolveColor, resolveHeight } from '../utils/theme';
type BaseTextLinkProps = {
$color?: Colors;
$underlined?: boolean;
+ $size?: FontSize;
+ $weight?: FontWeight;
};
const BaseTextLink = styled.a`
+ display: inline-flex;
+ align-items: center;
color: ${({ $color }) => resolveColor($color)};
+ font-size: ${({ $size }) => $size && theme.text[$size]};
+ line-height: ${({ $size }) => resolveHeight($size)};
+ font-weight: ${({ $weight }) => $weight && theme.fontWeight[$weight]};
text-decoration: ${(props) => props.$underlined && 'underline'};
&:hover,
@@ -18,4 +28,11 @@ const BaseTextLink = styled.a`
}
`;
-export { BaseTextLink };
+const StyledIcon = styled(ArrowTopRightOnSquare)`
+ margin-left: ${theme.spacing.spacing2};
+ width: 1em;
+ height: 1em;
+ color: inherit;
+`;
+
+export { BaseTextLink, StyledIcon };
diff --git a/src/component-library/TextLink/TextLink.tsx b/src/component-library/TextLink/TextLink.tsx
index 25fec25ee1..7bb6cb05ab 100644
--- a/src/component-library/TextLink/TextLink.tsx
+++ b/src/component-library/TextLink/TextLink.tsx
@@ -1,13 +1,16 @@
import { forwardRef } from 'react';
import { Link, LinkProps } from 'react-router-dom';
-import { Colors } from '../utils/prop-types';
-import { BaseTextLink } from './TextLink.style';
+import { Colors, FontSize, FontWeight } from '../utils/prop-types';
+import { BaseTextLink, StyledIcon } from './TextLink.style';
type Props = {
color?: Colors;
external?: boolean;
underlined?: boolean;
+ size?: FontSize;
+ weight?: FontWeight;
+ icon?: boolean;
};
type NativeAttrs = Omit;
@@ -16,12 +19,26 @@ type TextLinkProps = Props & NativeAttrs;
// TODO: merge this with CTALink
const TextLink = forwardRef(
- ({ color = 'primary', external, to, underlined, ...props }, ref): JSX.Element => {
+ ({ color = 'primary', external, to, underlined, size, weight, icon, children, ...props }, ref): JSX.Element => {
const linkProps: TextLinkProps = external
? { to: { pathname: to as string }, target: '_blank', rel: 'noreferrer' }
: { to };
- return ;
+ return (
+
+ {children}
+ {icon && }
+
+ );
}
);
diff --git a/src/component-library/TokenInput/TokenInput.style.tsx b/src/component-library/TokenInput/TokenInput.style.tsx
index 3331c5b6d2..5ba46b0c61 100644
--- a/src/component-library/TokenInput/TokenInput.style.tsx
+++ b/src/component-library/TokenInput/TokenInput.style.tsx
@@ -89,11 +89,11 @@ const StyledListItemLabel = styled(Span)`
const StyledList = styled(List)`
overflow: auto;
- padding: 0 ${theme.modal.body.paddingX} ${theme.modal.body.paddingY} ${theme.modal.body.paddingX};
+ padding: 0 ${theme.spacing.spacing4} ${theme.spacing.spacing2} ${theme.spacing.spacing4};
`;
const StyledListHeader = styled(Flex)`
- padding: ${theme.modal.body.paddingY} ${theme.modal.body.paddingX};
+ padding: ${theme.spacing.spacing2} ${theme.spacing.spacing4};
`;
const StyledListTokenWrapper = styled(Flex)`
diff --git a/src/component-library/Tooltip/Tooltip.tsx b/src/component-library/Tooltip/Tooltip.tsx
index 6077f95860..7e83274bb8 100644
--- a/src/component-library/Tooltip/Tooltip.tsx
+++ b/src/component-library/Tooltip/Tooltip.tsx
@@ -6,14 +6,13 @@ import { AriaTooltipProps, TooltipTriggerProps as StatelyTooltipTriggerProps } f
import React, { Children, cloneElement, HTMLAttributes, ReactElement, ReactNode, useRef } from 'react';
import { Span } from '../Text';
-import { theme } from '../theme';
import { Placement } from '../utils/prop-types';
import { StyledTooltip, StyledTooltipLabel, StyledTooltipTip } from './Tooltip.style';
// MEMO: https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/tooltip/src/TooltipTrigger.tsx#L22
const DEFAULT_OFFSET = -1;
const DEFAULT_CROSS_OFFSET = 0;
-const DEFAULT_DELAY = Number(theme.transition.default);
+const DEFAULT_DELAY = 500;
type Props = {
label?: ReactNode;
diff --git a/src/component-library/index.tsx b/src/component-library/index.tsx
index d385b075d1..2a09fe612f 100644
--- a/src/component-library/index.tsx
+++ b/src/component-library/index.tsx
@@ -32,6 +32,17 @@ export type { ModalBodyProps, ModalDividerProps, ModalFooterProps, ModalHeaderPr
export { Modal, ModalBody, ModalDivider, ModalFooter, ModalHeader } from './Modal';
export type { NumberInputProps } from './NumberInput';
export { NumberInput } from './NumberInput';
+export type {
+ PopoverBodyProps,
+ PopoverContentProps,
+ PopoverFooterProps,
+ PopoverHeaderProps,
+ PopoverProps,
+ PopoverTriggerProps
+} from './Popover';
+export { Popover, PopoverBody, PopoverContent, PopoverFooter, PopoverHeader, PopoverTrigger } from './Popover';
+export type { ProgressBarProps } from './ProgressBar';
+export { ProgressBar } from './ProgressBar';
export type { SelectProps } from './Select';
export { Item, Select } from './Select';
export type { StackProps } from './Stack';
diff --git a/src/component-library/theme/theme.base.css b/src/component-library/theme/theme.base.css
index 532086f98e..801f09e550 100644
--- a/src/component-library/theme/theme.base.css
+++ b/src/component-library/theme/theme.base.css
@@ -98,6 +98,7 @@
--spacing-12: 3rem;
--spacing-14: 3.5rem;
--spacing-16: 4rem;
+ --spacing-18: 4.5rem;
--spacing-28: 7rem;
--rounded-sm: 2px;
diff --git a/src/component-library/theme/theme.ts b/src/component-library/theme/theme.ts
index c7a21c8791..45f079bb66 100644
--- a/src/component-library/theme/theme.ts
+++ b/src/component-library/theme/theme.ts
@@ -16,7 +16,10 @@ const theme = {
textSecondary: 'var(--colors-text-secondary)',
textTertiary: 'var(--colors-text-tertiary)',
bgPrimary: 'var(--colors-bg-primary)',
- warn: `var(--colors-shared-red)`
+ warn: `var(--colors-shared-red)`,
+ error: 'var(--colors-error)',
+ warning: 'var(--colors-warning)',
+ success: 'var(--colors-success-darker)'
},
font: {
primary: 'var(--fonts-primary)'
@@ -60,6 +63,7 @@ const theme = {
spacing12: 'var(--spacing-12)',
spacing14: 'var(--spacing-14)',
spacing16: 'var(--spacing-16)',
+ spacing18: 'var(--spacing-18)',
spacing28: 'var(--spacing-28)'
},
rounded: {
@@ -320,6 +324,9 @@ const theme = {
}
}
},
+ progressBar: {
+ bg: 'var(--colors-border)'
+ },
spinner: {
determinate: {
color: 'var(--colors-cta-primary)',
@@ -381,8 +388,79 @@ const theme = {
width: '5.625rem'
}
},
+ dialog: {
+ small: {
+ width: '400px',
+ header: {
+ paddingTop: 'var(--spacing-4)',
+ paddingBottom: 'var(--spacing-2)',
+ paddingX: 'var(--spacing-4)',
+ padding: 'var(--spacing-4) var(--spacing-8) var(--spacing-2) var(--spacing-4)'
+ },
+ divider: {
+ marginX: 'var(--spacing-4)',
+ marginBottom: 'var(--spacing-1)'
+ },
+ body: {
+ paddingY: 'var(--spacing-2)',
+ paddingX: 'var(--spacing-4)'
+ },
+ footer: {
+ paddingTop: 'var(--spacing-1)',
+ paddingBottom: 'var(--spacing-4)',
+ paddingX: 'var(--spacing-4)',
+ padding: 'var(--spacing-1) var(--spacing-4) var(--spacing-4)'
+ }
+ },
+ medium: {
+ width: '32rem',
+ header: {
+ paddingY: 'var(--spacing-4)',
+ paddingX: 'var(--spacing-6)',
+ padding: 'var(--spacing-4) var(--spacing-8) var(--spacing-4) var(--spacing-6)'
+ },
+ divider: {
+ marginX: 'var(--spacing-6)',
+ marginBottom: 'var(--spacing-2)'
+ },
+ body: {
+ paddingY: 'var(--spacing-3)',
+ paddingX: 'var(--spacing-6)'
+ },
+ footer: {
+ paddingTop: 'var(--spacing-4)',
+ paddingBottom: 'var(--spacing-6)',
+ paddingX: 'var(--spacing-6)',
+ padding: 'var(--spacing-4) var(--spacing-6) var(--spacing-6)'
+ }
+ },
+ large: {
+ width: '32rem',
+ header: {
+ paddingY: 'var(--spacing-4)',
+ paddingX: 'var(--spacing-6)',
+ padding: 'var(--spacing-4) var(--spacing-8) var(--spacing-4) var(--spacing-6)'
+ },
+ divider: {
+ marginX: 'var(--spacing-6)',
+ marginBottom: 'var(--spacing-2)'
+ },
+ body: {
+ paddingY: 'var(--spacing-3)',
+ paddingX: 'var(--spacing-6)'
+ },
+ footer: {
+ paddingTop: 'var(--spacing-4)',
+ paddingBottom: 'var(--spacing-6)',
+ paddingX: 'var(--spacing-6)',
+ padding: 'var(--spacing-4) var(--spacing-6) var(--spacing-6)'
+ }
+ },
+ closeBtn: {
+ zIndex: 100
+ }
+ },
modal: {
- maxWidth: '32rem',
maxHeight: 'calc(100vh - var(--spacing-12))',
// TODO: z-index needs to be higher
zIndex: 2,
@@ -394,27 +472,6 @@ const theme = {
exiting: 'opacity .1s cubic-bezier(0.5,0,1,1), visibility 0s linear .1s'
}
},
- header: {
- paddingY: 'var(--spacing-4)',
- paddingX: 'var(--spacing-6)',
- paddingRight: 'var(--spacing-8)'
- },
- divider: {
- marginX: 'var(--spacing-6)',
- marginBottom: 'var(--spacing-2)'
- },
- body: {
- paddingY: 'var(--spacing-3)',
- paddingX: 'var(--spacing-6)'
- },
- footer: {
- paddingTop: 'var(--spacing-4)',
- paddingBottom: 'var(--spacing-6)',
- paddingX: 'var(--spacing-6)'
- },
- closeBtn: {
- zIndex: 100
- },
transition: {
entering: 'transform .15s cubic-bezier(0,0,0.4,1) .1s, opacity .15s cubic-bezier(0,0,0.4,1)',
exiting: 'opacity .1s cubic-bezier(0.5,0,1,1), visibility 0s linear, transform 0s linear .1s'
diff --git a/src/component-library/utils/prop-types.ts b/src/component-library/utils/prop-types.ts
index ee0db47dc0..2b5d4b629d 100644
--- a/src/component-library/utils/prop-types.ts
+++ b/src/component-library/utils/prop-types.ts
@@ -12,7 +12,8 @@ export const status = tuple('error', 'warning', 'success');
export const sizes = tuple('small', 'medium', 'large');
-export const colors = tuple('primary', 'secondary', 'tertiary');
+// TODO: add info
+export const colors = tuple('primary', 'secondary', 'tertiary', 'success', 'warning', 'error');
export const justifyContent = tuple(
'flex-start',
@@ -105,3 +106,5 @@ export type IconSize = keyof typeof theme.icon.sizes;
export type Overflow = 'auto' | 'hidden' | 'scroll' | 'visible' | 'inherit';
export type BreakPoints = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+
+export type ProgressBarColors = 'default' | 'red';
diff --git a/src/component-library/utils/theme.ts b/src/component-library/utils/theme.ts
index d9ff873a53..7f597b8379 100644
--- a/src/component-library/utils/theme.ts
+++ b/src/component-library/utils/theme.ts
@@ -9,6 +9,12 @@ const resolveColor = (color: Colors | undefined): string => {
return theme.colors.textSecondary;
case 'tertiary':
return theme.colors.textTertiary;
+ case 'success':
+ return theme.colors.success;
+ case 'warning':
+ return theme.colors.warning;
+ case 'error':
+ return theme.colors.error;
default:
return theme.colors.textPrimary;
}
diff --git a/src/components/AccountSelect/AccountSelect.style.tsx b/src/components/AccountSelect/AccountSelect.style.tsx
index 850c26e0fc..ea962fc8e0 100644
--- a/src/components/AccountSelect/AccountSelect.style.tsx
+++ b/src/components/AccountSelect/AccountSelect.style.tsx
@@ -49,7 +49,8 @@ const StyledAccountLabelName = styled(StyledAccountLabelAddress)
Date: Wed, 31 May 2023 09:37:50 +0100
Subject: [PATCH 021/220] fix: Dialog, Modal and Popover (#1245)
---
src/component-library/Dialog/Dialog.style.tsx | 1 +
src/component-library/Modal/Modal.style.tsx | 1 +
src/component-library/Popover/Popover.style.tsx | 6 ++++--
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/component-library/Dialog/Dialog.style.tsx b/src/component-library/Dialog/Dialog.style.tsx
index 5f482bc6c5..59c0278559 100644
--- a/src/component-library/Dialog/Dialog.style.tsx
+++ b/src/component-library/Dialog/Dialog.style.tsx
@@ -16,6 +16,7 @@ const StyledDialog = styled.section`
border: ${theme.border.default};
border-radius: ${theme.rounded.md};
color: ${theme.colors.textPrimary};
+ max-width: 100%;
width: ${({ $size }) => theme.dialog[$size].width};
display: flex;
flex-direction: column;
diff --git a/src/component-library/Modal/Modal.style.tsx b/src/component-library/Modal/Modal.style.tsx
index 9a222b8a7f..250dad20f8 100644
--- a/src/component-library/Modal/Modal.style.tsx
+++ b/src/component-library/Modal/Modal.style.tsx
@@ -36,6 +36,7 @@ const StyledWrapper = styled.div`
`;
const StyledModal = styled.div`
+ max-width: calc(100% - ${theme.spacing.spacing12});
max-height: ${({ $isCentered }) => $isCentered && theme.modal.maxHeight};
margin: ${({ $isCentered }) => ($isCentered ? 0 : theme.spacing.spacing16)} ${theme.spacing.spacing6};
diff --git a/src/component-library/Popover/Popover.style.tsx b/src/component-library/Popover/Popover.style.tsx
index 40db6c8b91..26fe2d5f73 100644
--- a/src/component-library/Popover/Popover.style.tsx
+++ b/src/component-library/Popover/Popover.style.tsx
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import { getOverlayPlacementCSS, overlayCSS } from '../css/overlay';
+import { theme } from '../theme';
import { Placement } from '../utils/prop-types';
type StyledPopoverProps = {
@@ -13,8 +14,9 @@ const StyledPopover = styled.div`
flex-direction: column;
box-sizing: border-box;
- min-width: 32px;
- min-height: 32px;
+ min-width: ${theme.spacing.spacing8};
+ min-height: ${theme.spacing.spacing8};
+ max-width: calc(100% - ${theme.spacing.spacing8});
position: absolute;
From c845c54f6d550ccaf038a384cbe3b7c9e628330e Mon Sep 17 00:00:00 2001
From: tomjeatt <40243778+tomjeatt@users.noreply.github.com>
Date: Wed, 31 May 2023 11:28:26 +0100
Subject: [PATCH 022/220] chore: rename strategies feature (#1247)
---
.env.dev | 2 +-
src/App.tsx | 10 +++---
src/assets/locales/en/translation.json | 4 +--
src/lib/form/schemas/earn-strategy.ts | 21 -----------
src/lib/form/schemas/index.ts | 2 +-
src/lib/form/schemas/strategy.ts | 21 +++++++++++
src/pages/EarnStrategies/EarnStrategies.tsx | 21 -----------
.../EarnStrategyDepositForm/index.ts | 1 -
.../EarnStrategyWithdrawalForm/index.ts | 1 -
.../components/EarnStrategyForm/index.ts | 1 -
src/pages/EarnStrategies/components/index.ts | 1 -
src/pages/EarnStrategies/index.tsx | 3 --
src/pages/EarnStrategies/types/form.ts | 18 ----------
.../Strategies.style.tsx} | 4 +--
src/pages/Strategies/Strategies.tsx | 21 +++++++++++
.../StrategyDepositForm.tsx} | 28 +++++++--------
.../StrategyForm/StrategyDepositForm/index.ts | 1 +
.../StrategyForm/StrategyForm.style.tsx} | 6 ++--
.../components/StrategyForm/StrategyForm.tsx} | 30 ++++++++--------
.../StrategyForm/StrategyFormFees.tsx} | 8 ++---
.../StrategyWithdrawalForm.tsx} | 36 +++++++++----------
.../StrategyWithdrawalForm/index.ts | 1 +
.../components/StrategyForm/index.ts | 1 +
src/pages/Strategies/components/index.ts | 1 +
src/pages/Strategies/index.tsx | 3 ++
src/pages/Strategies/types/form.ts | 13 +++++++
.../SidebarContent/Navigation/index.tsx | 17 +++------
src/utils/constants/links.ts | 2 +-
src/utils/hooks/use-feature-flag.ts | 4 +--
29 files changed, 135 insertions(+), 147 deletions(-)
delete mode 100644 src/lib/form/schemas/earn-strategy.ts
create mode 100644 src/lib/form/schemas/strategy.ts
delete mode 100644 src/pages/EarnStrategies/EarnStrategies.tsx
delete mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts
delete mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts
delete mode 100644 src/pages/EarnStrategies/components/EarnStrategyForm/index.ts
delete mode 100644 src/pages/EarnStrategies/components/index.ts
delete mode 100644 src/pages/EarnStrategies/index.tsx
delete mode 100644 src/pages/EarnStrategies/types/form.ts
rename src/pages/{EarnStrategies/EarnStrategies.style.tsx => Strategies/Strategies.style.tsx} (74%)
create mode 100644 src/pages/Strategies/Strategies.tsx
rename src/pages/{EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx => Strategies/components/StrategyForm/StrategyDepositForm/StrategyDepositForm.tsx} (69%)
create mode 100644 src/pages/Strategies/components/StrategyForm/StrategyDepositForm/index.ts
rename src/pages/{EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx => Strategies/components/StrategyForm/StrategyForm.style.tsx} (79%)
rename src/pages/{EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx => Strategies/components/StrategyForm/StrategyForm.tsx} (56%)
rename src/pages/{EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx => Strategies/components/StrategyForm/StrategyFormFees.tsx} (81%)
rename src/pages/{EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx => Strategies/components/StrategyForm/StrategyWithdrawalForm/StrategyWithdrawalForm.tsx} (73%)
create mode 100644 src/pages/Strategies/components/StrategyForm/StrategyWithdrawalForm/index.ts
create mode 100644 src/pages/Strategies/components/StrategyForm/index.ts
create mode 100644 src/pages/Strategies/components/index.ts
create mode 100644 src/pages/Strategies/index.tsx
create mode 100644 src/pages/Strategies/types/form.ts
diff --git a/.env.dev b/.env.dev
index 95c64b470e..5c4633047e 100644
--- a/.env.dev
+++ b/.env.dev
@@ -4,7 +4,7 @@ REACT_APP_FEATURE_FLAG_LENDING=enabled
REACT_APP_FEATURE_FLAG_AMM=enabled
REACT_APP_FEATURE_FLAG_WALLET=enabled
REACT_APP_FEATURE_FLAG_BANXA=enabled
-REACT_APP_FEATURE_FLAG_EARN_STRATEGIES=enabled
+REACT_APP_FEATURE_FLAG_STRATEGIES=enabled
/* DEVELOPMENT */
diff --git a/src/App.tsx b/src/App.tsx
index 463f6f1528..a94a8a4eb2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -26,7 +26,7 @@ import TestnetBanner from './legacy-components/TestnetBanner';
import { FeatureFlags, useFeatureFlag } from './utils/hooks/use-feature-flag';
const Bridge = React.lazy(() => import(/* webpackChunkName: 'bridge' */ '@/pages/Bridge'));
-const EarnStrategies = React.lazy(() => import(/* webpackChunkName: 'earn-strategies' */ '@/pages/EarnStrategies'));
+const Strategies = React.lazy(() => import(/* webpackChunkName: 'strategies' */ '@/pages/Strategies'));
const Transfer = React.lazy(() => import(/* webpackChunkName: 'transfer' */ '@/pages/Transfer'));
const Transactions = React.lazy(() => import(/* webpackChunkName: 'transactions' */ '@/pages/Transactions'));
const TX = React.lazy(() => import(/* webpackChunkName: 'tx' */ '@/pages/TX'));
@@ -51,7 +51,7 @@ const App = (): JSX.Element => {
const isLendingEnabled = useFeatureFlag(FeatureFlags.LENDING);
const isAMMEnabled = useFeatureFlag(FeatureFlags.AMM);
const isWalletEnabled = useFeatureFlag(FeatureFlags.WALLET);
- const isEarnStrategiesEnabled = useFeatureFlag(FeatureFlags.EARN_STRATEGIES);
+ const isStrategiesEnabled = useFeatureFlag(FeatureFlags.STRATEGIES);
// Loads the connection to the faucet - only for testnet purposes
const loadFaucet = React.useCallback(async (): Promise => {
@@ -214,9 +214,9 @@ const App = (): JSX.Element => {
)}
- {isEarnStrategiesEnabled && (
-
-
+ {isStrategiesEnabled && (
+
+
)}
diff --git a/src/assets/locales/en/translation.json b/src/assets/locales/en/translation.json
index defab36d41..9e03c16050 100644
--- a/src/assets/locales/en/translation.json
+++ b/src/assets/locales/en/translation.json
@@ -75,7 +75,7 @@
"issue": "Issue",
"redeem": "Redeem",
"nav_bridge": "Bridge",
- "nav_earn_strategies": "Earn Strategies",
+ "nav_strategies": "Strategies",
"nav_transfer": "Transfer",
"nav_lending": "Lending",
"nav_swap": "Swap",
@@ -633,7 +633,7 @@
"available_to_stake": "Available to stake",
"voting_power_governance": "Voting Power {{token}}"
},
- "earn_strategy": {
+ "strategy": {
"withdraw_rewards_in_wrapped": "Withdraw rewards in {{wrappedCurrencySymbol}}:",
"update_position": "Update position"
}
diff --git a/src/lib/form/schemas/earn-strategy.ts b/src/lib/form/schemas/earn-strategy.ts
deleted file mode 100644
index 75dd1f1b15..0000000000
--- a/src/lib/form/schemas/earn-strategy.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { EarnStrategyFormType } from '@/pages/EarnStrategies/types/form';
-
-import yup, { MaxAmountValidationParams, MinAmountValidationParams } from '../yup.custom';
-
-type EarnStrategyValidationParams = MaxAmountValidationParams & MinAmountValidationParams;
-
-const earnStrategySchema = (
- earnStrategyFormType: EarnStrategyFormType,
- params: EarnStrategyValidationParams
-): yup.ObjectSchema => {
- return yup.object().shape({
- [earnStrategyFormType]: yup
- .string()
- .requiredAmount(earnStrategyFormType)
- .maxAmount(params)
- .minAmount(params, earnStrategyFormType)
- });
-};
-
-export { earnStrategySchema };
-export type { EarnStrategyValidationParams };
diff --git a/src/lib/form/schemas/index.ts b/src/lib/form/schemas/index.ts
index 87a172bab2..fac035a489 100644
--- a/src/lib/form/schemas/index.ts
+++ b/src/lib/form/schemas/index.ts
@@ -5,9 +5,9 @@ export type {
WithdrawLiquidityPoolValidationParams
} from './amm';
export { depositLiquidityPoolSchema, WITHDRAW_LIQUIDITY_POOL_FIELD, withdrawLiquidityPoolSchema } from './amm';
-export { earnStrategySchema } from './earn-strategy';
export type { LoanFormData, LoanValidationParams } from './loans';
export { loanSchema } from './loans';
+export { StrategySchema } from './strategy';
export type { SwapFormData, SwapValidationParams } from './swap';
export {
SWAP_INPUT_AMOUNT_FIELD,
diff --git a/src/lib/form/schemas/strategy.ts b/src/lib/form/schemas/strategy.ts
new file mode 100644
index 0000000000..66a38c8ce0
--- /dev/null
+++ b/src/lib/form/schemas/strategy.ts
@@ -0,0 +1,21 @@
+import { StrategyFormType } from '@/pages/Strategies/types/form';
+
+import yup, { MaxAmountValidationParams, MinAmountValidationParams } from '../yup.custom';
+
+type StrategyValidationParams = MaxAmountValidationParams & MinAmountValidationParams;
+
+const StrategySchema = (
+ StrategyFormType: StrategyFormType,
+ params: StrategyValidationParams
+): yup.ObjectSchema => {
+ return yup.object().shape({
+ [StrategyFormType]: yup
+ .string()
+ .requiredAmount(StrategyFormType)
+ .maxAmount(params)
+ .minAmount(params, StrategyFormType)
+ });
+};
+
+export { StrategySchema };
+export type { StrategyValidationParams };
diff --git a/src/pages/EarnStrategies/EarnStrategies.tsx b/src/pages/EarnStrategies/EarnStrategies.tsx
deleted file mode 100644
index bd1eac8fdc..0000000000
--- a/src/pages/EarnStrategies/EarnStrategies.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { withErrorBoundary } from 'react-error-boundary';
-
-import ErrorFallback from '@/legacy-components/ErrorFallback';
-
-import { EarnStrategyForm } from './components/EarnStrategyForm';
-import { StyledEarnStrategiesLayout } from './EarnStrategies.style';
-
-const EarnStrategies = (): JSX.Element => {
- return (
-
-
-
- );
-};
-
-export default withErrorBoundary(EarnStrategies, {
- FallbackComponent: ErrorFallback,
- onReset: () => {
- window.location.reload();
- }
-});
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts
deleted file mode 100644
index aaedb7d4bb..0000000000
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { EarnStrategyDepositForm } from './EarnStrategyDepositForm';
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts b/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts
deleted file mode 100644
index aa5c13a062..0000000000
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { EarnStrategyWithdrawalForm } from './EarnStrategyWithdrawalForm';
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/index.ts b/src/pages/EarnStrategies/components/EarnStrategyForm/index.ts
deleted file mode 100644
index dd8d107bb2..0000000000
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { EarnStrategyForm } from './EarnStrategyForm';
diff --git a/src/pages/EarnStrategies/components/index.ts b/src/pages/EarnStrategies/components/index.ts
deleted file mode 100644
index dd8d107bb2..0000000000
--- a/src/pages/EarnStrategies/components/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { EarnStrategyForm } from './EarnStrategyForm';
diff --git a/src/pages/EarnStrategies/index.tsx b/src/pages/EarnStrategies/index.tsx
deleted file mode 100644
index 7a73a2c641..0000000000
--- a/src/pages/EarnStrategies/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import EarnStrategies from './EarnStrategies';
-
-export default EarnStrategies;
diff --git a/src/pages/EarnStrategies/types/form.ts b/src/pages/EarnStrategies/types/form.ts
deleted file mode 100644
index c912d7e549..0000000000
--- a/src/pages/EarnStrategies/types/form.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-type EarnStrategyFormType = 'deposit' | 'withdraw';
-type EarnStrategyRiskVariant = 'low' | 'high';
-
-interface EarnStrategyDepositFormData {
- deposit?: string;
-}
-
-interface EarnStrategyWithdrawalFormData {
- withdraw?: string;
- withdrawAsWrapped?: boolean;
-}
-
-export type {
- EarnStrategyDepositFormData,
- EarnStrategyFormType,
- EarnStrategyRiskVariant,
- EarnStrategyWithdrawalFormData
-};
diff --git a/src/pages/EarnStrategies/EarnStrategies.style.tsx b/src/pages/Strategies/Strategies.style.tsx
similarity index 74%
rename from src/pages/EarnStrategies/EarnStrategies.style.tsx
rename to src/pages/Strategies/Strategies.style.tsx
index 8bc1b94263..a9bf6f17ea 100644
--- a/src/pages/EarnStrategies/EarnStrategies.style.tsx
+++ b/src/pages/Strategies/Strategies.style.tsx
@@ -1,7 +1,7 @@
import styled from 'styled-components';
import { theme } from '@/component-library';
-const StyledEarnStrategiesLayout = styled.div`
+const StyledStrategiesLayout = styled.div`
display: grid;
gap: ${theme.spacing.spacing6};
@media (min-width: 80em) {
@@ -10,4 +10,4 @@ const StyledEarnStrategiesLayout = styled.div`
padding: ${theme.spacing.spacing6};
`;
-export { StyledEarnStrategiesLayout };
+export { StyledStrategiesLayout };
diff --git a/src/pages/Strategies/Strategies.tsx b/src/pages/Strategies/Strategies.tsx
new file mode 100644
index 0000000000..88c8b90357
--- /dev/null
+++ b/src/pages/Strategies/Strategies.tsx
@@ -0,0 +1,21 @@
+import { withErrorBoundary } from 'react-error-boundary';
+
+import ErrorFallback from '@/legacy-components/ErrorFallback';
+
+import { StrategyForm } from './components/StrategyForm';
+import { StyledStrategiesLayout } from './Strategies.style';
+
+const Strategies = (): JSX.Element => {
+ return (
+
+
+
+ );
+};
+
+export default withErrorBoundary(Strategies, {
+ FallbackComponent: ErrorFallback,
+ onReset: () => {
+ window.location.reload();
+ }
+});
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx b/src/pages/Strategies/components/StrategyForm/StrategyDepositForm/StrategyDepositForm.tsx
similarity index 69%
rename from src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx
rename to src/pages/Strategies/components/StrategyForm/StrategyDepositForm/StrategyDepositForm.tsx
index b0939fd093..ab318185af 100644
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyDepositForm/EarnStrategyDepositForm.tsx
+++ b/src/pages/Strategies/components/StrategyForm/StrategyDepositForm/StrategyDepositForm.tsx
@@ -6,24 +6,24 @@ import { convertMonetaryAmountToValueInUSD, newSafeMonetaryAmount } from '@/comm
import { TokenInput } from '@/component-library';
import { AuthCTA } from '@/components';
import { TRANSACTION_FEE_AMOUNT, WRAPPED_TOKEN, WRAPPED_TOKEN_SYMBOL } from '@/config/relay-chains';
-import { earnStrategySchema, isFormDisabled, useForm } from '@/lib/form';
+import { isFormDisabled, StrategySchema, useForm } from '@/lib/form';
import { useGetBalances } from '@/utils/hooks/api/tokens/use-get-balances';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
import { useTransaction } from '@/utils/hooks/transaction';
-import { EarnStrategyDepositFormData } from '../../../types/form';
-import { EarnStrategyFormBaseProps } from '../EarnStrategyForm';
-import { StyledEarnStrategyFormContent } from '../EarnStrategyForm.style';
-import { EarnStrategyFormFees } from '../EarnStrategyFormFees';
+import { StrategyDepositFormData } from '../../../types/form';
+import { StrategyFormBaseProps } from '../StrategyForm';
+import { StyledStrategyFormContent } from '../StrategyForm.style';
+import { StrategyFormFees } from '../StrategyFormFees';
-const EarnStrategyDepositForm = ({ riskVariant, hasActiveStrategy }: EarnStrategyFormBaseProps): JSX.Element => {
+const StrategyDepositForm = ({ riskVariant, hasActiveStrategy }: StrategyFormBaseProps): JSX.Element => {
const { getAvailableBalance } = useGetBalances();
const prices = useGetPrices();
const { t } = useTranslation();
// TODO: add transaction
const transaction = useTransaction();
- const handleSubmit = (data: EarnStrategyDepositFormData) => {
+ const handleSubmit = (data: StrategyDepositFormData) => {
// TODO: Execute transaction with params
// transaction.execute();
console.log(`transaction should be executed with parameters: ${data}, ${riskVariant}`);
@@ -32,9 +32,9 @@ const EarnStrategyDepositForm = ({ riskVariant, hasActiveStrategy }: EarnStrateg
const minAmount = newMonetaryAmount(1, WRAPPED_TOKEN);
const maxDepositAmount = getAvailableBalance(WRAPPED_TOKEN_SYMBOL) || newMonetaryAmount(0, WRAPPED_TOKEN);
- const form = useForm({
+ const form = useForm({
initialValues: { deposit: '' },
- validationSchema: earnStrategySchema('deposit', { maxAmount: maxDepositAmount, minAmount }),
+ validationSchema: StrategySchema('deposit', { maxAmount: maxDepositAmount, minAmount }),
onSubmit: handleSubmit
});
@@ -44,7 +44,7 @@ const EarnStrategyDepositForm = ({ riskVariant, hasActiveStrategy }: EarnStrateg
return (
);
};
-export { EarnStrategyDepositForm };
+export { StrategyDepositForm };
diff --git a/src/pages/Strategies/components/StrategyForm/StrategyDepositForm/index.ts b/src/pages/Strategies/components/StrategyForm/StrategyDepositForm/index.ts
new file mode 100644
index 0000000000..3cf5b84bea
--- /dev/null
+++ b/src/pages/Strategies/components/StrategyForm/StrategyDepositForm/index.ts
@@ -0,0 +1 @@
+export { StrategyDepositForm } from './StrategyDepositForm';
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx b/src/pages/Strategies/components/StrategyForm/StrategyForm.style.tsx
similarity index 79%
rename from src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx
rename to src/pages/Strategies/components/StrategyForm/StrategyForm.style.tsx
index 4179c1e7a4..59c4bd98a5 100644
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.style.tsx
+++ b/src/pages/Strategies/components/StrategyForm/StrategyForm.style.tsx
@@ -2,7 +2,7 @@ import styled from 'styled-components';
import { Dl, Flex, theme } from '@/component-library';
-const StyledEarnStrategyForm = styled(Flex)`
+const StyledStrategyForm = styled(Flex)`
margin-top: ${theme.spacing.spacing8};
background: ${theme.colors.bgPrimary};
padding: ${theme.spacing.spacing6};
@@ -16,7 +16,7 @@ const StyledDl = styled(Dl)`
border-radius: ${theme.rounded.rg};
`;
-const StyledEarnStrategyFormContent = styled(Flex)`
+const StyledStrategyFormContent = styled(Flex)`
margin-top: ${theme.spacing.spacing8};
flex-direction: column;
gap: ${theme.spacing.spacing8};
@@ -31,4 +31,4 @@ const StyledSwitchLabel = styled('label')`
font-weight: ${theme.fontWeight.bold};
`;
-export { StyledDl, StyledEarnStrategyForm, StyledEarnStrategyFormContent, StyledSwitchLabel };
+export { StyledDl, StyledStrategyForm, StyledStrategyFormContent, StyledSwitchLabel };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx b/src/pages/Strategies/components/StrategyForm/StrategyForm.tsx
similarity index 56%
rename from src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx
rename to src/pages/Strategies/components/StrategyForm/StrategyForm.tsx
index 7f4ba377d5..4dee2206fa 100644
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyForm.tsx
+++ b/src/pages/Strategies/components/StrategyForm/StrategyForm.tsx
@@ -3,20 +3,20 @@ import { newMonetaryAmount } from '@interlay/interbtc-api';
import { Tabs, TabsItem } from '@/component-library';
import { WRAPPED_TOKEN } from '@/config/relay-chains';
-import { EarnStrategyFormType, EarnStrategyRiskVariant } from '../../types/form';
-import { EarnStrategyDepositForm } from './EarnStrategyDepositForm';
-import { StyledEarnStrategyForm } from './EarnStrategyForm.style';
-import { EarnStrategyWithdrawalForm } from './EarnStrategyWithdrawalForm';
+import { StrategyFormType, StrategyRiskVariant } from '../../types/form';
+import { StrategyDepositForm } from './StrategyDepositForm';
+import { StyledStrategyForm } from './StrategyForm.style';
+import { StrategyWithdrawalForm } from './StrategyWithdrawalForm';
-interface EarnStrategyFormProps {
- riskVariant: EarnStrategyRiskVariant;
+interface StrategyFormProps {
+ riskVariant: StrategyRiskVariant;
}
-interface EarnStrategyFormBaseProps extends EarnStrategyFormProps {
+interface StrategyFormBaseProps extends StrategyFormProps {
hasActiveStrategy: boolean | undefined;
}
-type TabData = { type: EarnStrategyFormType; title: string };
+type TabData = { type: StrategyFormType; title: string };
const tabs: Array = [
{
@@ -29,21 +29,21 @@ const tabs: Array = [
}
];
-const EarnStrategyForm = ({ riskVariant }: EarnStrategyFormProps): JSX.Element => {
+const StrategyForm = ({ riskVariant }: StrategyFormProps): JSX.Element => {
// TODO: replace with actually withdrawable amount once we know how to get that information,
// for now it's statically set for display purposes
const maxWithdrawableAmount = newMonetaryAmount(1.337, WRAPPED_TOKEN, true);
const hasActiveStrategy = maxWithdrawableAmount && !maxWithdrawableAmount.isZero();
return (
-
+
{tabs.map(({ type, title }) => (
{type === 'deposit' ? (
-
+
) : (
-
))}
-
+
);
};
-export { EarnStrategyForm };
-export type { EarnStrategyFormBaseProps, EarnStrategyFormProps };
+export { StrategyForm };
+export type { StrategyFormBaseProps, StrategyFormProps };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx b/src/pages/Strategies/components/StrategyForm/StrategyFormFees.tsx
similarity index 81%
rename from src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx
rename to src/pages/Strategies/components/StrategyForm/StrategyFormFees.tsx
index 007867f302..13fd333592 100644
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyFormFees.tsx
+++ b/src/pages/Strategies/components/StrategyForm/StrategyFormFees.tsx
@@ -7,13 +7,13 @@ import { Dd, DlGroup, Dt } from '@/component-library';
import { getTokenPrice } from '@/utils/helpers/prices';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
-import { StyledDl } from './EarnStrategyForm.style';
+import { StyledDl } from './StrategyForm.style';
-interface EarnStrategyFormFeesProps {
+interface StrategyFormFeesProps {
amount: MonetaryAmount;
}
-const EarnStrategyFormFees = ({ amount }: EarnStrategyFormFeesProps): JSX.Element => {
+const StrategyFormFees = ({ amount }: StrategyFormFeesProps): JSX.Element => {
const prices = useGetPrices();
const { t } = useTranslation();
@@ -32,4 +32,4 @@ const EarnStrategyFormFees = ({ amount }: EarnStrategyFormFeesProps): JSX.Elemen
);
};
-export { EarnStrategyFormFees };
+export { StrategyFormFees };
diff --git a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx b/src/pages/Strategies/components/StrategyForm/StrategyWithdrawalForm/StrategyWithdrawalForm.tsx
similarity index 73%
rename from src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx
rename to src/pages/Strategies/components/StrategyForm/StrategyWithdrawalForm/StrategyWithdrawalForm.tsx
index 606fec950e..bcf30df624 100644
--- a/src/pages/EarnStrategies/components/EarnStrategyForm/EarnStrategyWithdrawalForm/EarnStrategyWithdrawalForm.tsx
+++ b/src/pages/Strategies/components/StrategyForm/StrategyWithdrawalForm/StrategyWithdrawalForm.tsx
@@ -12,16 +12,16 @@ import {
WRAPPED_TOKEN,
WRAPPED_TOKEN_SYMBOL
} from '@/config/relay-chains';
-import { earnStrategySchema, isFormDisabled, useForm } from '@/lib/form';
+import { isFormDisabled, StrategySchema, useForm } from '@/lib/form';
import { useGetPrices } from '@/utils/hooks/api/use-get-prices';
import { useTransaction } from '@/utils/hooks/transaction';
-import { EarnStrategyWithdrawalFormData } from '../../../types/form';
-import { EarnStrategyFormBaseProps } from '../EarnStrategyForm';
-import { StyledEarnStrategyFormContent, StyledSwitchLabel } from '../EarnStrategyForm.style';
-import { EarnStrategyFormFees } from '../EarnStrategyFormFees';
+import { StrategyWithdrawalFormData } from '../../../types/form';
+import { StrategyFormBaseProps } from '../StrategyForm';
+import { StyledStrategyFormContent, StyledSwitchLabel } from '../StrategyForm.style';
+import { StrategyFormFees } from '../StrategyFormFees';
-interface EarnStrategyWithdrawalFormProps extends EarnStrategyFormBaseProps {
+interface StrategyWithdrawalFormProps extends StrategyFormBaseProps {
maxWithdrawableAmount: MonetaryAmount | undefined;
}
@@ -33,7 +33,7 @@ const calculateReceivableAssets = (
return [amountToWithdraw];
}
// TODO: do some magic calculation to get the receivable assets based on input amount here,
- // or better move this computation to earn-strategy hook
+ // or better move this computation to strategy hook
const mockedReceivableAssets = [
amountToWithdraw.div(1.2),
newMonetaryAmount(amountToWithdraw.toBig().mul(213.2), RELAY_CHAIN_NATIVE_TOKEN, true)
@@ -42,17 +42,17 @@ const calculateReceivableAssets = (
return mockedReceivableAssets;
};
-const EarnStrategyWithdrawalForm = ({
+const StrategyWithdrawalForm = ({
riskVariant,
hasActiveStrategy,
maxWithdrawableAmount
-}: EarnStrategyWithdrawalFormProps): JSX.Element => {
+}: StrategyWithdrawalFormProps): JSX.Element => {
const { t } = useTranslation();
const prices = useGetPrices();
// TODO: add transaction
const transaction = useTransaction();
- const handleSubmit = (data: EarnStrategyWithdrawalFormData) => {
+ const handleSubmit = (data: StrategyWithdrawalFormData) => {
// TODO: Execute transaction with params
// transaction.execute()
console.log(data, riskVariant);
@@ -60,9 +60,9 @@ const EarnStrategyWithdrawalForm = ({
const minAmount = newMonetaryAmount(1, WRAPPED_TOKEN);
- const form = useForm({
+ const form = useForm({
initialValues: { withdraw: '', withdrawAsWrapped: true },
- validationSchema: earnStrategySchema('withdraw', {
+ validationSchema: StrategySchema('withdraw', {
maxAmount: maxWithdrawableAmount || newMonetaryAmount(0, WRAPPED_TOKEN),
minAmount
}),
@@ -76,7 +76,7 @@ const EarnStrategyWithdrawalForm = ({
return (
);
};
-export { EarnStrategyWithdrawalForm };
+export { StrategyWithdrawalForm };
diff --git a/src/pages/Strategies/components/StrategyForm/StrategyWithdrawalForm/index.ts b/src/pages/Strategies/components/StrategyForm/StrategyWithdrawalForm/index.ts
new file mode 100644
index 0000000000..26ff40f62c
--- /dev/null
+++ b/src/pages/Strategies/components/StrategyForm/StrategyWithdrawalForm/index.ts
@@ -0,0 +1 @@
+export { StrategyWithdrawalForm } from './StrategyWithdrawalForm';
diff --git a/src/pages/Strategies/components/StrategyForm/index.ts b/src/pages/Strategies/components/StrategyForm/index.ts
new file mode 100644
index 0000000000..2088c63879
--- /dev/null
+++ b/src/pages/Strategies/components/StrategyForm/index.ts
@@ -0,0 +1 @@
+export { StrategyForm } from './StrategyForm';
diff --git a/src/pages/Strategies/components/index.ts b/src/pages/Strategies/components/index.ts
new file mode 100644
index 0000000000..2088c63879
--- /dev/null
+++ b/src/pages/Strategies/components/index.ts
@@ -0,0 +1 @@
+export { StrategyForm } from './StrategyForm';
diff --git a/src/pages/Strategies/index.tsx b/src/pages/Strategies/index.tsx
new file mode 100644
index 0000000000..617a2532db
--- /dev/null
+++ b/src/pages/Strategies/index.tsx
@@ -0,0 +1,3 @@
+import Strategies from './Strategies';
+
+export default Strategies;
diff --git a/src/pages/Strategies/types/form.ts b/src/pages/Strategies/types/form.ts
new file mode 100644
index 0000000000..c6b5d6bad5
--- /dev/null
+++ b/src/pages/Strategies/types/form.ts
@@ -0,0 +1,13 @@
+type StrategyFormType = 'deposit' | 'withdraw';
+type StrategyRiskVariant = 'low' | 'high';
+
+interface StrategyDepositFormData {
+ deposit?: string;
+}
+
+interface StrategyWithdrawalFormData {
+ withdraw?: string;
+ withdrawAsWrapped?: boolean;
+}
+
+export type { StrategyDepositFormData, StrategyFormType, StrategyRiskVariant, StrategyWithdrawalFormData };
diff --git a/src/parts/Sidebar/SidebarContent/Navigation/index.tsx b/src/parts/Sidebar/SidebarContent/Navigation/index.tsx
index 54dabf7446..065c5be8e2 100644
--- a/src/parts/Sidebar/SidebarContent/Navigation/index.tsx
+++ b/src/parts/Sidebar/SidebarContent/Navigation/index.tsx
@@ -70,7 +70,7 @@ const Navigation = ({
const isLendingEnabled = useFeatureFlag(FeatureFlags.LENDING);
const isAMMEnabled = useFeatureFlag(FeatureFlags.AMM);
const isWalletEnabled = useFeatureFlag(FeatureFlags.WALLET);
- const isEarnStrategiesEnabled = useFeatureFlag(FeatureFlags.EARN_STRATEGIES);
+ const isStrategiesEnabled = useFeatureFlag(FeatureFlags.STRATEGIES);
const NAVIGATION_ITEMS = React.useMemo(
() => [
@@ -81,10 +81,10 @@ const Navigation = ({
disabled: !isWalletEnabled
},
{
- name: 'nav_earn_strategies',
- link: PAGES.EARN_STRATEGIES,
+ name: 'nav_strategies',
+ link: PAGES.STRATEGIES,
icon: BanknotesIcon,
- disabled: !isEarnStrategiesEnabled
+ disabled: !isStrategiesEnabled
},
{
name: 'nav_bridge',
@@ -200,14 +200,7 @@ const Navigation = ({
}
}
],
- [
- isWalletEnabled,
- isEarnStrategiesEnabled,
- isLendingEnabled,
- isAMMEnabled,
- selectedAccount?.address,
- vaultClientLoaded
- ]
+ [isWalletEnabled, isStrategiesEnabled, isLendingEnabled, isAMMEnabled, selectedAccount?.address, vaultClientLoaded]
);
return (
diff --git a/src/utils/constants/links.ts b/src/utils/constants/links.ts
index fb189728c4..c6cd038371 100644
--- a/src/utils/constants/links.ts
+++ b/src/utils/constants/links.ts
@@ -12,7 +12,7 @@ const URL_PARAMETERS = Object.freeze({
const PAGES = Object.freeze({
HOME: '/',
BRIDGE: '/bridge',
- EARN_STRATEGIES: '/earn-strategies',
+ STRATEGIES: '/strategies',
TRANSFER: '/transfer',
TRANSACTIONS: '/transactions',
TX: '/tx',
diff --git a/src/utils/hooks/use-feature-flag.ts b/src/utils/hooks/use-feature-flag.ts
index 917bd3b3c8..94a1f979f0 100644
--- a/src/utils/hooks/use-feature-flag.ts
+++ b/src/utils/hooks/use-feature-flag.ts
@@ -3,7 +3,7 @@ enum FeatureFlags {
AMM = 'amm',
WALLET = 'wallet',
BANXA = 'banxa',
- EARN_STRATEGIES = 'earn-strategies',
+ STRATEGIES = 'strategies',
GEOBLOCK = 'geoblock'
}
@@ -12,7 +12,7 @@ const featureFlags: Record = {
[FeatureFlags.AMM]: process.env.REACT_APP_FEATURE_FLAG_AMM,
[FeatureFlags.WALLET]: process.env.REACT_APP_FEATURE_FLAG_WALLET,
[FeatureFlags.BANXA]: process.env.REACT_APP_FEATURE_FLAG_BANXA,
- [FeatureFlags.EARN_STRATEGIES]: process.env.REACT_APP_FEATURE_FLAG_EARN_STRATEGIES,
+ [FeatureFlags.STRATEGIES]: process.env.REACT_APP_FEATURE_FLAG_EARN_STRATEGIES,
[FeatureFlags.GEOBLOCK]: process.env.REACT_APP_FEATURE_FLAG_GEOBLOCK
};
From 64716ee03cb395576b79e250e5afe57306fad418 Mon Sep 17 00:00:00 2001
From: Thomas Jeatt
Date: Wed, 31 May 2023 11:29:10 +0100
Subject: [PATCH 023/220] chore: release v2.32.6
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index af4e4260e6..581d654255 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interbtc-ui",
- "version": "2.32.5",
+ "version": "2.32.6",
"private": true,
"dependencies": {
"@craco/craco": "^6.1.1",
From a38f2af4f398c0cd2a31dd5bf70e5315379b39a5 Mon Sep 17 00:00:00 2001
From: Chanakya Kilaru
Date: Wed, 31 May 2023 16:57:06 +0530
Subject: [PATCH 024/220] 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>
---
src/utils/hooks/use-update-query-parameters.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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)
});
From ce9f2846d0630c79651bfec0738bfc5eee9c9b30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Sim=C3=A3o?=
Date: Thu, 1 Jun 2023 16:04:59 +0100
Subject: [PATCH 025/220] feat: add transaction notifications (#1177)
---
package.json | 2 +-
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 ++
.../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 +-
.../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 | 30 +-
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 +-
.../CollateralModal/CollateralModal.tsx | 49 +--
.../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 | 6 +-
src/utils/constants/links.ts | 21 +-
src/utils/context/Notifications.tsx | 141 +++++++
.../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 | 46 ++-
src/utils/hooks/use-copy-tooltip.tsx | 1 +
src/utils/hooks/use-countdown.ts | 69 ++++
src/utils/hooks/use-sign-message.ts | 1 +
src/utils/hooks/use-window-focus.ts | 26 ++
yarn.lock | 12 +-
85 files changed, 2000 insertions(+), 1197 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/package.json b/package.json
index 581d654255..5e804dcbdd 100644
--- a/package.json
+++ b/package.json
@@ -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/components/NotificationsPopover/NotificationsList.tsx b/src/components/NotificationsPopover/NotificationsList.tsx
new file mode 100644
index 0000000000..97d51a2c9a
--- /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);
+
+ 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(
-
-
-
+
+
+