Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dfe36998d | ||
|
|
30187ef69f | ||
|
|
7d1b9eb65f | ||
|
|
1835b42bb0 | ||
|
|
37a50def99 | ||
|
|
a97f296905 | ||
|
|
683d806f44 | ||
|
|
e635778438 | ||
|
|
7a456b33d4 | ||
|
|
6d25a072bd | ||
|
|
d6baa6ef3f | ||
|
|
195ddd5af7 | ||
|
|
2a5373c972 | ||
|
|
64f663ed5b | ||
|
|
983128fd74 | ||
|
|
e15f4b919c | ||
|
|
71c29524b1 | ||
|
|
0e26433e29 | ||
|
|
3afd5ef783 | ||
|
|
b2cac81235 | ||
|
|
e48ec4ca73 | ||
|
|
78f9ddff7a | ||
|
|
c1dcca43c8 | ||
|
|
029476fa19 | ||
|
|
6ecdcd981e | ||
|
|
591166e93d | ||
|
|
e30940e7df | ||
|
|
2d0a944097 | ||
|
|
0dd2ecb6d2 | ||
|
|
76819e0839 | ||
|
|
32b375cfa4 | ||
|
|
3dc87e873d | ||
|
|
ed61b3112e | ||
|
|
42f4142307 | ||
|
|
c5e79980d1 | ||
|
|
6e179191d8 |
@@ -1,4 +1,4 @@
|
||||
# Contributing to V4-web
|
||||
# Contributing to Trader-Fe
|
||||
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
|
||||
|
||||
- Reporting a bug
|
||||
@@ -18,10 +18,10 @@ Pull requests are the best way to propose changes to the codebase (we use [Githu
|
||||
4. Issue that pull request!
|
||||
|
||||
## Any contributions you make will be under the same License
|
||||
When you submit code changes, your submissions are understood to be under the same [License](https://github.com/dydxprotocol/v4-web/blob/master/LICENSE) that covers the project.
|
||||
When you submit code changes, your submissions are understood to be under the same [License](https://github.com/dydxprotocol/trader-fe/blob/master/LICENSE) that covers the project.
|
||||
|
||||
## Report bugs using Github's [issues](https://github.com/dydxprotocol/v4-web/issues)
|
||||
Report a bug by [opening a new issue](https://github.com/dydxprotocol/v4-web/issues/new).
|
||||
## Report bugs using Github's [issues](https://github.com/dydxprotocol/trader-fe/issues)
|
||||
Report a bug by [opening a new issue](https://github.com/dydxprotocol/trader-fe/issues/new).
|
||||
|
||||
**Great Bug Reports** tend to have:
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
Subject to your compliance with applicable law, you are granted the right to use the Licensed Work (defined below) under the terms of the below licenses; provided, however, that if you violate any such applicable law in your use of the Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Licensed Work will automatically and immediately terminate.
|
||||
Business Source License 1.1
|
||||
License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
|
||||
Copyright (C) 2023 dYdX Trading Inc.
|
||||
|
||||
Use of this software before the Change Date specified in the LICENSE file is governed by the Business Source License included in the LICENSE file and at https://spdx.org/licenses/BUSL-1.1.html.
|
||||
|
||||
On and after the Change Date specified in the LICENSE file, use of this software will be governed by the Change License (GNU Affero GPL v3) as specified in the LICENSE file.
|
||||
|
||||
|
||||
Business Source License 1.1
|
||||
Business Source License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
|
||||
Copyright (C) 2023 dYdX Trading Inc.
|
||||
|
||||
Parameters
|
||||
Licensor: dYdX Trading Inc.
|
||||
Licensed Work: dydxprotocol/cosmos-sdk, dydxprotocol/cometbft, dydxprotocol/v4-chain, dydxprotocol/v4-clients, dydxprotocol/v4-web, dydxprotocol/v4-abacus, dydxprotocol/v4-localization, dydxprotocol/v4-documentation, and any dYdX or dYdX Trading Inc. github repository reflecting a copy of this license, or link to this license.
|
||||
Additional Use Grant: None
|
||||
Change Date: On the later to occur of (i) the release of the Licensed Work labeled “dYdX Version 1.0 Affero GPL”, and which includes a document uploaded to the relevant github repository stating “The software in this repository is Version 1.0, subject to Affero GPL license”, and (ii) September 30, 2023 (approximate projected release of “Version 1.0” of this software).
|
||||
Change Date: On the later to occur of (i) the release of the Licensed Work labeled “dYdX Version 1.0 GPL”, and which includes a document uploaded to the relevant github repository stating “The software in this repository is Version 1.0, subject to GPL license”, and (ii) September 30, 2023 (approximate projected release of “Version 1.0” of this software).
|
||||
Change License: GNU Affero GPL License v3
|
||||
|
||||
License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
|
||||
@@ -42,7 +39,7 @@ OPEN SOURCE LICENSE BELOW - TO BE APPLIED AFTER THE CHANGE DATE.
|
||||
|
||||
Copyright (C) 2023 dYdX Trading Inc.
|
||||
|
||||
Subject to your compliance with applicable law, you are granted the right to use the Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Licensed Work will automatically and immediately terminate.
|
||||
Use of this software is governed by the The GNU Affero General Public License as set forth below.
|
||||
|
||||
|
||||
The GNU Affero General Public License
|
||||
@@ -838,4 +835,4 @@ copy of the Program in return for a fee.
|
||||
|
||||
|
||||
For more information about this software, see https://dydx.exchange.
|
||||
Copyright (C) 2023 dYdX Trading Inc.
|
||||
Copyright (C) 2023 dYdX Trading Inc.
|
||||
@@ -22,8 +22,8 @@ Alternatively, follow the [IPFS Kubo installation guide](https://docs.ipfs.tech
|
||||
Clone the repository and navigate to its directory:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dydxprotocol/v4-web.git
|
||||
cd v4-web
|
||||
git clone https://github.com/dydxprotocol/trader-fe.git
|
||||
cd trader-fe
|
||||
```
|
||||
|
||||
### Step 2: Install pnpm and dependencies
|
||||
@@ -102,47 +102,3 @@ To access your content on IPFS:
|
||||
2. **Public IPFS gateway:** Access your content via a public IPFS gateway, such as [https://dweb.link](https://dweb.link/) or [https://w3s.link/](https://w3s.link/). Use the gateway URL with your CID appended, like `https://dweb.link/ipfs/your_cid`.
|
||||
|
||||
Replace `your_cid` with the actual CID.
|
||||
|
||||
|
||||
## Part 5: Customization
|
||||
|
||||
### Using CircularXX font
|
||||
|
||||
1. **Procure a license for CircularXX:** Visit [Lineto](https://lineto.com/shop/select?family=circular&set=DOPhtVQR-8m) and add `Circular/Family Package` + `Circular Mono/Regular` to your cart. Check the applicable boxes in the `Configure` step to fill out licensing information. After the `Payment` step, download the `.woff2` files.
|
||||
|
||||
2. **Add the fonts to `v4-web`:** Add `CircularXXWeb-Bold.woff2`, `CircularXXWeb-Book.woff2`, `CircularXXWeb-Medium.woff2`, and `CircularXXMonoWeb-Regular.woff2` to the `src/styles/fonts` directory.
|
||||
|
||||
3. **Add font-family to stylesheets:** In `src/styles/fonts.css`, add the following code to import your newly purchased `Circular` font.
|
||||
```css
|
||||
@font-face {
|
||||
src: url("fonts/CircularXXWeb-Book.woff2") format("woff2");
|
||||
font-family: "Circular";
|
||||
font-weight: 450;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/CircularXXWeb-Medium.woff2") format("woff2");
|
||||
font-family: "Circular";
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/CircularXXWeb-Bold.woff2") format("woff2");
|
||||
font-family: "Circular";
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/CircularXXMonoWeb-Regular.woff2") format("woff2");
|
||||
font-family: "CircularMono";
|
||||
font-weight: 400;
|
||||
}
|
||||
```
|
||||
|
||||
In `src/styles/text.css`, update lines `4` and `5` to
|
||||
```css
|
||||
--fontFamily-base: "Circular", "Satoshi", system-ui, -apple-system, Helvetica, Arial, sans-serif;
|
||||
--fontFamily-monospace: "CircularMono", Courier, monospace, var(--fontFamily-base);
|
||||
```
|
||||
|
||||
Enjoy the newly installed font.
|
||||
|
||||
@@ -30,16 +30,16 @@
|
||||
},
|
||||
"packageManager": "pnpm@8.6.6",
|
||||
"dependencies": {
|
||||
"@0xsquid/sdk": "^1.10.0",
|
||||
"@0xsquid/sdk": "^1.7.2",
|
||||
"@cosmjs/amino": "^0.31.0",
|
||||
"@cosmjs/crypto": "^0.31.0",
|
||||
"@cosmjs/encoding": "^0.31.0",
|
||||
"@cosmjs/proto-signing": "^0.31.0",
|
||||
"@cosmjs/stargate": "^0.31.0",
|
||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||
"@dydxprotocol/abacus": "^0.4.16",
|
||||
"@dydxprotocol/v4-client-js": "^0.33.1",
|
||||
"@dydxprotocol/v4-localization": "^0.0.25",
|
||||
"@dydxprotocol/abacus": "^0.4.1",
|
||||
"@dydxprotocol/v4-client-js": "^0.32.0",
|
||||
"@dydxprotocol/v4-localization": "^0.0.21",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 817 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 973 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -13,9 +13,8 @@ import { AccountsProvider } from '@/hooks/useAccounts';
|
||||
import { DialogAreaProvider, useDialogArea } from './hooks/useDialogArea';
|
||||
import { LocaleProvider } from './hooks/useLocaleSeparators';
|
||||
import { NotificationsProvider } from './hooks/useNotifications';
|
||||
import { LocalNotificationsProvider } from './hooks/useLocalNotifications';
|
||||
import { SubaccountProvider } from './hooks/useSubaccount';
|
||||
import { SquidProvider } from '@/hooks/useSquid';
|
||||
import { SquidProvider } from '@/hooks/useSquidRouter';
|
||||
|
||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||
|
||||
@@ -99,31 +98,29 @@ const Content = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<Component {...props}>{children}</Component>
|
||||
);
|
||||
};
|
||||
|
||||
const providers = [
|
||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||
wrapProvider(GrazProvider),
|
||||
wrapProvider(WagmiConfig, { config }),
|
||||
wrapProvider(LocaleProvider),
|
||||
wrapProvider(DydxProvider),
|
||||
wrapProvider(AccountsProvider),
|
||||
wrapProvider(SubaccountProvider),
|
||||
wrapProvider(SquidProvider),
|
||||
wrapProvider(LocalNotificationsProvider),
|
||||
wrapProvider(NotificationsProvider),
|
||||
wrapProvider(DialogAreaProvider),
|
||||
];
|
||||
|
||||
const App = () => {
|
||||
return [...providers].reverse().reduce((children, Provider) => {
|
||||
return <Provider>{children}</Provider>;
|
||||
}, <Content />);
|
||||
};
|
||||
const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<GrazProvider>
|
||||
<WagmiConfig config={config}>
|
||||
<LocaleProvider>
|
||||
<DydxProvider>
|
||||
<AccountsProvider>
|
||||
<SubaccountProvider>
|
||||
<NotificationsProvider>
|
||||
<DialogAreaProvider>
|
||||
<SquidProvider>
|
||||
<Content />
|
||||
</SquidProvider>
|
||||
</DialogAreaProvider>
|
||||
</NotificationsProvider>
|
||||
</SubaccountProvider>
|
||||
</AccountsProvider>
|
||||
</DydxProvider>
|
||||
</LocaleProvider>
|
||||
</WagmiConfig>
|
||||
</GrazProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
@@ -195,9 +192,7 @@ Styled.NotificationsToastArea = styled(NotificationsToastArea)`
|
||||
`;
|
||||
|
||||
Styled.DialogArea = styled.aside`
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: clip;
|
||||
${layoutMixins.noPointerEvents}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
@@ -82,9 +82,7 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
<Styled.Item
|
||||
// value={item.value} // search by both value and textContent
|
||||
// value={[group.groupLabel, item.label, item.tag].filter(Boolean).join('|')} // exclude item.value from searchable terms (not guaranteed to be unique)
|
||||
value={[group.groupLabel, item.value, item.description, item.label, item.tag]
|
||||
.filter(Boolean)
|
||||
.join('|')}
|
||||
value={[group.groupLabel, item.value, item.description, item.label, item.tag].filter(Boolean).join('|')}
|
||||
onSelect={() => {
|
||||
if (item.subitems) {
|
||||
// open submenu
|
||||
@@ -118,15 +116,7 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
<Styled.Item
|
||||
// value={subitem.value} // search by both value and textContent
|
||||
// value={[group.groupLabel, item.label, subitem.label, subitem.tag].filter(Boolean).join('|')}
|
||||
value={[
|
||||
group.groupLabel,
|
||||
item.value,
|
||||
item.description,
|
||||
item.label,
|
||||
item.tag,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('|')}
|
||||
value={[group.groupLabel, item.value, item.description, item.label, item.tag].filter(Boolean).join('|')}
|
||||
onSelect={() => {
|
||||
subitem.onSelect?.(subitem.value);
|
||||
onItemSelected?.();
|
||||
@@ -178,7 +168,7 @@ Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
|
||||
background-color: var(--comboboxMenu-backgroundColor);
|
||||
border-radius: inherit;
|
||||
|
||||
input:focus-visible {
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -282,7 +272,7 @@ Styled.Item = styled(Command.Item)`
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&[aria-disabled='true'] {
|
||||
&[aria-disabled="true"] {
|
||||
opacity: 0.75;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,10 @@ export const Dialog = ({
|
||||
return (
|
||||
<Root modal={showOverlay} open={isOpen} onOpenChange={setIsOpen}>
|
||||
{slotTrigger && <Trigger asChild>{slotTrigger}</Trigger>}
|
||||
<DialogPortal withPortal={placement !== DialogPlacement.Inline} container={portalContainer}>
|
||||
<DialogPortal
|
||||
withPortal={placement !== DialogPlacement.Inline}
|
||||
container={portalContainer}
|
||||
>
|
||||
{showOverlay && <Styled.Overlay />}
|
||||
<Styled.Container
|
||||
placement={placement}
|
||||
@@ -422,7 +425,7 @@ Styled.Close = styled(Close)`
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ Styled.ComboboxMenu = styled(ComboboxMenu)`
|
||||
|
||||
--stickyArea1-topHeight: var(--form-input-height);
|
||||
|
||||
input:focus-visible {
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -743,7 +743,7 @@ Styled.Tr = styled.tr<{
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
--tableRow-currentBackgroundColor: var(--tableRow-hover-backgroundColor);
|
||||
filter: brightness(1.1);
|
||||
@@ -753,7 +753,7 @@ Styled.Tr = styled.tr<{
|
||||
${({ withFocusStickyRows }) =>
|
||||
withFocusStickyRows &&
|
||||
css`
|
||||
&:focus-visible,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
${layoutMixins.sticky}
|
||||
z-index: 1;
|
||||
|
||||
@@ -17,7 +17,6 @@ type ElementProps = {
|
||||
slotIcon?: React.ReactNode;
|
||||
slotTitle?: React.ReactNode;
|
||||
slotDescription?: React.ReactNode;
|
||||
slotCustomContent?: React.ReactNode;
|
||||
slotAction?: React.ReactNode;
|
||||
actionDescription?: string;
|
||||
actionAltText?: string;
|
||||
@@ -34,7 +33,6 @@ export const Toast = ({
|
||||
slotIcon,
|
||||
slotTitle,
|
||||
slotDescription,
|
||||
slotCustomContent,
|
||||
slotAction,
|
||||
actionDescription = '',
|
||||
actionAltText = actionDescription,
|
||||
@@ -80,13 +78,12 @@ export const Toast = ({
|
||||
|
||||
<$Title>{slotTitle}</$Title>
|
||||
</$Header>
|
||||
{!slotCustomContent && <$Description>{slotDescription}</$Description>}
|
||||
{slotCustomContent}
|
||||
{actionDescription && (
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
)}
|
||||
|
||||
<$Description>{slotDescription}</$Description>
|
||||
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
</$Container>
|
||||
</div>
|
||||
</$Root>
|
||||
@@ -121,7 +118,8 @@ const $Root = styled(Root)`
|
||||
align-items: end;
|
||||
transform-origin: left bottom;
|
||||
|
||||
animation: ${keyframes`
|
||||
animation:
|
||||
${keyframes`
|
||||
from {
|
||||
/* scale: 0; */
|
||||
grid-template-rows: 0fr; // height transition
|
||||
@@ -152,7 +150,7 @@ const $Root = styled(Root)`
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -205,8 +203,8 @@ const $Container = styled.div`
|
||||
// Rules
|
||||
${popoverMixins.popover}
|
||||
padding: 1rem;
|
||||
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
||||
// border
|
||||
box-shadow:
|
||||
0 0 0 var(--border-width) var(--color-border), // border
|
||||
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
||||
|
||||
${$Root}:focus:not([data-swipe='end']) & {
|
||||
|
||||
@@ -91,7 +91,7 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
Styled.Content = styled(Content)`
|
||||
z-index: 1;
|
||||
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -49,11 +49,11 @@ export enum EvmDerivedAccountStatus {
|
||||
Derived,
|
||||
}
|
||||
|
||||
import type { DydxAddress, EvmAddress } from './wallets';
|
||||
import type { DydxAddress, EthereumAddress } from './wallets';
|
||||
|
||||
export type EvmDerivedAddresses = {
|
||||
version?: string;
|
||||
[EvmAddress: EvmAddress]: {
|
||||
[EthereumAddress: EthereumAddress]: {
|
||||
encryptedSignature?: string;
|
||||
dydxAddress?: DydxAddress;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SupportedLocales } from './localization';
|
||||
import type { DydxNetwork } from './networks';
|
||||
import type { OnboardingState, OnboardingSteps } from './account';
|
||||
import type { DydxAddress, WalletType, WalletConnectionType, EvmAddress } from './wallets';
|
||||
import type { DydxAddress, WalletType, WalletConnectionType, EthereumAddress } from './wallets';
|
||||
import type { DialogTypes } from './dialogs';
|
||||
import type { TradeTypes } from './trade';
|
||||
import type { AbacusApiStatus, HumanReadablePlaceOrderPayload } from './abacus';
|
||||
@@ -40,7 +40,7 @@ export type AnalyticsUserPropertyValue<T extends AnalyticsUserProperty> =
|
||||
: T extends AnalyticsUserProperty.WalletConnectionType
|
||||
? WalletConnectionType | undefined
|
||||
: T extends AnalyticsUserProperty.WalletAddress
|
||||
? EvmAddress | DydxAddress | undefined
|
||||
? EthereumAddress | DydxAddress | undefined
|
||||
: // Account
|
||||
T extends AnalyticsUserProperty.DydxAddress
|
||||
? DydxAddress | undefined
|
||||
@@ -67,7 +67,6 @@ export enum AnalyticsEvent {
|
||||
// Onboarding
|
||||
OnboardingStepChanged = 'OnboardingStepChanged',
|
||||
OnboardingAccountDerived = 'OnboardingAccountDerived',
|
||||
OnboardingWalletIsNonDeterministic = 'OnboardingWalletIsNonDeterministic',
|
||||
|
||||
// Transfers
|
||||
TransferFaucet = 'TransferFaucet',
|
||||
|
||||
@@ -12,7 +12,6 @@ export enum LocalStorageKey {
|
||||
NotificationsLastUpdated = 'dydx.NotificationsLastUpdated',
|
||||
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
|
||||
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
|
||||
TransferNotifications = 'dydx.TransferNotifications',
|
||||
|
||||
// UI State
|
||||
LastViewedMarket = 'dydx.LastViewedMarket',
|
||||
|
||||
@@ -34,8 +34,6 @@ export const STRING_KEYS = {
|
||||
...WARNINGS_STRING_KEYS,
|
||||
};
|
||||
|
||||
export type StringKey = keyof typeof STRING_KEYS;
|
||||
|
||||
export type LocaleData = typeof EN_LOCALE_DATA;
|
||||
|
||||
export type StringGetterFunction = (a: {
|
||||
@@ -90,17 +88,17 @@ export type TooltipStrings = {
|
||||
};
|
||||
|
||||
export const ORDER_ERROR_CODE_MAP: { [key: number]: string } = {
|
||||
2000: STRING_KEYS['2000_FILL_OR_KILL_ORDER_COULD_NOT_BE_FULLY_FILLED'],
|
||||
2001: STRING_KEYS['2001_REDUCE_ONLY_WOULD_INCREASE_POSITION_SIZE'],
|
||||
2002: STRING_KEYS['2002_REDUCE_ONLY_WOULD_CHANGE_POSITION_SIDE'],
|
||||
2003: STRING_KEYS['2003_POST_ONLY_WOULD_CROSS_MAKER_ORDER'],
|
||||
3000: STRING_KEYS['3000_INVALID_ORDER_FLAGS'],
|
||||
3001: STRING_KEYS['3001_INVALID_STATEFUL_ORDER_GOOD_TIL_BLOCK_TIME'],
|
||||
3002: STRING_KEYS['3002_STATEFUL_ORDERS_CANNOT_REQUIRE_IMMEDIATE_EXECUTION'],
|
||||
3003: STRING_KEYS['3003_TIME_EXCEEDS_GOOD_TIL_BLOCK_TIME'],
|
||||
3004: STRING_KEYS['3004_GOOD_TIL_BLOCK_TIME_EXCEEDS_STATEFUL_ORDER_TIME_WINDOW'],
|
||||
3005: STRING_KEYS['3005_STATEFUL_ORDER_ALREADY_EXISTS'],
|
||||
3006: STRING_KEYS['3006_STATEFUL_ORDER_DOES_NOT_EXIST'],
|
||||
3007: STRING_KEYS['3007_STATEFUL_ORDER_COLLATERALIZATION_CHECK_FAILED'],
|
||||
3008: STRING_KEYS['3008_STATEFUL_ORDER_PREVIOUSLY_CANCELLED'],
|
||||
2000: '2000_FILL_OR_KILL_ORDER_COULD_NOT_BE_FULLY_FILLED',
|
||||
2001: '2001_REDUCE_ONLY_WOULD_INCREASE_POSITION_SIZE',
|
||||
2002: '2002_REDUCE_ONLY_WOULD_CHANGE_POSITION_SIDE',
|
||||
2003: '2003_POST_ONLY_WOULD_CROSS_MAKER_ORDER',
|
||||
3000: '3000_INVALID_ORDER_FLAGS',
|
||||
3001: '3001_INVALID_STATEFUL_ORDER_GOOD_TIL_BLOCK_TIME',
|
||||
3002: '3002_STATEFUL_ORDERS_CANNOT_REQUIRE_IMMEDIATE_EXECUTION',
|
||||
3003: '3003_TIME_EXCEEDS_GOOD_TIL_BLOCK_TIME',
|
||||
3004: '3004_GOOD_TIL_BLOCK_TIME_EXCEEDS_STATEFUL_ORDER_TIME_WINDOW',
|
||||
3005: '3005_STATEFUL_ORDER_ALREADY_EXISTS',
|
||||
3006: '3006_STATEFUL_ORDER_DOES_NOT_EXIST',
|
||||
3007: '3007_STATEFUL_ORDER_COLLATERALIZATION_CHECK_FAILED',
|
||||
3008: '3008_STATEFUL_ORDER_PREVIOUSLY_CANCELLED',
|
||||
};
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
import { Asset, PerpetualMarket } from '@/constants/abacus';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
export interface Market {
|
||||
market: string;
|
||||
status: string;
|
||||
baseAsset: string;
|
||||
quoteAsset: string;
|
||||
stepSize: string;
|
||||
tickSize: string;
|
||||
oraclePrice: string;
|
||||
priceChange24H: string;
|
||||
nextFundingRate: string;
|
||||
nextFundingAt: string;
|
||||
minOrderSize: string;
|
||||
type: string;
|
||||
initialMarginFraction: string;
|
||||
maintenanceMarginFraction: string;
|
||||
volume24H: string;
|
||||
trades24H: string;
|
||||
openInterest: string;
|
||||
incrementalInitialMarginFraction: string;
|
||||
incrementalPositionSize: string;
|
||||
maxPositionSize: string;
|
||||
baselinePositionSize: string;
|
||||
assetResolution: string;
|
||||
syntheticAssetId: string;
|
||||
}
|
||||
|
||||
export type AllMarkets = {
|
||||
[market: string]: Market;
|
||||
};
|
||||
|
||||
export type MarketData = {
|
||||
asset: Asset;
|
||||
tickSizeDecimals: number;
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
import { StatusResponse } from "@0xsquid/sdk";
|
||||
|
||||
/** implemented in useNotificationTypes */
|
||||
export enum NotificationType {
|
||||
OrderStatusChanged = 'OrderStatusChanged',
|
||||
SquidTransfer = 'SquidTransfer',
|
||||
}
|
||||
|
||||
export enum NotificationComponentType {}
|
||||
|
||||
export type NotificationId = string | number;
|
||||
export type NotificationId = string | number
|
||||
|
||||
export type NotificationTypeConfig<
|
||||
_NotificationId extends NotificationId = string,
|
||||
NotificationUpdateKey = any
|
||||
> = {
|
||||
export type NotificationTypeConfig<_NotificationId extends NotificationId = string, NotificationUpdateKey = any> = {
|
||||
type: NotificationType;
|
||||
|
||||
/** React hook to trigger notifications based on app state */
|
||||
/** React hook to trigger notifications based on app state */
|
||||
useTrigger: (_: {
|
||||
trigger: (
|
||||
/** Unique ID for the triggered notification */
|
||||
@@ -36,7 +30,7 @@ export type NotificationTypeConfig<
|
||||
* @param true (default): Notification initialized with status NotificationStatus.Triggered
|
||||
* @param false: Notification initialized with status NotificationStatus.Cleared
|
||||
*/
|
||||
isNew?: boolean
|
||||
isNew?: boolean,
|
||||
) => void;
|
||||
|
||||
lastUpdated: number;
|
||||
@@ -64,10 +58,7 @@ export enum NotificationStatus {
|
||||
}
|
||||
|
||||
/** Notification state. Serialized and cached into localStorage. */
|
||||
export type Notification<
|
||||
_NotificationId extends NotificationId = string,
|
||||
NotificationUpdateKey = any
|
||||
> = {
|
||||
export type Notification<_NotificationId extends NotificationId = string, NotificationUpdateKey = any> = {
|
||||
id: _NotificationId;
|
||||
type: NotificationType;
|
||||
status: NotificationStatus;
|
||||
@@ -83,11 +74,7 @@ export type NotificationDisplayData = {
|
||||
|
||||
title?: string;
|
||||
|
||||
description?: React.ReactNode;
|
||||
|
||||
customContent?: React.ReactNode;
|
||||
|
||||
customMenuContent?: React.ReactNode;
|
||||
description?: string;
|
||||
|
||||
actionDescription?: string;
|
||||
|
||||
@@ -114,13 +101,3 @@ export type NotificationDisplayData = {
|
||||
*/
|
||||
toastDuration?: number;
|
||||
};
|
||||
|
||||
// Notification types
|
||||
export type TransferNotifcation = {
|
||||
txHash: string;
|
||||
toChainId?: string;
|
||||
fromChainId?: string;
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
@@ -32,18 +32,6 @@ export enum WalletConnectionType {
|
||||
WalletConnect2 = 'walletConnect2',
|
||||
}
|
||||
|
||||
export enum WalletErrorType {
|
||||
// General
|
||||
ChainMismatch,
|
||||
UserCanceled,
|
||||
|
||||
// Non-Deterministic
|
||||
NonDeterministicWallet,
|
||||
|
||||
// Misc
|
||||
Unknown,
|
||||
}
|
||||
|
||||
type WalletConnectionTypeConfig = {
|
||||
name: string;
|
||||
wagmiConnectorId?: string;
|
||||
@@ -337,7 +325,7 @@ export const SIGN_TYPED_DATA = {
|
||||
|
||||
export type PrivateInformation = ReturnType<typeof onboarding.deriveHDKeyFromEthereumSignature>;
|
||||
|
||||
export type EvmAddress = `0x${string}`;
|
||||
export type EthereumAddress = `0x${string}`;
|
||||
export type DydxAddress = `dydx${string}`;
|
||||
|
||||
export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { formatUnits } from 'viem';
|
||||
|
||||
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
|
||||
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||
import { EvmAddress } from '@/constants/wallets';
|
||||
import { EthereumAddress } from '@/constants/wallets';
|
||||
|
||||
import { convertBech32Address } from '@/lib/addressUtils';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
@@ -16,7 +16,6 @@ import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
import { useAccounts } from './useAccounts';
|
||||
import { usePollNativeTokenBalance } from './usePollNativeTokenBalance';
|
||||
import { usePollUSDCBalance } from './usePollUSDCBalance';
|
||||
|
||||
type UseAccountBalanceProps = {
|
||||
// Token Items
|
||||
@@ -59,7 +58,7 @@ export const useAccountBalance = ({
|
||||
token:
|
||||
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS
|
||||
? undefined
|
||||
: (addressOrDenom as EvmAddress),
|
||||
: (addressOrDenom as EthereumAddress),
|
||||
watch: true,
|
||||
});
|
||||
|
||||
@@ -97,13 +96,9 @@ export const useAccountBalance = ({
|
||||
.div(QUANTUM_MULTIPLIER)
|
||||
.toNumber();
|
||||
|
||||
const usdcCoinBalance = usePollUSDCBalance({ dydxAddress });
|
||||
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber();
|
||||
|
||||
return {
|
||||
balance,
|
||||
nativeTokenBalance,
|
||||
usdcBalance,
|
||||
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
|
||||
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-clien
|
||||
|
||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
||||
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
|
||||
import { DydxAddress, EthereumAddress, PrivateInformation } from '@/constants/wallets';
|
||||
|
||||
import {
|
||||
setOnboardingState,
|
||||
@@ -82,7 +82,7 @@ const useAccountsContext = () => {
|
||||
evmAddress,
|
||||
dydxAddress,
|
||||
}: {
|
||||
evmAddress: EvmAddress;
|
||||
evmAddress: EthereumAddress;
|
||||
dydxAddress?: DydxAddress;
|
||||
}) => {
|
||||
saveEvmDerivedAddresses({
|
||||
@@ -192,7 +192,7 @@ const useAccountsContext = () => {
|
||||
} catch (error) {
|
||||
log('useAccounts/setLocalDydxWallet', error);
|
||||
}
|
||||
} else if (evmAddress) {
|
||||
} else if (evmAddress && signerWagmi) {
|
||||
if (!localDydxWallet) {
|
||||
dispatch(setOnboardingState(OnboardingState.WalletConnected));
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useMatch, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
import { closeDialogInTradeBox } from '@/state/dialogs';
|
||||
|
||||
@@ -57,8 +57,8 @@ const useDydxClientContext = () => {
|
||||
networkConfig?.indexerSocketUrl &&
|
||||
networkConfig?.validatorUrl
|
||||
) {
|
||||
try {
|
||||
const initializedClient = await CompositeClient.connect(
|
||||
setCompositeClient(
|
||||
await CompositeClient.connect(
|
||||
new Network(
|
||||
selectedNetwork,
|
||||
new IndexerConfig(networkConfig.indexerUrl, networkConfig.indexerSocketUrl),
|
||||
@@ -67,11 +67,8 @@ const useDydxClientContext = () => {
|
||||
broadcastTimeoutMs: 60_000,
|
||||
})
|
||||
)
|
||||
);
|
||||
setCompositeClient(initializedClient);
|
||||
} catch (error) {
|
||||
log('useDydxClient/initializeCompositeClient', error);
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setCompositeClient(undefined);
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import type { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { type TransferNotifcation } from '@/constants/notifications';
|
||||
|
||||
import { useAccounts } from '@/hooks/useAccounts';
|
||||
import { useSquid } from '@/hooks/useSquid';
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
|
||||
const LocalNotificationsContext = createContext<
|
||||
ReturnType<typeof useLocalNotificationsContext> | undefined
|
||||
>(undefined);
|
||||
|
||||
LocalNotificationsContext.displayName = 'LocalNotifications';
|
||||
|
||||
export const LocalNotificationsProvider = ({ ...props }) => (
|
||||
<LocalNotificationsContext.Provider value={useLocalNotificationsContext()} {...props} />
|
||||
);
|
||||
|
||||
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
|
||||
|
||||
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
|
||||
|
||||
const useLocalNotificationsContext = () => {
|
||||
// transfer notifications
|
||||
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
|
||||
[key: `dydx${string}`]: TransferNotifcation[];
|
||||
}>({
|
||||
key: LocalStorageKey.TransferNotifications,
|
||||
defaultValue: {},
|
||||
});
|
||||
|
||||
const { dydxAddress } = useAccounts();
|
||||
|
||||
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
|
||||
|
||||
const setTransferNotifications = useCallback(
|
||||
(notifications: TransferNotifcation[]) => {
|
||||
if (!dydxAddress) return;
|
||||
const updatedNotifications = { ...allTransferNotifications };
|
||||
updatedNotifications[dydxAddress] = notifications;
|
||||
setAllTransferNotifications(updatedNotifications);
|
||||
},
|
||||
[setAllTransferNotifications, dydxAddress]
|
||||
);
|
||||
|
||||
const addTransferNotification = useCallback(
|
||||
(notification: TransferNotifcation) =>
|
||||
setTransferNotifications([...transferNotifications, notification]),
|
||||
[transferNotifications]
|
||||
);
|
||||
|
||||
const squid = useSquid();
|
||||
|
||||
const { data: transferStatuses } = useQuery({
|
||||
queryKey: ['getTransactionStatus', transferNotifications],
|
||||
queryFn: async () => {
|
||||
const statuses: { [key: string]: StatusResponse } = {};
|
||||
for (const {
|
||||
txHash,
|
||||
toChainId,
|
||||
fromChainId,
|
||||
status: currentStatus,
|
||||
} of transferNotifications) {
|
||||
try {
|
||||
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
|
||||
|
||||
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
|
||||
if (status) statuses[txHash] = status;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return statuses;
|
||||
},
|
||||
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!transferStatuses) return;
|
||||
const newTransferNotifications = transferNotifications.map((notification) => {
|
||||
const status = transferStatuses[notification.txHash];
|
||||
if (!status) return notification;
|
||||
return {
|
||||
...notification,
|
||||
status,
|
||||
};
|
||||
});
|
||||
setTransferNotifications(newTransferNotifications);
|
||||
}, [transferStatuses]);
|
||||
|
||||
return {
|
||||
transferNotifications,
|
||||
addTransferNotification,
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
|
||||
import { groupBy } from 'lodash';
|
||||
|
||||
@@ -8,20 +8,12 @@ import { STRING_KEYS } from '@/constants/localization';
|
||||
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
|
||||
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade';
|
||||
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { TransferStatusToast } from '@/views/TransferStatus';
|
||||
|
||||
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
|
||||
|
||||
import { useStringGetter } from './useStringGetter';
|
||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
export const notificationTypes = [
|
||||
{
|
||||
@@ -101,51 +93,4 @@ export const notificationTypes = [
|
||||
};
|
||||
},
|
||||
} as NotificationTypeConfig<string, [string, number]>,
|
||||
{
|
||||
type: NotificationType.SquidTransfer,
|
||||
useTrigger: ({ trigger }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { transferNotifications } = useLocalNotifications();
|
||||
|
||||
const getTitleStringKey = useCallback((type: 'deposit' | 'withdraw', finished: boolean) => {
|
||||
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
|
||||
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
|
||||
if (type === 'withdraw' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
|
||||
return STRING_KEYS.WITHDRAW;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
for (const transfer of transferNotifications) {
|
||||
const { toChainId, status, txHash, toAmount } = transfer;
|
||||
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
||||
const type = toChainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdraw';
|
||||
|
||||
trigger(
|
||||
txHash,
|
||||
{
|
||||
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
|
||||
title: stringGetter({ key: getTitleStringKey(type, finished) }),
|
||||
// TODO: confirm with design what the description should be
|
||||
description: (
|
||||
<>
|
||||
<span>{type === 'deposit' ? 'Deposit of ' : 'Withdraw of'}</span>
|
||||
<Output type={OutputType.Fiat} value={toAmount} />
|
||||
</>
|
||||
),
|
||||
customContent: (
|
||||
<TransferStatusToast
|
||||
toAmount={transfer.toAmount}
|
||||
triggeredAt={transfer.triggeredAt}
|
||||
status={transfer.status}
|
||||
/>
|
||||
),
|
||||
customMenuContent: !finished && <TransferStatusSteps status={transfer.status} />,
|
||||
toastSensitivity: 'foreground',
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
}, [transferNotifications]);
|
||||
},
|
||||
},
|
||||
] satisfies NotificationTypeConfig[];
|
||||
|
||||
@@ -106,10 +106,7 @@ const useNotificationsContext = () => {
|
||||
updateKey,
|
||||
} as Notification);
|
||||
|
||||
updateStatus(
|
||||
notification,
|
||||
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
|
||||
);
|
||||
updateStatus(notification, isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared);
|
||||
}
|
||||
|
||||
// updateKey changed - update existing notification
|
||||
@@ -144,9 +141,7 @@ const useNotificationsContext = () => {
|
||||
// Push notifications
|
||||
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
|
||||
key: LocalStorageKey.PushNotificationsEnabled,
|
||||
defaultValue: Boolean(
|
||||
globalThis.Notification && globalThis.Notification.permission === 'granted'
|
||||
),
|
||||
defaultValue: Boolean(globalThis.Notification && globalThis.Notification.permission === 'granted'),
|
||||
});
|
||||
const [isEnablingPush, setIsEnablingPush] = useState(false);
|
||||
|
||||
@@ -241,3 +236,4 @@ const useNotificationsContext = () => {
|
||||
setIsMenuOpen,
|
||||
};
|
||||
};
|
||||
// }
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
import { Squid } from '@0xsquid/sdk';
|
||||
|
||||
import { CLIENT_NETWORK_CONFIGS, DydxV4Network, isDydxV4Network } from '@/constants/networks';
|
||||
import { DydxV4Network, isDydxV4Network } from '@/constants/networks';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
export const NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
||||
const SQUID_BASE_URL: Record<DydxV4Network, string | undefined> = {
|
||||
[DydxV4Network.V4Testnet2]: 'https://squid-api-git-feat-cosmos-maintestnet-0xsquid.vercel.app',
|
||||
[DydxV4Network.V4Staging]: undefined,
|
||||
[DydxV4Network.V4Local]: undefined,
|
||||
[DydxV4Network.V4Mainnet]: 'https://api.0xsquid.com',
|
||||
};
|
||||
|
||||
const useSquidContext = () => {
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
@@ -23,7 +27,7 @@ const useSquidContext = () => {
|
||||
const squid = useMemo(
|
||||
() =>
|
||||
isDydxV4Network(selectedNetwork)
|
||||
? new Squid({ baseUrl: CLIENT_NETWORK_CONFIGS[selectedNetwork]?.endpoints['0xsquid'] })
|
||||
? new Squid({ baseUrl: SQUID_BASE_URL[selectedNetwork as DydxV4Network] })
|
||||
: undefined,
|
||||
[selectedNetwork]
|
||||
);
|
||||
@@ -551,8 +551,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
const errorCode: number | undefined = error?.code;
|
||||
const errorStringKey = errorCode ? ORDER_ERROR_CODE_MAP[errorCode] : undefined;
|
||||
const errorStringKey = error?.code && ORDER_ERROR_CODE_MAP[error.code as number];
|
||||
onError?.({ errorStringKey });
|
||||
|
||||
log('useSubaccount/placeOrder', error, {
|
||||
|
||||
@@ -4,20 +4,17 @@ import { LocalStorageKey } from '@/constants/localStorage';
|
||||
|
||||
import {
|
||||
type DydxAddress,
|
||||
type EvmAddress,
|
||||
type EthereumAddress,
|
||||
WalletConnectionType,
|
||||
WalletType,
|
||||
wallets,
|
||||
DYDX_CHAIN_INFO,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||
|
||||
import {
|
||||
useConnect as useConnectWagmi,
|
||||
useAccount as useAccountWagmi,
|
||||
useDisconnect as useDisconnectWagmi,
|
||||
usePublicClient as usePublicClientWagmi,
|
||||
useWalletClient as useWalletClientWagmi,
|
||||
} from 'wagmi';
|
||||
import {
|
||||
@@ -29,21 +26,21 @@ import {
|
||||
} from 'graz';
|
||||
|
||||
import { resolveWagmiConnector } from '@/lib/wagmi';
|
||||
import { getWalletConnection, parseWalletError } from '@/lib/wallet';
|
||||
import { getWalletConnection } from '@/lib/wallet';
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||
import { useStringGetter } from './useStringGetter';
|
||||
|
||||
export const useWalletConnection = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
// EVM wallet connection
|
||||
const [evmAddress, saveEvmAddress] = useLocalStorage<EvmAddress | undefined>({
|
||||
const [evmAddress, saveEvmAddress] = useLocalStorage<EthereumAddress | undefined>({
|
||||
key: LocalStorageKey.EvmAddress,
|
||||
defaultValue: undefined,
|
||||
});
|
||||
const { address: evmAddressWagmi, isConnected: isConnectedWagmi } = useAccountWagmi();
|
||||
const publicClientWagmi = usePublicClientWagmi();
|
||||
const { data: signerWagmi } = useWalletClientWagmi();
|
||||
const { disconnectAsync: disconnectWagmi } = useDisconnectWagmi();
|
||||
|
||||
@@ -132,6 +129,8 @@ export const useWalletConnection = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log('useWalletConnection/connectWallet', error);
|
||||
|
||||
throw Object.assign(
|
||||
new Error([error.message, error.cause?.message].filter(Boolean).join('\n')),
|
||||
{
|
||||
@@ -159,7 +158,7 @@ export const useWalletConnection = () => {
|
||||
// Wallet selection
|
||||
|
||||
const [selectedWalletType, setSelectedWalletType] = useState<WalletType | undefined>(walletType);
|
||||
const [selectedWalletError, setSelectedWalletError] = useState<string>();
|
||||
const [selectedWalletError, setSelectedWalletError] = useState<Error>();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -174,15 +173,8 @@ export const useWalletConnection = () => {
|
||||
setWalletType(walletType);
|
||||
setWalletConnectionType(walletConnectionType);
|
||||
} catch (error) {
|
||||
const { walletErrorType, message } = parseWalletError({
|
||||
error,
|
||||
stringGetter,
|
||||
});
|
||||
|
||||
if (message) {
|
||||
log('useWalletConnection/connectWallet', error, { walletErrorType });
|
||||
setSelectedWalletError(message);
|
||||
}
|
||||
log('useWalletConnection/connectWallet', error);
|
||||
setSelectedWalletError(error);
|
||||
}
|
||||
} else {
|
||||
setWalletType(undefined);
|
||||
@@ -216,8 +208,7 @@ export const useWalletConnection = () => {
|
||||
evmAddress,
|
||||
evmAddressWagmi,
|
||||
signerWagmi,
|
||||
publicClientWagmi,
|
||||
|
||||
|
||||
// Wallet connection (Cosmos)
|
||||
dydxAddress,
|
||||
dydxAddressGraz,
|
||||
|
||||
@@ -54,7 +54,7 @@ button:disabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:focus-visible{
|
||||
:focus {
|
||||
outline: var(--color-accent) 1px solid;
|
||||
outline-offset: -1px;
|
||||
/* outline-offset: -2px; */
|
||||
|
||||
@@ -41,7 +41,6 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
slotIcon={displayData.icon}
|
||||
slotTitle={displayData.title}
|
||||
slotDescription={displayData.description}
|
||||
slotCustomContent={displayData.customContent}
|
||||
slotAction={
|
||||
<Button size={ButtonSize.Small} onClick={() => onNotificationAction(notification)}>
|
||||
{displayData.actionDescription}
|
||||
|
||||
@@ -153,7 +153,6 @@ class AbacusStateManager {
|
||||
|
||||
// ------ Set Data ------ //
|
||||
setStore = (store: RootStore) => {
|
||||
this.store = store;
|
||||
this.stateNotifier.setStore(store);
|
||||
};
|
||||
|
||||
|
||||
@@ -72,9 +72,3 @@ export const getTimeTillNextUnit = (unit: 'minute' | 'hour' | 'day') => {
|
||||
};
|
||||
|
||||
export const getTimeString = (time: number) => time.toString().padStart(2, '0');
|
||||
|
||||
export const formatSeconds = (seconds: number) => {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${getTimeString(minutes)}:${getTimeString(remainingSeconds)}`;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
|
||||
|
||||
import {
|
||||
type WalletConnection,
|
||||
wallets,
|
||||
WalletConnectionType,
|
||||
WalletErrorType,
|
||||
WalletType,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
@@ -71,66 +68,3 @@ export const getWalletConnection = ({
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getWalletErrorType = ({ error }: { error: Error }) => {
|
||||
const { message } = error;
|
||||
const messageLower = message.toLowerCase();
|
||||
|
||||
// General - Cancelled
|
||||
if (
|
||||
messageLower.includes('connection request reset') ||
|
||||
messageLower.includes('rejected') ||
|
||||
messageLower.includes('reject') ||
|
||||
messageLower.includes('cancelled') ||
|
||||
messageLower.includes('canceled') ||
|
||||
messageLower.includes('user denied')
|
||||
) {
|
||||
return WalletErrorType.UserCanceled;
|
||||
}
|
||||
|
||||
if (messageLower.includes('chain mismatch')) {
|
||||
return WalletErrorType.ChainMismatch;
|
||||
}
|
||||
|
||||
// ImToken - User canceled
|
||||
if (messageLower.includes('用户取消了操作')) {
|
||||
return WalletErrorType.UserCanceled;
|
||||
}
|
||||
|
||||
if (messageLower.includes('does not support deterministic signing')) {
|
||||
return WalletErrorType.NonDeterministicWallet;
|
||||
}
|
||||
|
||||
return WalletErrorType.Unknown;
|
||||
};
|
||||
|
||||
export const parseWalletError = ({
|
||||
error,
|
||||
stringGetter,
|
||||
}: {
|
||||
error: Error;
|
||||
stringGetter: StringGetterFunction;
|
||||
}) => {
|
||||
const walletErrorType = getWalletErrorType({ error });
|
||||
let message;
|
||||
|
||||
switch (walletErrorType) {
|
||||
case WalletErrorType.ChainMismatch:
|
||||
case WalletErrorType.UserCanceled: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
message = stringGetter({
|
||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||
params: {
|
||||
ERROR_MESSAGE: error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
walletErrorType,
|
||||
message,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ export const DYDXBalancePanel = () => {
|
||||
size={ButtonSize.Small}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Receive }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.RECEIVE })}
|
||||
{stringGetter({ key: STRING_KEYS.EXCHANGE_RECEIVED })}
|
||||
</Styled.ReceiveButton>
|
||||
<Button
|
||||
slotLeft={<Icon iconName={IconName.Send} />}
|
||||
|
||||
@@ -1,42 +1,38 @@
|
||||
/**
|
||||
* @license
|
||||
/*
|
||||
* Legal Disclaimer for Web licence
|
||||
*
|
||||
* Font Family: Satoshi
|
||||
* Designed by: Deni Anggara
|
||||
* URL: https://www.fontshare.com/fonts/satoshi
|
||||
* © 2023 Indian Type Foundry
|
||||
* Licence to use self-hosted webfonts for displaying dynamic text on specified website domains. Our package includes WOFF and WOFF2 font formats.
|
||||
*
|
||||
* Font Styles:
|
||||
* Satoshi Regular
|
||||
* Satoshi Medium
|
||||
* Satoshi Bold
|
||||
* Satoshi Black
|
||||
*/
|
||||
|
||||
* Should the license page views limit be exceeded the license holder will be required to contact Lineto.com for an upgrade.
|
||||
*
|
||||
* It is strictly prohibited to rename the font and to download or use these fonts in any other media.
|
||||
*
|
||||
* These Web fonts are licensed exclusively for the use on the following domain(s) and their subdomains:
|
||||
*
|
||||
* dydx.exchange (monthly pageviews: <50K)
|
||||
*
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/Satoshi-Regular.woff2") format("woff2");
|
||||
font-family: "Satoshi";
|
||||
font-weight: 400;
|
||||
src: url("fonts/CircularXXWeb-Book.woff2") format("woff2");
|
||||
font-family: "Circular";
|
||||
font-weight: 450;
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/Satoshi-Medium.woff2") format("woff2");
|
||||
font-family: "Satoshi";
|
||||
src: url("fonts/CircularXXWeb-Medium.woff2") format("woff2");
|
||||
font-family: "Circular";
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/Satoshi-Bold.woff2") format("woff2");
|
||||
font-family: "Satoshi";
|
||||
src: url("fonts/CircularXXWeb-Bold.woff2") format("woff2");
|
||||
font-family: "Circular";
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
src: url("fonts/Satoshi-Black.woff2") format("woff2");
|
||||
font-family: "Satoshi";
|
||||
font-weight: 900;
|
||||
src: url("fonts/CircularXXMonoWeb-Regular.woff2") format("woff2");
|
||||
font-family: "CircularMono";
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ export const popoverMixins = {
|
||||
/* &:hover, */
|
||||
/* :not(:has(* > [data-highlighted])) > &:hover, */
|
||||
&[data-radix-collection-item]:hover, // @radix-ui/react-navigation-menu
|
||||
/* &:focus-visible, */
|
||||
/* &:focus, */
|
||||
&[aria-selected="true"], // cmdk
|
||||
&[data-highlighted] // @radix-ui
|
||||
{
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
/* Constants */
|
||||
|
||||
:root {
|
||||
--fontFamily-base: "Satoshi", system-ui, -apple-system, Helvetica, Arial, sans-serif;
|
||||
--fontFamily-monospace: Courier, monospace, var(--fontFamily-base);
|
||||
--fontFamily-base: "Circular", system-ui, -apple-system, Helvetica, Arial, sans-serif;
|
||||
--fontFamily-monospace: "CircularMono", Courier, monospace, var(--fontFamily-base);
|
||||
|
||||
--fontWeight-base-0: 450;
|
||||
--fontWeight-base-1: 500;
|
||||
--fontWeight-base-2: 700;
|
||||
--fontWeight-monospace: 400;
|
||||
|
||||
--letter-spacing-base: -0.02em;
|
||||
|
||||
/* New Typography system */
|
||||
|
||||
/**
|
||||
* @size 10px/11px
|
||||
* @weight 400
|
||||
*/
|
||||
--fontSize-tiny: 0.625rem; /* 10px/11px */
|
||||
--fontSize-mini: 0.75rem; /* 12px/13px */
|
||||
--fontSize-small: 0.8438rem; /* 13.5px/14.5px */
|
||||
|
||||
@@ -2,7 +2,7 @@ import { shallowEqual, useSelector } from 'react-redux';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
|
||||
import { STRING_KEYS, type StringKey } from '@/constants/localization';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||
import { breakpoints } from '@/styles';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
@@ -34,7 +34,10 @@ export const MarketDetails: React.FC = () => {
|
||||
initialMarginFraction,
|
||||
maintenanceMarginFraction,
|
||||
minOrderSize,
|
||||
stepSizeDecimals,
|
||||
maxPositionSize,
|
||||
baselinePositionSize,
|
||||
incrementalInitialMarginFraction,
|
||||
incrementalPositionSize,
|
||||
tickSizeDecimals,
|
||||
} = configs;
|
||||
|
||||
@@ -69,28 +72,12 @@ export const MarketDetails: React.FC = () => {
|
||||
key: 'step-size',
|
||||
label: stringGetter({ key: STRING_KEYS.STEP_SIZE }),
|
||||
tooltip: 'step-size',
|
||||
value: (
|
||||
<Output
|
||||
useGrouping
|
||||
value={stepSize}
|
||||
type={OutputType.Asset}
|
||||
tag={symbol}
|
||||
fractionDigits={stepSizeDecimals}
|
||||
/>
|
||||
),
|
||||
value: <Output useGrouping value={stepSize} type={OutputType.Asset} tag={symbol} />,
|
||||
},
|
||||
{
|
||||
key: 'min-order-size',
|
||||
label: stringGetter({ key: STRING_KEYS.MINIMUM_ORDER_SIZE }),
|
||||
value: (
|
||||
<Output
|
||||
useGrouping
|
||||
value={minOrderSize}
|
||||
type={OutputType.Asset}
|
||||
tag={symbol}
|
||||
fractionDigits={stepSizeDecimals}
|
||||
/>
|
||||
),
|
||||
value: <Output useGrouping value={minOrderSize} type={OutputType.Asset} tag={symbol} />,
|
||||
},
|
||||
{
|
||||
key: 'max-leverage',
|
||||
@@ -118,6 +105,36 @@ export const MarketDetails: React.FC = () => {
|
||||
tooltip: 'initial-margin-fraction',
|
||||
value: <Output useGrouping value={initialMarginFraction} type={OutputType.SmallPercent} />,
|
||||
},
|
||||
{
|
||||
key: 'incremental-initial-margin-fraction',
|
||||
label: stringGetter({ key: STRING_KEYS.INCREMENTAL_INITIAL_MARGIN_FRACTION }),
|
||||
value: (
|
||||
<Output
|
||||
useGrouping
|
||||
value={incrementalInitialMarginFraction}
|
||||
type={OutputType.SmallPercent}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'incremental-position-size',
|
||||
label: stringGetter({ key: STRING_KEYS.INCREMENTAL_POSITION_SIZE }),
|
||||
value: (
|
||||
<Output useGrouping value={incrementalPositionSize} type={OutputType.Asset} tag={symbol} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'baseline-position-size',
|
||||
label: stringGetter({ key: STRING_KEYS.BASELINE_POSITION_SIZE }),
|
||||
value: (
|
||||
<Output useGrouping value={baselinePositionSize} type={OutputType.Asset} tag={symbol} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'max-position-size',
|
||||
label: stringGetter({ key: STRING_KEYS.MAXIMUM_POSITION_SIZE }),
|
||||
value: <Output useGrouping value={maxPositionSize} type={OutputType.Asset} tag={symbol} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -133,10 +150,10 @@ export const MarketDetails: React.FC = () => {
|
||||
|
||||
<Styled.MarketDescription>
|
||||
{primaryDescriptionKey && (
|
||||
<p>{stringGetter({ key: STRING_KEYS[primaryDescriptionKey as StringKey] })}</p>
|
||||
<p>{stringGetter({ key: STRING_KEYS[primaryDescriptionKey] })}</p>
|
||||
)}
|
||||
{secondaryDescriptionKey && (
|
||||
<p>{stringGetter({ key: STRING_KEYS[secondaryDescriptionKey as StringKey] })}</p>
|
||||
<p>{stringGetter({ key: STRING_KEYS[secondaryDescriptionKey] })}</p>
|
||||
)}
|
||||
</Styled.MarketDescription>
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ Styled.Popover = styled(Popover)`
|
||||
}
|
||||
`} 0.2s;
|
||||
}
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useInterval } from '@/hooks';
|
||||
|
||||
import { getTimeTillNextUnit, formatSeconds } from '@/lib/timeUtils';
|
||||
import { getTimeTillNextUnit, getTimeString } from '@/lib/timeUtils';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
|
||||
export const NextFundingTimer = () => {
|
||||
@@ -18,7 +18,7 @@ export const NextFundingTimer = () => {
|
||||
type={OutputType.Text}
|
||||
value={
|
||||
secondsLeft !== undefined
|
||||
? formatSeconds(secondsLeft)
|
||||
? `${getTimeString(Math.floor(secondsLeft / 60))}:${getTimeString(secondsLeft % 60)}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
import { Root, Trigger, Content } from '@radix-ui/react-collapsible';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
import { useInterval, useStringGetter } from '@/hooks';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { formatSeconds } from '@/lib/timeUtils';
|
||||
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { WithReceipt } from '@/components/WithReceipt';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
||||
import { LoadingDots } from '@/components/Loading/LoadingDots';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
export const TransferStatusToast = ({
|
||||
toAmount,
|
||||
triggeredAt = Date.now(),
|
||||
status,
|
||||
}: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [secondsLeft, setSecondsLeft] = useState<number | undefined>();
|
||||
|
||||
const type = useMemo(
|
||||
() => (status?.toChain?.chainData?.chainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal'),
|
||||
[status]
|
||||
);
|
||||
|
||||
const updateSecondsLeft = useCallback(() => {
|
||||
const fromChainEta = (status?.fromChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
const toChainEta = (status?.toChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
setSecondsLeft(Math.floor((triggeredAt + fromChainEta + toChainEta - Date.now()) / 1000));
|
||||
}, [status]);
|
||||
|
||||
useInterval({ callback: updateSecondsLeft });
|
||||
|
||||
if (!status) return <LoadingDots size={3} />;
|
||||
|
||||
return (
|
||||
<Styled.Root open={open} onOpenChange={setOpen}>
|
||||
<WithReceipt
|
||||
hideReceipt={!open}
|
||||
side="bottom"
|
||||
slotReceipt={
|
||||
<Styled.Receipt>
|
||||
<TransferStatusSteps status={status} />
|
||||
</Styled.Receipt>
|
||||
}
|
||||
>
|
||||
<Styled.BridgingStatus>
|
||||
<Styled.Status>
|
||||
{stringGetter({
|
||||
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_STATUS : STRING_KEYS.WITHDRAW_STATUS,
|
||||
params: {
|
||||
AMOUNT_USD: <Styled.InlineOutput type={OutputType.Fiat} value={toAmount} />,
|
||||
ESTIMATED_DURATION: (
|
||||
<Styled.InlineOutput
|
||||
type={OutputType.Text}
|
||||
value={formatSeconds(Math.max(secondsLeft || 0, 0))}
|
||||
/>
|
||||
),
|
||||
},
|
||||
})}
|
||||
</Styled.Status>
|
||||
<Styled.Trigger>
|
||||
<Styled.TriggerIcon>
|
||||
<Icon iconName={IconName.Caret} />
|
||||
</Styled.TriggerIcon>
|
||||
{stringGetter({ key: open ? STRING_KEYS.HIDE_DETAILS : STRING_KEYS.VIEW_DETAILS })}
|
||||
</Styled.Trigger>
|
||||
</Styled.BridgingStatus>
|
||||
</WithReceipt>
|
||||
</Styled.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Root = styled(Root)`
|
||||
margin: 0.5rem -1rem -1rem -1rem;
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
`;
|
||||
|
||||
Styled.Receipt = styled.div`
|
||||
padding: 0 1rem;
|
||||
`;
|
||||
|
||||
Styled.BridgingStatus = styled.div`
|
||||
${layoutMixins.flexColumn};
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
gap: 0.5rem;
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Status = styled.div`
|
||||
color: var(--color-text-0);
|
||||
|
||||
font-size: 0.875rem;
|
||||
`;
|
||||
|
||||
Styled.InlineOutput = styled(Output)`
|
||||
display: inline-block;
|
||||
|
||||
color: var(--color-text-1);
|
||||
`;
|
||||
|
||||
Styled.Content = styled(Content)`
|
||||
${layoutMixins.flexColumn};
|
||||
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
`;
|
||||
|
||||
Styled.Step = styled.div`
|
||||
${layoutMixins.row};
|
||||
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Trigger = styled(Trigger)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
color: var(--color-accent);
|
||||
user-select: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.TriggerIcon = styled.span`
|
||||
width: 0.75em;
|
||||
|
||||
display: inline-flex;
|
||||
transition: transform 0.3s var(--ease-out-expo);
|
||||
|
||||
${Styled.Trigger}[data-state='open'] & {
|
||||
rotate: -0.5turn;
|
||||
}
|
||||
`;
|
||||
@@ -1,175 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import { Link } from '@/components/Link';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { LoadingDots } from '@/components/Loading/LoadingDots';
|
||||
import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
type ElementProps = {
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
enum TransferStatusStep {
|
||||
FromChain,
|
||||
Bridge,
|
||||
ToChain,
|
||||
Complete,
|
||||
}
|
||||
|
||||
export const TransferStatusSteps = ({ status }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const { currentStep, steps, type } = useMemo(() => {
|
||||
const routeStatus = status?.routeStatus;
|
||||
const fromChain = status?.fromChain?.chainData?.chainId;
|
||||
const toChain = status?.toChain?.chainData?.chainId;
|
||||
const type = toChain === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal';
|
||||
|
||||
const steps = [
|
||||
{
|
||||
label: stringGetter({
|
||||
key:
|
||||
type === 'deposit' ? STRING_KEYS.INITIATED_DEPOSIT : STRING_KEYS.INITIATED_WITHDRAWAL,
|
||||
}),
|
||||
step: TransferStatusStep.FromChain,
|
||||
link: status?.fromChain?.transactionUrl,
|
||||
},
|
||||
{
|
||||
label: stringGetter({ key: STRING_KEYS.BRIDGING_TOKENS }),
|
||||
step: TransferStatusStep.Bridge,
|
||||
link: status?.axelarTransactionUrl,
|
||||
},
|
||||
{
|
||||
label: stringGetter({
|
||||
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_TO_CHAIN : STRING_KEYS.WITHDRAW_TO_CHAIN,
|
||||
params: {
|
||||
CHAIN: status?.toChain?.chainData?.chainName,
|
||||
},
|
||||
}),
|
||||
step: TransferStatusStep.ToChain,
|
||||
link: status?.toChain?.transactionUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const currentStatus = routeStatus[routeStatus?.length - 1];
|
||||
|
||||
let currentStep = TransferStatusStep.Bridge;
|
||||
|
||||
if (!routeStatus?.length) {
|
||||
currentStep = TransferStatusStep.FromChain;
|
||||
} else if (currentStatus.chainId === toChain) {
|
||||
currentStep =
|
||||
currentStatus.status !== 'success'
|
||||
? TransferStatusStep.ToChain
|
||||
: TransferStatusStep.Complete;
|
||||
} else if (currentStatus.chainId === fromChain && currentStatus.status !== 'success') {
|
||||
currentStep = TransferStatusStep.FromChain;
|
||||
}
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
steps,
|
||||
type,
|
||||
};
|
||||
}, [status, stringGetter]);
|
||||
|
||||
if (!status) return <LoadingDots size={3} />;
|
||||
|
||||
return (
|
||||
<Styled.BridgingStatus>
|
||||
{steps.map((step) => (
|
||||
<Styled.Step key={step.step}>
|
||||
<Styled.row>
|
||||
{step.step === currentStep ? (
|
||||
<Styled.Icon>
|
||||
<Styled.Spinner />
|
||||
</Styled.Icon>
|
||||
) : step.step < currentStep ? (
|
||||
<Styled.Icon state="complete">
|
||||
<Icon iconName={IconName.Check} />
|
||||
</Styled.Icon>
|
||||
) : (
|
||||
<Styled.Icon state="default">{step.step + 1}</Styled.Icon>
|
||||
)}
|
||||
{step.link && currentStep >= step.step ? (
|
||||
<Link href={step.link}>
|
||||
<Styled.Label highlighted={currentStep >= step.step}>
|
||||
{step.label}
|
||||
<Icon iconName={IconName.LinkOut} />
|
||||
</Styled.Label>
|
||||
</Link>
|
||||
) : (
|
||||
<Styled.Label highlighted={currentStep >= step.step}>{step.label}</Styled.Label>
|
||||
)}
|
||||
</Styled.row>
|
||||
</Styled.Step>
|
||||
))}
|
||||
</Styled.BridgingStatus>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.BridgingStatus = styled.div`
|
||||
${layoutMixins.flexColumn};
|
||||
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
`;
|
||||
|
||||
Styled.Step = styled.div`
|
||||
${layoutMixins.spacedRow};
|
||||
`;
|
||||
|
||||
Styled.row = styled.div`
|
||||
${layoutMixins.inlineRow};
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Icon = styled.div<{ state: 'complete' | 'default' }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
|
||||
border-radius: 50%;
|
||||
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
${({ state }) =>
|
||||
({
|
||||
['complete']: css`
|
||||
color: var(--color-positive);
|
||||
`,
|
||||
['default']: css`
|
||||
color: var(--color-text-0);
|
||||
`,
|
||||
}[state])}
|
||||
`;
|
||||
|
||||
Styled.Spinner = styled(LoadingSpinner)`
|
||||
--spinner-width: 1.25rem;
|
||||
|
||||
color: var(--color-accent);
|
||||
`;
|
||||
|
||||
Styled.Label = styled(Styled.row)<{ highlighted?: boolean }>`
|
||||
${({ highlighted }) =>
|
||||
highlighted
|
||||
? css`
|
||||
color: var(--color-text-2);
|
||||
`
|
||||
: css`
|
||||
color: var(--color-text-0);
|
||||
`}
|
||||
`;
|
||||
@@ -32,12 +32,8 @@ export const ChooseWallet = () => {
|
||||
<>
|
||||
{selectedWalletType && selectedWalletError && (
|
||||
<Styled.AlertMessage type={AlertType.Error}>
|
||||
{
|
||||
<h4>
|
||||
Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.
|
||||
</h4>
|
||||
}
|
||||
{selectedWalletError}
|
||||
{<h4>Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.</h4>}
|
||||
<code>{selectedWalletError.message}</code>
|
||||
</Styled.AlertMessage>
|
||||
)}
|
||||
|
||||
|
||||
@@ -25,12 +25,11 @@ import { Switch } from '@/components/Switch';
|
||||
import { WithReceipt } from '@/components/WithReceipt';
|
||||
import { WithTooltip } from '@/components/WithTooltip';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
import { track } from '@/lib/analytics';
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
import { log } from '@/lib/telemetry';
|
||||
import { parseWalletError } from '@/lib/wallet';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
type ElementProps = {
|
||||
status: EvmDerivedAccountStatus;
|
||||
@@ -67,12 +66,8 @@ export const GenerateKeys = ({
|
||||
try {
|
||||
await matchNetwork?.();
|
||||
} catch (error) {
|
||||
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
|
||||
|
||||
if (message) {
|
||||
log('GenerateKeys/switchNetwork', error, { walletErrorType });
|
||||
setError(message);
|
||||
}
|
||||
setError(error.message);
|
||||
log('GenerateKeys/switchNetwork', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -129,12 +124,7 @@ export const GenerateKeys = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const { message } = parseWalletError({ error, stringGetter });
|
||||
|
||||
if (message) {
|
||||
track(AnalyticsEvent.OnboardingWalletIsNonDeterministic);
|
||||
setError(message);
|
||||
}
|
||||
log('GenerateKeys/getSubaccounts', error);
|
||||
}
|
||||
|
||||
await setWalletFromEvmSignature(signature);
|
||||
@@ -150,48 +140,48 @@ export const GenerateKeys = ({
|
||||
setStatus(EvmDerivedAccountStatus.Derived);
|
||||
} catch (error) {
|
||||
setStatus(EvmDerivedAccountStatus.NotDerived);
|
||||
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
|
||||
setError(error?.message);
|
||||
|
||||
if (message) {
|
||||
setError(message);
|
||||
log('GenerateKeys/deriveKeys', error, { walletErrorType });
|
||||
}
|
||||
log('GenerateKeys/deriveKeys', error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Styled.StatusCardsContainer>
|
||||
{[
|
||||
{
|
||||
status: EvmDerivedAccountStatus.Deriving,
|
||||
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
},
|
||||
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
|
||||
status: EvmDerivedAccountStatus.EnsuringDeterminism,
|
||||
title: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_COMPATIBILITY }),
|
||||
description: stringGetter({ key: STRING_KEYS.ENSURES_WALLET_SUPPORT }),
|
||||
},
|
||||
]
|
||||
.filter(isTruthy)
|
||||
.map((step) => (
|
||||
<Styled.StatusCard key={step.status} active={status === step.status}>
|
||||
{status < step.status ? (
|
||||
<LoadingSpinner disabled />
|
||||
) : status === step.status ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Styled.GreenCheckCircle />
|
||||
)}
|
||||
<div>
|
||||
<h3>{step.title}</h3>
|
||||
<p>{step.description}</p>
|
||||
</div>
|
||||
</Styled.StatusCard>
|
||||
))}
|
||||
</Styled.StatusCardsContainer>
|
||||
|
||||
{isMobile && (
|
||||
<Styled.MobileStatusCards>
|
||||
{[
|
||||
{
|
||||
status: EvmDerivedAccountStatus.Deriving,
|
||||
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
},
|
||||
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
|
||||
status: EvmDerivedAccountStatus.EnsuringDeterminism,
|
||||
title: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_COMPATIBILITY }),
|
||||
description: stringGetter({ key: STRING_KEYS.ENSURES_WALLET_SUPPORT }),
|
||||
},
|
||||
]
|
||||
.filter(isTruthy)
|
||||
.map((step) => (
|
||||
<Styled.StatusCard key={step.status} active={status === step.status}>
|
||||
{status < step.status ? (
|
||||
<LoadingSpinner disabled />
|
||||
) : status === step.status ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Styled.GreenCheckCircle />
|
||||
)}
|
||||
<div>
|
||||
<h3>{step.title}</h3>
|
||||
<p>{step.description}</p>
|
||||
</div>
|
||||
</Styled.StatusCard>
|
||||
))}
|
||||
</Styled.MobileStatusCards>
|
||||
)}
|
||||
<Styled.Footer>
|
||||
<Styled.RememberMe htmlFor="remember-me">
|
||||
<WithTooltip withIcon tooltip="remember-me">
|
||||
@@ -261,7 +251,7 @@ export const GenerateKeys = ({
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.StatusCardsContainer = styled.div`
|
||||
Styled.MobileStatusCards = styled.div`
|
||||
display: grid;
|
||||
margin-top: 1rem;
|
||||
gap: 1rem;
|
||||
|
||||
@@ -3,21 +3,16 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { type NumberFormatValues } from 'react-number-format';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
import { parseUnits } from 'viem'
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
import erc20 from '@/abi/erc20.json';
|
||||
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { NumberSign } from '@/constants/numbers';
|
||||
import type { EvmAddress } from '@/constants/wallets';
|
||||
|
||||
import { useAccounts, useDebounce, useStringGetter } from '@/hooks';
|
||||
import { useAccountBalance } from '@/hooks/useAccountBalance';
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
import { NATIVE_TOKEN_ADDRESS, useSquid } from '@/hooks/useSquid';
|
||||
import { useWalletConnection } from '@/hooks/useWalletConnection';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
@@ -37,7 +32,6 @@ import { getTransferInputs } from '@/state/inputsSelectors';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { ChainSelectMenu } from './ChainSelectMenu';
|
||||
import { TokenSelectMenu } from './TokenSelectMenu';
|
||||
@@ -54,17 +48,13 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { evmAddress, signerWagmi } = useAccounts();
|
||||
const { publicClientWagmi } = useWalletConnection();
|
||||
|
||||
const { addTransferNotification } = useLocalNotifications();
|
||||
const { signerWagmi } = useAccounts();
|
||||
|
||||
const {
|
||||
requestPayload,
|
||||
token,
|
||||
chain: chainIdStr,
|
||||
resources,
|
||||
summary,
|
||||
} = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined;
|
||||
|
||||
@@ -74,16 +64,13 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
[token, resources]
|
||||
);
|
||||
|
||||
const sourceChain = useMemo(
|
||||
() => (chainIdStr ? resources?.chainResources?.get(chainIdStr) : undefined),
|
||||
[chainId, resources]
|
||||
);
|
||||
|
||||
const [fromAmount, setFromAmount] = useState('');
|
||||
const [slippage, setSlippage] = useState(0.01); // 1% slippage
|
||||
const debouncedAmount = useDebounce<string>(fromAmount, 500);
|
||||
|
||||
// Async Data
|
||||
const [transactionHash, setTransactionHash] = useState<string>();
|
||||
|
||||
const { balance, queryStatus, isQueryFetching } = useAccountBalance({
|
||||
addressOrDenom: sourceToken?.address || undefined,
|
||||
assetSymbol: sourceToken?.symbol || undefined,
|
||||
@@ -170,39 +157,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
}
|
||||
}, [balance, setFromAmount]);
|
||||
|
||||
const validateTokenApproval = useCallback(async () => {
|
||||
if (!signerWagmi || !publicClientWagmi) throw new Error('Missing signer');
|
||||
if (!sourceToken?.address || !sourceToken.decimals) throw new Error('Missing source token address');
|
||||
if (!sourceChain?.rpc) throw new Error('Missing source chain rpc');
|
||||
if (!requestPayload?.targetAddress) throw new Error('Missing target address');
|
||||
if (!requestPayload?.value) throw new Error('Missing transaction value');
|
||||
if (sourceToken?.address === NATIVE_TOKEN_ADDRESS) return;
|
||||
|
||||
const allowance = await publicClientWagmi.readContract({
|
||||
address: sourceToken.address as EvmAddress,
|
||||
abi: erc20,
|
||||
functionName: 'allowance',
|
||||
args: [evmAddress as EvmAddress, requestPayload.targetAddress as EvmAddress]
|
||||
});
|
||||
|
||||
const sourceAmountBN = parseUnits(debouncedAmount, sourceToken.decimals);
|
||||
|
||||
if (sourceAmountBN > (allowance as bigint)) {
|
||||
const { request } = await publicClientWagmi.simulateContract({
|
||||
account: evmAddress,
|
||||
address: sourceToken.address as EvmAddress,
|
||||
abi: erc20,
|
||||
functionName: 'approve',
|
||||
args: [requestPayload.targetAddress as EvmAddress, sourceAmountBN],
|
||||
})
|
||||
|
||||
const approveTx = await signerWagmi.writeContract(request);
|
||||
await publicClientWagmi.waitForTransactionReceipt({
|
||||
hash: approveTx,
|
||||
})
|
||||
}
|
||||
}, [signerWagmi, sourceToken, sourceChain, requestPayload, publicClientWagmi]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
try {
|
||||
@@ -224,32 +178,27 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
await validateTokenApproval();
|
||||
|
||||
let tx = {
|
||||
to: requestPayload.targetAddress as EvmAddress,
|
||||
data: requestPayload.data as EvmAddress,
|
||||
gasLimit: BigInt(requestPayload.gasLimit),
|
||||
to: requestPayload.targetAddress as `0x${string}`,
|
||||
data: requestPayload.data as `0x${string}`,
|
||||
gasLimit: ethers.toBigInt(requestPayload.gasLimit),
|
||||
value:
|
||||
requestPayload.routeType !== 'SEND' ? BigInt(requestPayload.value) : undefined,
|
||||
requestPayload.routeType !== 'SEND' ? ethers.toBigInt(requestPayload.value) : undefined,
|
||||
};
|
||||
const txHash = await signerWagmi.sendTransaction(tx);
|
||||
|
||||
onDeposit?.();
|
||||
|
||||
if (txHash) {
|
||||
addTransferNotification({
|
||||
txHash: txHash,
|
||||
setTransactionHash(txHash);
|
||||
abacusStateManager.setTransferStatus({
|
||||
hash: txHash,
|
||||
toChainId: TESTNET_CHAIN_ID,
|
||||
fromChainId: chainIdStr || undefined,
|
||||
toAmount: summary?.usdcSize || undefined,
|
||||
triggeredAt: Date.now(),
|
||||
fromChainId: chainId?.toString(),
|
||||
});
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
setFromAmount('');
|
||||
}
|
||||
} catch (error) {
|
||||
log('DepositForm/onSubmit', error);
|
||||
setError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -337,7 +286,17 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
}
|
||||
/>
|
||||
</Styled.WithDetailsReceipt>
|
||||
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
|
||||
{errorMessage ? (
|
||||
<AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>
|
||||
) : (
|
||||
transactionHash && (
|
||||
<AlertMessage type={AlertType.Success}>
|
||||
<Styled.TransactionInfo>
|
||||
{stringGetter({ key: STRING_KEYS.DEPOSIT_IN_PROGRESS })}
|
||||
</Styled.TransactionInfo>
|
||||
</AlertMessage>
|
||||
)
|
||||
)}
|
||||
<DepositButtonAndReceipt
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { STRING_KEYS } from '@/constants/localization';
|
||||
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||
|
||||
import { useDebounce, useStringGetter, useSubaccount } from '@/hooks';
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
@@ -64,9 +63,9 @@ export const WithdrawForm = () => {
|
||||
[token, resources]
|
||||
);
|
||||
|
||||
const { addTransferNotification } = useLocalNotifications();
|
||||
|
||||
// Async Data
|
||||
const [transactionHash, setTransactionHash] = useState<string>();
|
||||
|
||||
const debouncedAmountBN = MustBigNumber(debouncedAmount);
|
||||
const withdrawAmountBN = MustBigNumber(withdrawAmount);
|
||||
const freeCollateralBN = MustBigNumber(freeCollateral?.current);
|
||||
@@ -134,12 +133,11 @@ export const WithdrawForm = () => {
|
||||
if (txHash?.hash) {
|
||||
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
|
||||
|
||||
addTransferNotification({
|
||||
txHash: hash,
|
||||
setTransactionHash(hash);
|
||||
abacusStateManager.setTransferStatus({
|
||||
hash,
|
||||
fromChainId: TESTNET_CHAIN_ID,
|
||||
toChainId: chainIdStr || undefined,
|
||||
toAmount: debouncedAmountBN.toNumber(),
|
||||
triggeredAt: Date.now(),
|
||||
});
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
setWithdrawAmount('');
|
||||
@@ -150,7 +148,7 @@ export const WithdrawForm = () => {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[requestPayload, debouncedAmountBN, chainIdStr]
|
||||
[setTransactionHash, requestPayload, debouncedAmountBN, chainIdStr]
|
||||
);
|
||||
|
||||
const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -293,7 +291,17 @@ export const WithdrawForm = () => {
|
||||
}
|
||||
/>
|
||||
</Styled.WithDetailsReceipt>
|
||||
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
|
||||
{errorMessage ? (
|
||||
<AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>
|
||||
) : (
|
||||
transactionHash && (
|
||||
<AlertMessage type={AlertType.Success}>
|
||||
<Styled.TransactionInfo>
|
||||
{stringGetter({ key: STRING_KEYS.WITHDRAW_IN_PROGRESS })}
|
||||
</Styled.TransactionInfo>
|
||||
</AlertMessage>
|
||||
)
|
||||
)}
|
||||
<WithdrawButtonAndReceipt
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -84,7 +84,7 @@ export const TransferForm = ({
|
||||
const { dydxAddress } = useAccounts();
|
||||
const { address: recipientAddress, size } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const { transfer, simulateTransfer } = useSubaccount();
|
||||
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
|
||||
const { nativeTokenBalance } = useAccountBalance();
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
|
||||
// User Input
|
||||
@@ -104,8 +104,6 @@ export const TransferForm = ({
|
||||
.toNumber();
|
||||
const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size;
|
||||
|
||||
const showNotEnoughGasWarning = fees && asset === DydxChainAsset.USDC && usdcBalance < fees;
|
||||
|
||||
// BN
|
||||
const amountBN = MustBigNumber(amount);
|
||||
const balanceBN = MustBigNumber(balance);
|
||||
@@ -361,15 +359,6 @@ export const TransferForm = ({
|
||||
/>
|
||||
</WithDetailsReceipt>
|
||||
|
||||
{showNotEnoughGasWarning && (
|
||||
<AlertMessage type={AlertType.Warning}>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.TRANSFER_INSUFFICIENT_GAS,
|
||||
params: { USDC_BALANCE: `(${usdcBalance})` },
|
||||
})}
|
||||
</AlertMessage>
|
||||
)}
|
||||
|
||||
{error && <AlertMessage type={AlertType.Error}>{error.message}</AlertMessage>}
|
||||
|
||||
<Styled.Footer>
|
||||
|
||||
@@ -89,9 +89,9 @@ export const NotificationsMenu = ({
|
||||
.map(({ notification, key, displayData }) => ({
|
||||
value: key,
|
||||
label: displayData.title ?? '',
|
||||
description: displayData.customMenuContent || displayData.description,
|
||||
slotBefore: !displayData.customMenuContent && displayData.icon,
|
||||
slotAfter: !displayData.customMenuContent && (
|
||||
description: displayData.description,
|
||||
slotBefore: displayData.icon,
|
||||
slotAfter: (
|
||||
<>
|
||||
<$Output
|
||||
type={OutputType.RelativeTime}
|
||||
|
||||
@@ -510,7 +510,7 @@ Styled.OrderbookTable = styled(OrderbookTradesTable)<StyleProps>`
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:not(:active):is(:focus-visible, :focus-within) {
|
||||
&:not(:active):is(:focus, :focus-within) {
|
||||
${orderbookMixins.scrollSnapItem}
|
||||
z-index: 2;
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ export const OrderbookTradesTable = styled(Table)<{ histogramSide: 'left' | 'rig
|
||||
}
|
||||
|
||||
tbody tr:hover,
|
||||
tr:focus-visible,
|
||||
td:focus-visible {
|
||||
tr:focus,
|
||||
td:focus {
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-layer-3);
|
||||
}
|
||||
|
||||