Compare commits

...
Author SHA1 Message Date
John Huang 9ecb54ee0c lint 2024-02-08 10:40:41 -08:00
aleka 3a3f817501 wrap details item + fix cancel reason overflow/overlay (#284)
* wrap details item

* more details nits
2024-02-08 13:10:42 -05:00
Jared Vu 5fb2f278da Liquid staking language and localization (#289)
* ✏️ Liquid staking language and localization

* import nits
2024-02-08 06:44:50 -08:00
Jared Vu bd788f22e8 Restore env format (#288)
* restore formatting

* copy from prev commit
2024-02-07 14:22:12 -08:00
Jared Vu 1a41ccaf2a Liquid Staking CTA (#281)
* 🚧 feat(token-page): Scaffold sidebar components for token page

* 🚧 feat(staking/governance): Scaffold adding Panels to Staking and Governance page

*  Update and add icons

* 🧱 Add strideZoneApp to shared config

* 🧱 Add dialogs, update ExternalLinkDialog

* 🚧 Add New Panels/Update paths

* Fix mobile Profile Panel

* 💄 fix desktop padding

*  Add New tag

* 💄 Highlight tag on StrideStakingPanel

* 💄 Single columns for panels on mobile breakpoint

* 💄 fix import nits and alignment nits on panels

* Add circular keplr icon
2024-02-07 11:17:54 -08:00
Jared Vu 26b426c9e9 fix(ladle): Fix ladle environment and parameter types in stories (#286) 2024-02-07 11:02:26 -08:00
John Huang 994ba832f6 Feature/mob 162 smartbanner (#274)
* Implemented according to instructions but not working

* Exclude touch screen windows (see in Issues on library repo)

* Make the banner generic

* Moved icon_url to generic section

* Hide in Safari

* Use injection script with ENV variables instead manual update

* debugging

* Use CDN for js and css

* PR
2024-02-07 10:10:22 -08:00
Jared Vu 597456a0d1 JUP Asset details (#285)
*  add JUP assets

* localize eg
2024-02-07 09:49:46 -08:00
Bill 3b75e60bd2 Add custom amplitude server URL (#282)
* Add custom amplitude server URL

* string

* add to readme
2024-02-06 21:43:05 -08:00
aleka e453977299 bump localization (#283) 2024-02-06 17:23:00 -05:00
moo-onthelawn dbc34599a6 TRCL-3543 Create red/green color tokens and audit (replace) usages of error/success color tokens (#271)
* audited

* fix double header
2024-02-06 13:38:13 -05:00
aleka f762e9bfac add featureFlags to env config for reduce only + remove mobile sign in flag (#278)
* add featureFlags to env config

* update abacus and remove mobilesignin test flag
2024-02-06 12:31:08 -05:00
Bill ed3c336cc0 Add memo to transactions for onchain tracking (#279) 2024-02-06 09:27:10 -08:00
Rui b4f9f03bdc Add Digital Asset Links for android app (#280) 2024-02-06 09:09:02 -08:00
aleka 44ed52d5e2 add more controlled pagination to table (#276) 2024-02-06 11:48:49 -05:00
Jonathan Fung 42595a3667 Revert "Bump abacus to enable ROC (#275)" (#277)
This reverts commit 2fc55bbe9f.
2024-02-05 17:25:29 -07:00
moo-onthelawn 2fc55bbe9f Bump abacus to enable ROC (#275)
* bump

* fixed
2024-02-05 16:39:33 -07:00
aleka 97452d7785 Add historical trading rewards gated by feature flag (#260)
* add historical trading rewards gated by feature flag
2024-02-05 12:24:12 -05:00
moo-onthelawn 2704baf378 Update PoweredBy string key to use all caps version (#272)
* bump package + update stringkey

* styling
2024-02-05 10:22:00 -05:00
Jared Vu 453d7c545e Convert opensource CSVs to JSON (#273)
* 🧱 use json instead of csv

* Fixed code size in MessageDetailsDialog
2024-02-02 22:01:18 -08:00
aleka f79c062808 use updated isaccountconnected check (#268)
* use updated isaccountconnected check

* address feedback
2024-02-02 15:12:52 -05:00
moo-onthelawn 7d30c80320 fix (#269) 2024-02-02 09:56:57 -05:00
moo-onthelawn 1c1d602bf3 TRCL-3491 Add "System" setting to themeing options, and match cmd+k dialog to new display preferences (#265)
* clean up

* clean up UI

* add functionality, without user exposure yet

* fix commit history

* remove unnecessary change

* lint

* implemented functionality

* clean up, add into cmd+k

* fix imports

* TRCL-3537 DisplaySettings + Themeing Polish (#267)

* wip

* make system panel reflect user prefs

* clean up PnlChart

* testing changes removal

* clean up logo styling

* fix export of dark dots background

* remove comment

* fix system panel color background
2024-02-01 20:03:10 -05:00
moo-onthelawn 23a96c1c20 TRCL-3533 Add green/red preference functionality to themeing (#256)
* add functionality, without user exposure yet

* TRCL-3535 Add green/red preference to display modal (#261)

* add functionality, without user exposure yet

* fix commit history

* remove unnecessary change

* lint

* update css

* audit of pos/neg colors

* fix flipping of disconnect/export keys colors

* review comments

* clean up naming of icon

* fix icon sizing

* small edit to story example
2024-02-01 19:29:11 -05:00
moo-onthelawn d81e6b3a2c TRCL-3520 Add entry point + modal for themeing (#255)
* wip

* clean up

* clean up UI
2024-02-01 19:01:53 -05:00
Jared Vu 732114a8de Use BigInt toString() for initialDepositAmount param (#266) 2024-02-01 11:52:04 -08:00
Jared Vu 2c220233e3 Update potential-market csv (#264)
* Update potential-market csv to match opensource guidance

* missing quantom-conversion-exponent

* fix filter

* alphabetize
2024-01-31 10:07:21 -08:00
aleka 8187e3615b add back clickable label to checkbox (#263)
* add back clickable label to checkbox

* make disabled optional
2024-01-30 16:42:05 -05:00
Bill c5888c2d5c Fix if conditioned before hooks (#262) 2024-01-30 13:18:10 -08:00
Jared VuandTaehoon Lee 793b522487 New Market Widget (#234)
* 🚧 New Market Form

* use dev-5 as default

* Additional UI work

* Add mock data

* 💄 More UI items

* 💄 add preview step

* 💄 Disable proposal button if not enough native tokens

* ✏️ Add disclaimer

* ✏️ fix combobox search

* 🚧 clean up components

* Add filters, modify, button

*  feat: Add details to New Market Dialog

* add assetName

* add helper method - spagetti code

* Update NewMarketMessageDetailsDialog, attempt to hook up client call

* 🚨 fix mobile safari overflow

* update init deposit to 10_001 whole token

* reduce delay block to 5

* Update mock data

* 🚧 SO FRIGGIN CLOSE

* 💄 style/ux nits

* add gov to registry

* PLS

* IT FUCKING WORKS

* Add assets

* FIX TICKER

* ADD NEW ASSETICON

* button width

* change default env to dev

* Remove mention of Impersonation dialog

* Market Search entry point

* uncomment feature

* Clean up NewMarketStep components

* Restore env.json

* Add space T.T

* useGlobalCommands fix types

* 🚧 feat: useNextClobPairId hook WIP

* Add potentialMarkets hook to parse CSV and hide new market entrypoints

* Use updated stringKeys

* Update localization, import nits

* bump v4-client

* add gov vars

* new useGovernanceVariables

* Add validator client calls: proposal fetch/submission

* Update token usage, utilize gov vars

* remove console log

* import nits

* NewMarketMessageDetailsDialog: Fix initial_deposit_amount

* NewMarketAgreement Dialog

* confirm flow

* Remove initialDepositAmount from mainnet env

* NewMarket: Add stringParams to step3

* Update csv

* update env.json add localization changes

* cleanup initialDepositAmountBN and decimals

* ^

* use undefined in place of 0 for DiffOutput

* remove hardcoded string

* Remove potentialMarket from csv

* Ensure user is out of liquidity tier modification

* bump localization, add additional details to receipts

* feedback addressed

* Add margin instead of space

* margin/padding nits, shorten filter method, remove ?. chaining

* additional feedback

---------

Co-authored-by: Taehoon Lee <19664986+ttl33@users.noreply.github.com>
2024-01-30 11:59:16 -08:00
John Huang 13d6610492 Added a TestFlight deployment so help native app testers (#257) 2024-01-30 11:38:39 -08:00
Bill 9cbb9fc2c6 remove cexdeposit testflag (#259) 2024-01-30 11:38:22 -08:00
Bill 91f5b89eb6 Coinbase Deposit (#251)
* Coinbase Deposit

* bump packages

* fix lock

* add testflag

* Address feedback

* fix chain select
2024-01-29 15:12:06 -08:00
aleka 41cc531700 downgrade follow-redirects since new version breaks safari (#254)
* downgrade follow-redirects

* explicit override
2024-01-29 11:53:38 -05:00
Brendan Chou 5aeb01f968 allow viewer to use the address test-flag to have a view-only experience of a wallet (#250)
* allow viewer to use  test-flag to specify a view-only experience of a wallet

* respond to CR

* lint
2024-01-26 17:08:51 -05:00
moo-onthelawn c9596de9c3 TRCL-3322 Implement reduce-only checkbox on frontend (#246)
* implemented

* eol lint again

* update with correct abacus version

* update localization to latest

* fix localization v
2024-01-26 17:03:32 -05:00
Bill cc8c724f09 bump v4-client (#252) 2024-01-26 15:38:47 -05:00
aleka ac07abea07 make sure view more is hidden when not num rows not specified (#249) 2024-01-25 15:10:16 -05:00
Rui 54e81cca80 Change iOS app scheme to dydx-t-v4 (#248) 2024-01-25 09:19:49 -08:00
Jared Vu 0ed1f33808 Canvas Orderbook (#227)
* 🚧 Canvas Orderbook

* 🚧 Add orderbookMap to aid in Canvas redraws

* enable mine column

* Merge main into canvas-orderbook-2

* 🧱 fix: Add orderbook constants file, remove console log, nits

* 💄 style: orderbook canvas text 13.5px -> 12px

* Updated constants and typings

* loadingSpace logic was slowing down ref mount
2024-01-24 23:16:33 -08:00
Bill 48e67f5681 Upgrade v4 clients and @cosmjs packages (#243)
* Upgrade v4-client and @cosmjs packages

* add comments
2024-01-24 15:29:43 -08:00
mike-dydx da1ac0d087 Update ios app scheme in env.json (#247) 2024-01-24 15:28:11 -08:00
moo-onthelawn 17fce5417a TRCL-3476 Create colorTokens file + new theme color types (#239)
* introduce tokens file + theme type

* fix lint error

* TRCL-3497 Use color tokens in app + tradingView widget (#240)

* update to use tokens, small fixes

* fix eol lint

* move usage style helper to lib/styles

* move files, fix text colors

* fix logo in light mode

* remove colors.css file

* small lint things

* add transparency to button + destructive borders
2024-01-24 17:03:44 -05:00
aleka b86a346be3 move governance staking panels out of trading rewards to profile on mobile (#242)
* add collapsible table

* update to add a TR inside table instead

* move governance staking panels to mobile profile
2024-01-24 15:33:01 -05:00
aleka 5cecc40a1c fix http->https in validator v4dev4 url (#245)
* fix http->https in validator v4dev4 url

* add faucet url
2024-01-24 15:32:40 -05:00
moo-onthelawn 7134fa541d add icons and update stories (#244) 2024-01-24 15:07:32 -05:00
aleka cd30c9c7d9 add collapsible table (#231)
* add collapsible table

* update to add a TR inside table instead
2024-01-24 12:26:32 -05:00
aleka 414e38afec Delay wagmi connection if remember me is enabled (#210)
* connect wagmi on deposit if remember me

* use alert message instead of button message

* clear input state when closing transfers forms to renable trade place order button
2024-01-23 13:45:14 -05:00
207 changed files with 55720 additions and 2317 deletions
+7
View File
@@ -8,7 +8,14 @@ VITE_V3_TOKEN_ADDRESS=
VITE_TOKEN_MIGRATION_URI=
AMPLITUDE_API_KEY=
AMPLITUDE_SERVER_URL=
BUGSNAG_API_KEY=
IOS_APP_ID=
INTERCOM_APP_ID=
STATUS_PAGE_SCRIPT_URI=
SMARTBANNER_APP_NAME=
SMARTBANNER_ORG_NAME=
SMARTBANNER_ICON_URL=
SMARTBANNER_APPSTORE_URL=
SMARTBANNER_GOOGLEPLAY_URL=
+1
View File
@@ -39,6 +39,7 @@ jobs:
pnpm run build:inject-amplitude
pnpm run build:inject-bugsnag
pnpm run build:inject-statuspage
pnpm run build:inject-smartbanner
sh scripts/inject-app-deeplinks.sh
- name: Upload to IPFS via web3.storage
+91 -33
View File
@@ -2,74 +2,132 @@ import '@/polyfills';
import { useEffect, useState } from 'react';
import { Provider } from 'react-redux';
import styled from 'styled-components';
import { WagmiConfig } from 'wagmi';
import { GrazProvider } from 'graz';
import { QueryClient, QueryClientProvider } from 'react-query';
import { store } from '@/state/_store';
import { SupportedLocales } from '@/constants/localization';
import { AccountsProvider } from '@/hooks/useAccounts';
import { AppThemeAndColorModeProvider } from '@/hooks/useAppThemeAndColorMode';
import { DydxProvider } from '@/hooks/useDydxClient';
import { DialogAreaProvider } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { PotentialMarketsProvider } from '@/hooks/usePotentialMarkets';
import { RestrictionProvider } from '@/hooks/useRestrictions';
import { SubaccountProvider } from '@/hooks/useSubaccount';
import { GlobalStyle } from '@/styles/globalStyle';
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
import { setLocaleLoaded } from '@/state/localization';
import {
AppTheme,
AppThemeSystemSetting,
AppColorMode,
setAppThemeSetting,
setAppColorMode,
} from '@/state/configs';
import { setLocaleLoaded, setSelectedLocale } from '@/state/localization';
import { store } from '@/state/_store';
import { config } from '@/lib/wagmi';
import '@/index.css';
import './ladle.css';
const queryClient = new QueryClient();
const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
// eslint-disable-next-line react/display-name
return ({ children }: { children: React.ReactNode }) => (
<Component {...props}>{children}</Component>
);
};
const providers = [
wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider),
wrapProvider(AccountsProvider),
wrapProvider(SubaccountProvider),
wrapProvider(DialogAreaProvider),
wrapProvider(PotentialMarketsProvider),
wrapProvider(AppThemeAndColorModeProvider),
];
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState('Default theme');
const [theme, setTheme] = useState(AppTheme.Classic);
const [colorMode, setColorMode] = useState(AppColorMode.GreenUp);
useEffect(() => {
switch (theme) {
case 'Dark theme': {
document?.documentElement?.classList.remove('theme-light');
document?.documentElement?.classList.add('theme-dark');
break;
}
case 'Light theme': {
document?.documentElement?.classList.remove('theme-dark');
document?.documentElement?.classList.add('theme-light');
break;
}
default: {
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
break;
}
}
}, [theme]);
store.dispatch(setAppThemeSetting(theme));
store.dispatch(setAppColorMode(colorMode));
}, [theme, colorMode]);
useEffect(() => {
store.dispatch(setSelectedLocale({ locale: SupportedLocales.EN }));
store.dispatch(setLocaleLoaded(true));
}, []);
const content = [...providers].reverse().reduce(
(children, Provider) => {
return <Provider>{children}</Provider>;
},
<StoryContent>
<GlobalStyle />
{children}
</StoryContent>
);
return (
<Provider store={store}>
<StoryHeader>
<h4>Active Theme:</h4>
<SelectMenu
value={theme}
onValueChange={setTheme}
>
<SelectMenu value={theme} onValueChange={setTheme}>
{[
{
value: 'Default theme',
value: AppTheme.Classic,
label: 'Default theme',
},
{
value: 'Dark theme',
value: AppThemeSystemSetting.System,
label: 'System theme',
},
{
value: AppTheme.Dark,
label: 'Dark theme',
},
{
value: 'Light theme',
value: AppTheme.Light,
label: 'Light theme',
},
].map(({ value, label }) => (
<SelectItem
key={value}
value={value}
label={label}
/>
<SelectItem key={value} value={value} label={label} />
))}
</SelectMenu>
<h4>Active Color Mode:</h4>
<SelectMenu value={colorMode} onValueChange={setColorMode}>
{[
{
value: AppColorMode.GreenUp,
label: 'Green up',
},
{
value: AppColorMode.RedUp,
label: 'Red up',
},
].map(({ value, label }) => (
<SelectItem key={value} value={value} label={label} />
))}
</SelectMenu>
</StoryHeader>
<hr />
<StoryContent>{children}</StoryContent>
{content}
</Provider>
);
};
+5 -1
View File
@@ -78,11 +78,12 @@ Set environment variables via `.env`.
- `VITE_V3_TOKEN_ADDRESS` (optional): Address of the V3 $DYDX token.
- `VITE_TOKEN_MIGRATION_URI` (optional): The URL of the token migration website.
- `AMPLITUDE_API_KEY` (optional): Amplitude API key for enabling Amplitude; used with `pnpm run build:inject-amplitude`.
- `AMPLITUDE_SERVER_URL` (optional): Custom Amplitude server URL
- `BUGSNAG_API_KEY` (optional): API key for enabling Bugsnag; used with `pnpm run build:inject-bugsnag`.
- `IOS_APP_ID` (optional): iOS app ID used for enabling deep linking to the iOS app; used with `pnpm run build:inject-app-deeplinks`.
- `INTERCOM_APP_ID` (optional): Used for enabling Intercom; utilized with `pnpm run build:inject-intercom`.
- `STATUS_PAGE_SCRIPT_URI` (optional): Used for enabling the status page; used with `pnpm run build:inject-statuspage`.
- `SMARTBANNER_APP_NAME`, `SMARTBANNER_ORG_NAME`, `SMARTBANNER_ICON_URL`, `SMARTBANNER_APPSTORE_URL` (optional): Used for enabling the smart app banner; used with `pnpm run build:inject-smartbanner`.
# Deployments
@@ -106,6 +107,9 @@ pnpm run build --mode testnet
If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our scripts:
`pnpm run build:inject-amplitude` and `pnpm run build:inject-bugsnag`. You will need to provide your own API keys for these services. In the Environment Variables section, name the variables as `AMPLITUDE_API_KEY` and `BUGSNAG_API_KEY` and provide the respective keys as their values.
If you wish to incorporate smart banner for iOS and/or Android apps, you can use our scripts:
`pnpm run build:inject-smartbanner`. You will need to provide your own app configurations for these services. In the Environment Variables section, name the variables as `SMARTBANNER_APP_NAME`, `SMARTBANNER_ORG_NAME`, `SMARTBANNER_ICON_URL` and `SMARTBANNER_APPSTORE_URL` or `SMARTBANNER_GOOGLEPLAY_URL` and provide the respective values.
For more details, check out Vercel's [official documentation](https://vercel.com/docs).
## Deploying to IPFS
+17 -10
View File
@@ -15,6 +15,7 @@
"build:inject-bugsnag": "node scripts/inject-bugsnag.js",
"build:inject-intercom": "node scripts/inject-intercom.js",
"build:inject-statuspage": "node scripts/inject-statuspage.js",
"build:inject-smartbanner": "node scripts/inject-smartbanner.js",
"deploy:ipfs": "node scripts/upload-ipfs.js --verbose",
"deploy:update-ipns": "node scripts/update-ipns.js",
"deploy:update-dnslink": "node scripts/update-dnslink.js",
@@ -33,15 +34,15 @@
"packageManager": "pnpm@8.6.6",
"dependencies": {
"@0xsquid/sdk": "^1.10.0",
"@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/v4-abacus": "^1.2.5",
"@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.11",
"@cosmjs/amino": "^0.32.1",
"@cosmjs/crypto": "^0.32.1",
"@cosmjs/encoding": "^0.32.1",
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "^1.4.2",
"@dydxprotocol/v4-client-js": "^1.0.20",
"@dydxprotocol/v4-localization": "^1.1.26",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
@@ -92,6 +93,7 @@
"buffer": "^6.0.3",
"cmdk": "^0.2.0",
"color": "^4.2.3",
"cosmjs-types": "^0.9.0",
"crypto-js": "^4.1.1",
"ethers": "^6.6.1",
"graz": "^0.0.43",
@@ -116,7 +118,7 @@
},
"devDependencies": {
"@babel/core": "^7.22.5",
"@ladle/react": "^2.15.0",
"@ladle/react": "^4.0.2",
"@types/color": "^3.0.3",
"@types/crypto-js": "^4.1.1",
"@types/luxon": "^3.3.0",
@@ -155,5 +157,10 @@
"vitest": "^0.32.2",
"w3name": "^1.0.8",
"web3.storage": "^4.5.4"
},
"pnpm": {
"overrides": {
"follow-redirects": "1.15.3"
}
}
}
+2017 -773
View File
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "exchange.dydx.trading.debug",
"sha256_cert_fingerprints": [
"8A:9C:CC:49:B0:35:9A:91:67:CB:98:B0:B5:87:92:5F:9E:B7:EF:CE:A0:47:57:85:A4:35:3E:0C:E1:56:9E:A2"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "exchange.dydx.trading",
"sha256_cert_fingerprints": [
"B2:2D:CC:27:9D:52:05:98:63:C9:7B:34:36:70:A3:8E:00:31:28:08:2D:2E:70:76:C9:31:AE:F9:55:21:15:A5"
]
}
}
]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

+19
View File
@@ -0,0 +1,19 @@
<svg width="120" height="97" viewBox="0 0 120 97" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="8" width="120" height="1" fill="currentColor"/>
<rect y="18" width="120" height="1" fill="currentColor"/>
<rect y="28" width="120" height="1" fill="currentColor"/>
<rect y="38" width="120" height="1" fill="currentColor"/>
<rect y="48" width="120" height="1" fill="currentColor"/>
<rect y="58" width="120" height="1" fill="currentColor"/>
<rect y="68" width="120" height="1" fill="currentColor"/>
<rect y="78" width="120" height="1" fill="currentColor"/>
<rect y="88" width="120" height="1" fill="currentColor"/>
<rect x="18" width="1" height="97" fill="currentColor"/>
<rect x="32" width="1" height="97" fill="currentColor"/>
<rect x="46" width="1" height="97" fill="currentColor"/>
<rect x="60" width="1" height="97" fill="currentColor"/>
<rect x="74" width="1" height="97" fill="currentColor"/>
<rect x="88" width="1" height="97" fill="currentColor"/>
<rect x="102" width="1" height="97" fill="currentColor"/>
<path d="M0 0H120V97H0V0Z" fill="url(#paint0_radial_314_37586)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+20
View File
@@ -0,0 +1,20 @@
<svg width="90" height="39" viewBox="0 0 90 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="31.2344" width="2.57142" height="7.71427" fill="#3ED9A4"/>
<rect x="5.14258" y="28.6648" width="2.57142" height="5.14284" fill="#E45555"/>
<rect x="25.7139" y="26.0923" width="2.57142" height="10.2857" fill="#E45555"/>
<rect x="51.4283" y="28.6648" width="2.57142" height="5.14284" fill="#E45555"/>
<rect x="56.5712" y="31.2344" width="2.57142" height="5.14284" fill="#E45555"/>
<rect x="77.1426" y="18.3767" width="2.57142" height="5.14284" fill="#E45555"/>
<rect x="41.1427" y="23.5198" width="2.57142" height="5.14284" fill="#E45555"/>
<rect x="10.2856" y="28.6648" width="2.57142" height="10.2857" fill="#3ED9A4"/>
<rect x="61.7143" y="23.5198" width="2.57142" height="10.2857" fill="#3ED9A4"/>
<rect x="66.8569" y="20.9492" width="2.57142" height="5.14284" fill="#3ED9A4"/>
<rect x="71.9997" y="13.2346" width="2.57142" height="7.71427" fill="#3ED9A4"/>
<rect x="82.2856" y="10.6631" width="2.57142" height="10.2857" fill="#3ED9A4"/>
<rect x="87.4287" y="0.37793" width="2.57142" height="15.4285" fill="#3ED9A4"/>
<rect x="15.4284" y="26.0923" width="2.57142" height="5.14284" fill="#3ED9A4"/>
<rect x="20.5714" y="23.5198" width="2.57142" height="5.14284" fill="#3ED9A4"/>
<rect x="30.857" y="31.2344" width="2.57142" height="5.14284" fill="#3ED9A4"/>
<rect x="35.9999" y="26.0923" width="2.57142" height="5.14284" fill="#3ED9A4"/>
<rect x="46.2853" y="26.0923" width="2.57142" height="2.57142" fill="#3ED9A4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 2.2 MiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 2.1 MiB

+192 -19
View File
@@ -1,7 +1,7 @@
{
"apps": {
"ios": {
"scheme": "dydxv4"
"scheme": "dydx-t-v4"
}
},
"deployments": {
@@ -11,6 +11,13 @@
],
"default": "dydxprotocol-mainnet"
},
"TESTFLIGHT": {
"environments": [
"dydxprotocol-mainnet",
"dydxprotocol-testnet"
],
"default": "dydxprotocol-mainnet"
},
"TESTNET": {
"environments": [
"dydxprotocol-testnet"
@@ -81,14 +88,15 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -109,6 +117,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-dev-2": {
@@ -154,14 +172,15 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -182,6 +201,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-dev-4": {
@@ -215,10 +244,11 @@
}
],
"validators": [
"http://validator.v4dev4.dydx.exchange"
"https://validator.v4dev4.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4dev4.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -227,14 +257,15 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -255,6 +286,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-dev-5": {
@@ -300,14 +341,15 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -328,6 +370,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-staging": {
@@ -377,12 +429,13 @@
"blogs": "https://www.dydx.foundation/blog",
"help": "https://help.dydx.exchange",
"foundation": "https://www.dydx.foundation",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -403,6 +456,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-staging-forced-update": {
@@ -450,7 +513,8 @@
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
"blogs": "https://www.dydx.foundation/blog",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals"
},
"wallets": {
"walletconnect": {
@@ -473,9 +537,19 @@
"apps": {
"ios": {
"minimalVersion": "1.0",
"build":40000,
"build": 40000,
"url": "https://apps.apple.com/app/dydx/id1564787350"
}
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-staging-west": {
@@ -525,12 +599,13 @@
"blogs": "https://www.dydx.foundation/blog",
"help": "https://help.dydx.exchange",
"foundation": "https://www.dydx.foundation",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -551,6 +626,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": true
}
},
"dydxprotocol-testnet": {
@@ -604,12 +689,13 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -630,6 +716,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-dydx": {
@@ -680,12 +776,13 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -706,6 +803,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-nodefleet": {
@@ -756,12 +863,13 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -782,6 +890,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-kingnodes": {
@@ -832,12 +950,13 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -858,6 +977,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-liquify": {
@@ -908,12 +1037,13 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnMore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -934,6 +1064,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-polkachu": {
@@ -982,7 +1122,8 @@
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
"blogs": "https://www.dydx.foundation/blog",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals"
},
"wallets": {
"walletconnect": {
@@ -1001,6 +1142,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-testnet-bware": {
@@ -1051,12 +1202,13 @@
"blogs": "https://www.dydx.foundation/blog",
"foundation": "https://www.dydx.foundation",
"help": "https://help.dydx.exchange/",
"initialMarginFractionLearnmore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"reduceOnlyLearnmore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"governanceLearnmore": "https://help.dydx.exchange",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"stakingLearnmore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnmore": "https://help.dydx.exchange",
"walletLearnmore": "https://www.dydx.academy/video/defi-wallet"
},
@@ -1077,6 +1229,16 @@
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 10000000,
"delayBlocks": 900,
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
},
"dydxprotocol-mainnet": {
@@ -1125,14 +1287,15 @@
"feedback": "[HTTP link to feedback form, can be null]",
"blogs": "[HTTP link to blogs, can be null]",
"foundation": "[HTTP link to foundation, can be null]",
"initialMarginFractionLearnMore": "[HTTP link to initial margin fraction learn more, can be null]",
"reduceOnlyLearnMore": "[HTTP link to reduce-only learn more, can be null]",
"documentation": "[HTTP link to documentation, can be null]",
"community": "[HTTP link to community, can be null]",
"help": "[HTTP link to help page, can be null]",
"governanceLearnMore": "[HTTP link to governance learn more, can be null]",
"newMarketProposalLearnMore": "[HTTP link to new market proposal learn more, can be null]",
"stakingLearnMore": "[HTTP link to staking learn more, can be null]",
"keplrDashboard": "[HTTP link to keplr dashboard, can be null]",
"strideZoneApp": "[HTTP link to stride zone app, can be null]",
"accountExportLearnMore": "[HTTP link to account export learn more, can be null]",
"walletLearnMore": "[HTTP link to wallet learn more, can be null]"
},
@@ -1153,6 +1316,16 @@
"images": "[Relative URL for wallet images]",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"governance": {
"newMarketProposal": {
"initialDepositAmount": 0,
"delayBlocks": 0,
"newMarketsMethodology": "[URL to spreadsheet or document that explains methodology]"
}
},
"featureFlags": {
"reduceOnlySupported": false
}
}
}
+8
View File
@@ -0,0 +1,8 @@
[
{
"name": "coinbase",
"label": "Coinbase",
"icon": "/exchanges/coinbase.png",
"depositType": "noble"
}
]
+14
View File
@@ -69,6 +69,13 @@
"whitepaperLink": "https://bitcoincash.org/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin-cash/"
},
"BONK-USD": {
"name": "BONK COIN",
"tags": [],
"websiteLink": "https://bonkcoin.com/",
"whitepaperLink": "https://bonkcoin.com/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bonk1/"
},
"BLUR-USD": {
"name": "Blur",
"tags": [],
@@ -169,6 +176,13 @@
"whitepaperLink": "https://dfinity.org/whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/internet-computer/"
},
"JUP-USD": {
"name": "Jupiter",
"tags": ["Defi"],
"websiteLink": "https://station.jup.ag/",
"whitepaperLink": "https://station.jup.ag/blog/green-paper",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/jupiter-ag/"
},
"LDO-USD": {
"name": "Lido DAO",
"tags": ["Defi"],
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.6 KiB

+15
View File
@@ -0,0 +1,15 @@
<!-- Smartbanner: Configure the mobile app -->
<meta name="smartbanner:title" content="SMARTBANNER_APP_NAME">
<meta name="smartbanner:author" content="SMARTBANNER_ORG_NAME">
<meta name="smartbanner:icon-apple" content="SMARTBANNER_ICON_URL">
<meta name="smartbanner:icon-google" content="SMARTBANNER_ICON_URL">
<!-- Smartbanner: The rest of the configurations can be kept as is -->
<meta name="smartbanner:price" content="FREE">
<meta name="smartbanner:price-suffix-apple" content=" - On the App Store">
<meta name="smartbanner:price-suffix-google" content=" - In Google Play">
<meta name="smartbanner:button" content="VIEW">
<meta name="smartbanner:close-label" content="Close">
<meta name="smartbanner:exclude-user-agent-regex" content="^.*(Windows NT|Intel Mac OS X).*$">
<script src="https://cdn.jsdelivr.net/npm/smartbanner.js@1.22.0/dist/smartbanner.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/smartbanner.js@1.22.0/dist/smartbanner.min.css" rel="stylesheet">
<!-- Smartbanner: End configuration -->
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+14
View File
@@ -36,3 +36,17 @@ Customize Intercom Messenger by adding logo and brand colors
3. Add API key in Github > Secrets and Variables > Actions as `INTERCOM_APP_ID`
4. In your deploy scripts add `pnpm run build:inject-intercom` after your pnpm build / vite build command.
5. If you are using with the Amplitude deployment scripts, your build command may look like the following: `pnpm build && pnpm run build:inject-amplitude && pnpm run build:inject-intercom`
### Smartbanner
Smartbanner to show download links to iOS and/or Android native apps on mobile devices.
<b>To use with dydxprotocol/v4-web:</b>
1. iOS app App Store link or Android app Google Play link.
2. Add configurations in Github > Secrets and Variables > Actions as
`SMARTBANNER_APP_NAME` for app name
`SMARTBANNER_ORG_NAME` for organization name
`SMARTBANNER_ICON_URL` for icon image
`SMARTBANNER_APPSTORE_URL` for iOS App Store link
`SMARTBANNER_GOOGLEPLAY_URL` for Android Google Play link
3. In your deploy scripts add `pnpm run build:inject-smartbanner` after your pnpm build / vite build command.
4. If you are using with the Amplitude deployment scripts, your build command may look like the following: `pnpm build && pnpm run build:inject-smartbanner`
+30 -3
View File
@@ -3,12 +3,13 @@ import path from 'path';
import { fileURLToPath } from 'url';
const AMPLITUDE_API_KEY = process.env.AMPLITUDE_API_KEY;
const AMPLITUDE_SERVER_URL = process.env.AMPLITUDE_SERVER_URL;
const currentPath = fileURLToPath(import.meta.url);
const projectRoot = path.dirname(currentPath);
const htmlFilePath = path.resolve(projectRoot, '../dist/index.html');
if(AMPLITUDE_API_KEY){
if (AMPLITUDE_API_KEY) {
try {
const html = await fs.readFile(htmlFilePath, 'utf-8');
@@ -18,8 +19,34 @@ if(AMPLITUDE_API_KEY){
`;
const amplitudeListenerScript = `<script type="module">
!function(){var e="${AMPLITUDE_API_KEY}";e&&(globalThis.amplitude.init(e),globalThis.amplitude.setOptOut(!1),globalThis.addEventListener("dydx:track",function(e){var t=e.detail.eventType,d=e.detail.eventData;globalThis.amplitude.track(t,d)}),globalThis.addEventListener("dydx:identify",function(e){var t=e.detail.property,d=e.detail.propertyValue;if("walletAddress"===t)globalThis.amplitude.setUserId(d);else{var i=new globalThis.amplitude.Identify;i.set(t,d),globalThis.amplitude.identify(i)}}),console.log("Amplitude enabled."))}();
</script>`;
!(function () {
var e = "${AMPLITUDE_API_KEY}";
e &&
(globalThis.amplitude.init(e${
AMPLITUDE_SERVER_URL
? `, undefined, {
serverUrl: "${AMPLITUDE_SERVER_URL}"
}`
: ''
}),
globalThis.amplitude.setOptOut(!1),
globalThis.addEventListener("dydx:track", function (e) {
var t = e.detail.eventType,
d = e.detail.eventData;
globalThis.amplitude.track(t, d);
}),
globalThis.addEventListener("dydx:identify", function (e) {
var t = e.detail.property,
d = e.detail.propertyValue;
if ("walletAddress" === t) globalThis.amplitude.setUserId(d);
else {
var i = new globalThis.amplitude.Identify();
i.set(t, d), globalThis.amplitude.identify(i);
}
}),
console.log("Amplitude enabled."));
})();
</script>`;
const injectedHtml = html.replace(
'<div id="root"></div>',
+60
View File
@@ -0,0 +1,60 @@
/* eslint-disable no-console */
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const SMARTBANNER_APP_NAME = process.env.SMARTBANNER_APP_NAME;
const SMARTBANNER_ORG_NAME = process.env.SMARTBANNER_ORG_NAME;
const SMARTBANNER_ICON_URL = process.env.SMARTBANNER_ICON_URL;
const SMARTBANNER_APPSTORE_URL = process.env.SMARTBANNER_APPSTORE_URL;
const SMARTBANNER_GOOGLEPLAY_URL = process.env.SMARTBANNER_GOOGLEPLAY_URL;
const currentPath = fileURLToPath(import.meta.url);
const projectRoot = path.dirname(currentPath);
const htmlFilePath = path.resolve(projectRoot, '../dist/index.html');
const smartbannerFilePath = path.resolve(projectRoot, '../dist/smartbanner.html');
if (
SMARTBANNER_APP_NAME &&
SMARTBANNER_ORG_NAME &&
SMARTBANNER_ICON_URL &&
(SMARTBANNER_APPSTORE_URL || SMARTBANNER_GOOGLEPLAY_URL)
) {
try {
const html = await fs.readFile(htmlFilePath, 'utf-8');
let smartbanner = await fs.readFile(smartbannerFilePath, 'utf-8');
smartbanner = smartbanner
.replace('SMARTBANNER_APP_NAME', SMARTBANNER_APP_NAME)
.replace('SMARTBANNER_ORG_NAME', SMARTBANNER_ORG_NAME)
.replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL)
.replace('SMARTBANNER_ICON_URL', SMARTBANNER_ICON_URL);
/* hardcoded injection depending on whether the app is available on App Store and/or Google Play */
if (SMARTBANNER_APPSTORE_URL) {
smartbanner = `\t<meta name="smartbanner:button-url-apple" content="${SMARTBANNER_APPSTORE_URL}">\n${smartbanner}`;
}
if (SMARTBANNER_GOOGLEPLAY_URL) {
smartbanner = `\t<meta name="smartbanner:button-url-google" content="${SMARTBANNER_GOOGLEPLAY_URL}">\n${smartbanner}`;
}
if (SMARTBANNER_APPSTORE_URL) {
if (SMARTBANNER_GOOGLEPLAY_URL) {
smartbanner = `\t<meta name="smartbanner:enabled-platforms" content="android,ios">\n${smartbanner}`;
} else {
smartbanner = `\t<meta name="smartbanner:enabled-platforms" content="ios">\n${smartbanner}`;
}
} else {
if (SMARTBANNER_GOOGLEPLAY_URL) {
smartbanner = `\t<meta name="smartbanner:enabled-platforms" content="android">\n${smartbanner}`;
}
}
const injectedHtml = html.replace('</head>', `${smartbanner}\n</head>`);
await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8');
console.log('Smartbanner scripts successfully injected.');
} catch (err) {
console.error('Error injecting Smartbanner scripts:', err);
}
}
+49 -36
View File
@@ -5,7 +5,7 @@ import { WagmiConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from 'react-query';
import { GrazProvider } from 'graz';
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
import { AppRoute, DEFAULT_TRADE_ROUTE, MarketsRoute } from '@/constants/routes';
import {
useBreakpoints,
@@ -16,10 +16,12 @@ import {
} from '@/hooks';
import { DydxProvider } from '@/hooks/useDydxClient';
import { AccountsProvider } from '@/hooks/useAccounts';
import { AppThemeAndColorModeProvider } from '@/hooks/useAppThemeAndColorMode';
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications';
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
import { PotentialMarketsProvider } from '@/hooks/usePotentialMarkets';
import { RestrictionProvider } from '@/hooks/useRestrictions';
import { SubaccountProvider } from '@/hooks/useSubaccount';
@@ -35,6 +37,7 @@ import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
import { config } from '@/lib/wagmi';
import { breakpoints } from '@/styles';
import { GlobalStyle } from '@/styles/globalStyle';
import { layoutMixins } from '@/styles/layoutMixins';
import { LoadingSpace } from './components/Loading/LoadingSpinner';
@@ -42,15 +45,16 @@ import '@/styles/constants.css';
import '@/styles/fonts.css';
import '@/styles/web3modal.css';
const NewMarket = lazy(() => import('@/pages/markets/NewMarket'));
const MarketsPage = lazy(() => import('@/pages/markets/Markets'));
const PortfolioPage = lazy(() => import('@/pages/portfolio/Portfolio'));
const AlertsPage = lazy(() => import('@/pages/AlertsPage'));
const ProfilePage = lazy(() => import('@/pages/Profile'));
const SettingsPage = lazy(() => import('@/pages/settings/Settings'));
const TradePage = lazy(() => import('@/pages/trade/Trade'));
const RewardsPage = lazy(() => import('@/pages/rewards/RewardsPage'));
const TermsOfUsePage = lazy(() => import('@/pages/TermsOfUsePage'));
const PrivacyPolicyPage = lazy(() => import('@/pages/PrivacyPolicyPage'));
const TokenPage = lazy(() => import('@/pages/token/Token'));
const queryClient = new QueryClient();
@@ -66,49 +70,55 @@ const Content = () => {
const { chainTokenLabel } = useTokenConfigs();
return (
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<>
<GlobalStyle />
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}>
<Routes>
<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
</Route>
<Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}>
<Routes>
<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
</Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</>
)}
<Route path={AppRoute.Markets}>
<Route path={MarketsRoute.New} element={<NewMarket />} />
<Route path={AppRoute.Markets} element={<MarketsPage />} />
</Route>
<Route path={`/${chainTokenLabel}/*`} element={<TokenPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</>
)}
<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>
<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
</Styled.Main>
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
</Styled.Main>
{isTablet ? <FooterMobile /> : <FooterDesktop />}
{isTablet ? <FooterMobile /> : <FooterDesktop />}
<Styled.NotificationsToastArea />
<Styled.NotificationsToastArea />
<Styled.DialogArea ref={setDialogArea}>
<DialogManager />
</Styled.DialogArea>
<Styled.DialogArea ref={setDialogArea}>
<DialogManager />
</Styled.DialogArea>
<GlobalCommandDialog />
</Styled.Content>
<GlobalCommandDialog />
</Styled.Content>
</>
);
};
@@ -131,6 +141,8 @@ const providers = [
wrapProvider(LocalNotificationsProvider),
wrapProvider(NotificationsProvider),
wrapProvider(DialogAreaProvider),
wrapProvider(PotentialMarketsProvider),
wrapProvider(AppThemeAndColorModeProvider),
];
const App = () => {
@@ -195,6 +207,7 @@ Styled.Content = styled.div<{ isShowingHeader: boolean; isShowingFooter: boolean
Styled.Main = styled.main`
${layoutMixins.contentSectionAttached}
box-shadow: none;
grid-area: Main;
+1 -1
View File
@@ -85,7 +85,7 @@ Styled.Trigger = styled(Trigger)`
&:hover {
${Styled.Icon} {
color: var(--color-text-2);
filter: brightness(1.1);
filter: brightness(var(--hover-filter-base));
}
}
-3
View File
@@ -40,9 +40,6 @@ const AlertContainer = styled.div<StyleProps>`
case AlertType.Info: {
return css`
--alert-accent-color: var(--color-text-1);
--alert-default-background-opacity: 0.133; // Relative
// --alert-background: var(--color-layer-6); // Absolute
`;
}
case AlertType.Success: {
+2
View File
@@ -16,6 +16,7 @@ const assetIcons = {
AVAX: '/currencies/avax.png',
BCH: '/currencies/bch.png',
BLUR: '/currencies/blur.png',
BONK: '/currencies/bonk.png',
BTC: '/currencies/btc.png',
CELO: '/currencies/celo.png',
COMP: '/currencies/comp.png',
@@ -30,6 +31,7 @@ const assetIcons = {
ETH: '/currencies/eth.png',
FIL: '/currencies/fil.png',
ICP: '/currencies/icp.png',
JUP: '/currencies/jup.png',
LDO: '/currencies/ldo.png',
LINK: '/currencies/link.png',
LTC: '/currencies/ltc.png',
+2 -2
View File
@@ -134,8 +134,8 @@ const ButtonStyle = css<StyleProps>`
--button-textColor: var(--color-text-0);
--button-backgroundColor: transparent;
--button-active-filter: brightness(0.9);
--button-hover-filter: brightness(1.1);
--button-active-filter: brightness(var(--active-filter));
--button-hover-filter: brightness(var(--hover-filter-base));
--button-hover-textColor: var(--button-textColor);
--button-radius: 0.5em;
+10 -6
View File
@@ -89,9 +89,10 @@ const buttonActionVariants = {
--button-border: solid var(--border-width) var(--color-border);
`,
[ButtonAction.Primary]: css`
--button-textColor: var(--color-text-2);
--button-textColor: var(--color-text-button);
--button-backgroundColor: var(--color-accent);
--button-border: solid var(--border-width) var(--color-border-white);
--button-hover-filter: brightness(var(--hover-filter-variant));
`,
[ButtonAction.Secondary]: css`
@@ -101,15 +102,17 @@ const buttonActionVariants = {
`,
[ButtonAction.Create]: css`
--button-textColor: var(--color-text-2);
--button-backgroundColor: var(--color-positive);
--button-textColor: var(--color-text-button);
--button-backgroundColor: var(--color-green);
--button-border: solid var(--border-width) var(--color-border-white);
--button-hover-filter: brightness(var(--hover-filter-variant));
`,
[ButtonAction.Destroy]: css`
--button-textColor: var(--color-text-2);
--button-backgroundColor: var(--color-negative);
--button-textColor: var(--color-text-button);
--button-backgroundColor: var(--color-red);
--button-border: solid var(--border-width) var(--color-border-white);
--button-hover-filter: brightness(var(--hover-filter-variant));
`,
[ButtonAction.Navigation]: css`
@@ -119,9 +122,10 @@ const buttonActionVariants = {
`,
[ButtonAction.Reset]: css`
--button-textColor: var(--color-negative);
--button-textColor: var(--color-red);
--button-backgroundColor: var(--color-layer-3);
--button-border: solid var(--border-width) var(--color-border-red);
--button-hover-filter: brightness(var(--hover-filter-variant));
`,
};
+13
View File
@@ -0,0 +1,13 @@
import { forwardRef } from 'react';
type CanvasProps = {
className?: string;
width: number | string;
height: number | string;
};
export const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(
({ className, width, height }, canvasRef) => (
<canvas ref={canvasRef} className={className} width={width} height={height} />
)
);
+10 -2
View File
@@ -5,7 +5,7 @@ import { Checkbox, CheckboxProps } from '@/components/Checkbox';
import { StoryWrapper } from '.ladle/components';
export const Checkboxes: Story<CheckboxProps> = (args) => {
export const CheckboxStory: Story<CheckboxProps> = (args) => {
const [isChecked, setIsChecked] = useState(false);
return (
@@ -21,4 +21,12 @@ export const Checkboxes: Story<CheckboxProps> = (args) => {
);
};
Checkboxes.args = {};
CheckboxStory.args = {};
CheckboxStory.argTypes = {
disabled: {
options: [true, false],
control: { type: 'select' },
defaultValue: false,
}
}
+34 -8
View File
@@ -1,4 +1,4 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import styled, { css, type AnyStyledComponent } from 'styled-components';
import { Root, Indicator } from '@radix-ui/react-checkbox';
import { CheckIcon } from '@radix-ui/react-icons';
@@ -9,6 +9,7 @@ type ElementProps = {
onCheckedChange: (checked: boolean) => void;
id?: string;
label?: React.ReactNode;
disabled?: boolean;
};
type StyleProps = {
@@ -23,14 +24,25 @@ export const Checkbox: React.FC<CheckboxProps> = ({
onCheckedChange,
id,
label,
disabled,
}) => (
<Styled.Container>
<Styled.Root className={className} checked={checked} onCheckedChange={onCheckedChange} id={id}>
<Styled.Root
className={className}
checked={checked}
disabled={disabled}
onCheckedChange={onCheckedChange}
id={id}
>
<Styled.Indicator>
<CheckIcon />
</Styled.Indicator>
</Styled.Root>
{label && <label htmlFor={id}>{label}</label>}
{label && (
<Styled.Label disabled={disabled} htmlFor={id}>
{label}
</Styled.Label>
)}
</Styled.Container>
);
@@ -40,25 +52,27 @@ Styled.Container = styled.div`
${layoutMixins.row}
gap: 1ch;
font: var(--font-small-book);
label {
cursor: pointer;
}
`;
Styled.Root = styled(Root)`
--checkbox-backgroundColor: var(--color-layer-0);
--checkbox-borderColor: var(--color-border);
min-width: 1.25rem;
height: 1.25rem;
border-radius: 0.375rem;
border: var(--border-width) solid var(--color-border);
border: var(--border-width) solid var(--checkbox-borderColor);
background-color: var(--checkbox-backgroundColor);
&[data-state='checked'] {
--checkbox-backgroundColor: var(--color-accent);
}
&[data-disabled] {
cursor: not-allowed;
--checkbox-backgroundColor: var(--color-layer-1);
}
`;
Styled.Indicator = styled(Indicator)`
@@ -66,5 +80,17 @@ Styled.Indicator = styled(Indicator)`
align-items: center;
justify-content: center;
color: var(--color-text-button);
`;
Styled.Label = styled.label<{ disabled?: boolean }>`
cursor: pointer;
color: var(--color-text-2);
${({ disabled }) =>
disabled &&
css`
cursor: not-allowed;
color: var(--color-text-0);
`}
`;
+5 -4
View File
@@ -50,10 +50,11 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
label={title}
// value={highlightedCommand}
// onValueChange={setHighlightedCommand}
filter={(value: string, search: string) => {
if (value.replace(/ /g, '').includes(search.replace(/ /g, ''))) return 1;
return 0;
}}
filter={(value: string, search: string) =>
value.replace(/ /g, '').toLowerCase().includes(search.replace(/ /g, '').toLowerCase())
? 1
: 0
}
className={className}
$withStickyLayout={withStickyLayout}
>
-1
View File
@@ -11,6 +11,5 @@ export const DetachedScrollableSection = styled.section`
export const AttachedExpandingSection = styled.section`
${layoutMixins.contentSectionAttached}
${layoutMixins.expandingColumnWithHeader}
gap: var(--border-width);
`;
+13 -9
View File
@@ -6,6 +6,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = {
title: string;
subtitle?: React.ReactNode;
slotLeft?: React.ReactNode;
slotRight?: React.ReactNode;
};
@@ -16,14 +17,16 @@ type StyleProps = {
export const ContentSectionHeader = ({
title,
subtitle,
slotLeft,
slotRight,
className,
}: ElementProps & StyleProps) => (
<Styled.ContentSectionHeader className={className}>
<div>
{slotLeft}
<Styled.Header>
{title && <h3>{title}</h3>}
{subtitle && <p>{subtitle}</p>}
</div>
</Styled.Header>
{slotRight}
</Styled.ContentSectionHeader>
);
@@ -40,9 +43,15 @@ Styled.ContentSectionHeader = styled.header<StyleProps>`
padding: 1rem var(--header-horizontal-padding);
> div {
${layoutMixins.column}
@media ${breakpoints.tablet} {
flex-wrap: wrap;
--header-horizontal-padding: 1.25rem;
}
`;
Styled.Header = styled.div`
${layoutMixins.column}
flex: 1;
h3 {
color: var(--color-text-2);
@@ -54,9 +63,4 @@ Styled.ContentSectionHeader = styled.header<StyleProps>`
font: var(--font-small-book);
margin-top: 0.25rem;
}
@media ${breakpoints.tablet} {
flex-wrap: wrap;
--header-horizontal-padding: 1.25rem;
}
`;
+3 -3
View File
@@ -77,7 +77,7 @@ Styled.InlineRow = styled.div<{ copied: boolean }>`
`
: css`
&:hover {
filter: brightness(1.1);
filter: brightness(var(--hover-filter-base));
text-decoration: underline;
}
`}
@@ -87,7 +87,7 @@ Styled.Icon = styled(Icon)<{ copied: boolean }>`
${({ copied }) =>
copied &&
css`
color: var(--color-positive);
color: var(--color-success);
`}
`;
@@ -96,7 +96,7 @@ Styled.IconButton = styled(IconButton)<{ copied: boolean }>`
copied &&
css`
svg {
color: var(--color-positive);
color: var(--color-success);
}
`}
`;
+38 -23
View File
@@ -7,35 +7,50 @@ import { StoryWrapper } from '.ladle/components';
import styled, { type AnyStyledComponent } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
export const DetailsStory: Story<Parameters<typeof Details>> = () => (
export const DetailsStory: Story<Parameters<typeof Details>[0]> = (args) => (
<StoryWrapper>
<Styled.Resizable>
<Details
items={[
{
key: 'item-1',
label: 'Item 1',
tooltip: 'leverage',
value: 'Value 1',
},
{
key: 'item-2',
label: 'Really really really long item name 2',
tooltip: 'liquidation-price',
value: 'Value 2',
},
{
key: 'item-3',
label: 'Item 3',
tooltip: 'realized-pnl',
value: 'Value 3',
},
]}
/>
<Details {...args} />
</Styled.Resizable>
</StoryWrapper>
);
DetailsStory.args = {
items: [
{
key: 'item-1',
label: 'Item 1',
tooltip: 'leverage',
value: 'Value 1',
},
{
key: 'item-2',
label: 'Really really really long item name 2',
tooltip: 'liquidation-price',
value: 'Value 2',
},
{
key: 'item-3',
label: 'Item 3',
tooltip: 'realized-pnl',
value: 'Value 3',
},
],
showSubitems: false,
isLoading: false,
withOverflow: false,
withSeparators: false,
};
DetailsStory.argTypes = {
justifyItems: { options: ['start', 'end'], control: { type: 'select' }, defaultValue: 'start' },
layout: {
options: ['column', 'row', 'rowColumns', 'grid', 'stackColumn'],
control: { type: 'select' },
defaultValue: 'column',
},
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Resizable = styled.section`
+9 -5
View File
@@ -133,6 +133,7 @@ const detailsLayoutVariants = {
row: css`
${layoutMixins.row}
align-self: stretch;
white-space: nowrap;
`,
rowColumns: css`
@@ -159,12 +160,16 @@ const itemLayoutVariants: Record<string, FlattenInterpolation<ThemeProps<any>>>
${layoutMixins.spacedRow}
gap: 0.5rem;
align-items: start;
padding: 0.5rem 0;
min-height: var(--details-item-height);
> :last-child {
align-self: stretch;
> :first-child > abbr {
min-width: auto;
}
> :last-child {
${layoutMixins.row}
${layoutMixins.stickyRight}
@@ -217,8 +222,6 @@ Styled.Details = styled.dl<{
--details-grid-numColumns: 2;
${({ layout }) => layout && detailsLayoutVariants[layout]}
white-space: nowrap;
`;
Styled.Item = styled.div<{
@@ -231,7 +234,7 @@ Styled.Item = styled.div<{
${({ justifyItems }) =>
justifyItems === 'end' &&
css`
&:nth-child(even) {
> :nth-child(even) {
justify-items: end;
text-align: end;
}
@@ -243,6 +246,7 @@ Styled.Item = styled.div<{
{
column: css`
&:not(:hover) > :first-child {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
+1 -1
View File
@@ -26,7 +26,7 @@ export const DetailsDialog = ({ slotIcon, title, items, slotFooter, setIsOpen }:
placement={isTablet ? DialogPlacement.Default : DialogPlacement.Sidebar}
>
<Styled.Content>
<Styled.Details withSeparators items={items} />
<Styled.Details withSeparators justifyItems="end" items={items} />
<Styled.Footer>{slotFooter}</Styled.Footer>
</Styled.Content>
+1 -1
View File
@@ -14,7 +14,7 @@ export const DiffArrowStory: Story<DiffArrowProps> = (args) => (
DiffArrowStory.argTypes = {
direction: {
options: ['left', 'right'],
options: ['left', 'right', 'up', 'down'],
control: { type: 'select' },
defaultValue: 'right',
},
+7 -1
View File
@@ -10,7 +10,7 @@ type ElementProps = {
type StyleProps = {
className?: string;
direction?: 'right' | 'left';
direction?: 'right' | 'left' | 'up' | 'down';
};
export type DiffArrowProps = ElementProps & StyleProps;
@@ -57,5 +57,11 @@ Styled.DiffArrowContainer = styled.span<DiffArrowProps>`
left: css`
transform: scaleX(-1);
`,
up: css`
transform: rotate(-90deg);
`,
down: css`
transform: rotate(90deg);
`,
}[direction || 'right'])}
`;
+1 -1
View File
@@ -75,7 +75,7 @@ Styled.DiffValue = styled.div<{ hasInvalidNewValue?: boolean }>`
${({ hasInvalidNewValue }) =>
hasInvalidNewValue &&
css`
color: var(--color-negative);
color: var(--color-error);
`}
`;
+3 -3
View File
@@ -35,8 +35,8 @@ export const DropdownHeaderMenu = <MenuItemValue extends string>({
<Root>
<Styled.Trigger className={className} asChild>
<div>
{children}
<Styled.DropdownIconButton iconName={IconName.Caret} isToggle />
{children}
<Styled.DropdownIconButton iconName={IconName.Caret} isToggle />
</div>
</Styled.Trigger>
<Portal>
@@ -87,7 +87,7 @@ Styled.Trigger = styled(Trigger)`
outline: none;
:hover {
filter: brightness(1.1);
filter: brightness(var(--hover-filter-base));
}
`;
+29 -24
View File
@@ -4,38 +4,43 @@ import { DropdownMenu } from '@/components/DropdownMenu';
import { StoryWrapper } from '.ladle/components';
export const DropdownMenuStory: Story<Parameters<typeof DropdownMenu>> = (args) => {
const exampleItems = [
{
value: '1',
label: 'Item 1',
onSelect: () => alert('Item 1 action'),
},
{
value: '2',
label: 'Item 2',
onSelect: () => alert('Item 2 action'),
},
{
value: '3',
label: 'Item 3',
onSelect: () => alert('Item 3 action'),
},
];
export const DropdownMenuStory: Story<Parameters<typeof DropdownMenu>[0]> = (args) => {
return (
<StoryWrapper>
<DropdownMenu
{...args}
items={exampleItems}
>
<DropdownMenu {...args}>
<span>Menu</span>
</DropdownMenu>
</StoryWrapper>
);
};
DropdownMenuStory.args = {};
DropdownMenuStory.args = {
items: [
{
value: '0',
label: 'Item 0',
onSelect: () => alert('Item 0 action'),
},
{
value: '1',
label: 'Item 1 (accent)',
onSelect: () => alert('Item 1 action'),
highlightColor: 'accent',
},
{
value: '2',
label: 'Item 2 (create)',
onSelect: () => alert('Item 2 action'),
highlightColor: 'create',
},
{
value: '3',
label: 'Item 3 (destroy)',
onSelect: () => alert('Item 3 action'),
highlightColor: 'destroy',
},
],
};
DropdownMenuStory.argTypes = {
align: {
+6 -6
View File
@@ -13,7 +13,7 @@ export type DropdownMenuItem<T> = {
label: React.ReactNode;
onSelect?: () => void;
separator?: boolean;
highlightColor?: 'accent' | 'positive' | 'negative';
highlightColor?: 'accent' | 'create' | 'destroy';
};
type StyleProps = {
@@ -82,7 +82,7 @@ Styled.Separator = styled(Separator)`
margin: 0.25rem 1rem;
`;
Styled.Item = styled(Item)<{ $highlightColor: 'accent' | 'positive' | 'negative' }>`
Styled.Item = styled(Item)<{ $highlightColor: 'accent' | 'create' | 'destroy' }>`
${popoverMixins.item}
--item-font-size: var(--dropdownMenu-item-font-size);
${({ $highlightColor }) =>
@@ -90,11 +90,11 @@ Styled.Item = styled(Item)<{ $highlightColor: 'accent' | 'positive' | 'negative'
['accent']: `
--item-highlighted-textColor: var(--color-accent);
`,
['positive']: `
--item-highlighted-textColor: var(--color-positive);
['create']: `
--item-highlighted-textColor: var(--color-green);
`,
['negative']: `
--item-highlighted-textColor: var(--color-negative);
['destroy']: `
--item-highlighted-textColor: var(--color-red);
`,
}[$highlightColor])}
@@ -28,21 +28,22 @@ const exampleItems = [
},
];
export const DropdownSelectMenuStory: Story<Parameters<typeof DropdownSelectMenu>> = (args) => {
export const DropdownSelectMenuStory: Story<
Pick<Parameters<typeof DropdownSelectMenu>[0], 'items' | 'align' | 'sideOffset' | 'disabled'>
> = (args) => {
const [item, setItem] = useState(exampleItems[0].value);
return (
<StoryWrapper>
<DropdownSelectMenu
items={exampleItems}
value={item}
onValueChange={(value) => setItem(value)}
{...args}
/>
<DropdownSelectMenu value={item} onValueChange={(value) => setItem(value)} {...args} />
</StoryWrapper>
);
};
DropdownSelectMenuStory.args = {};
DropdownSelectMenuStory.args = {
items: exampleItems,
sideOffset: 1,
disabled: false,
};
DropdownSelectMenuStory.argTypes = {
align: {
+3 -3
View File
@@ -32,7 +32,7 @@ export const FormInput = forwardRef<HTMLInputElement, FormInputProps>(
isValidationAttached={validationConfig?.attached}
>
<Styled.InputContainer hasSlotRight={!!slotRight}>
<Styled.WithLabel label={label} inputID={id}>
<Styled.WithLabel label={label} inputID={id} disabled={otherProps?.disabled}>
<Input ref={ref} id={id} {...otherProps} />
</Styled.WithLabel>
{slotRight}
@@ -85,11 +85,11 @@ Styled.InputContainer = styled.div<{ hasSlotRight?: boolean }>`
`}
`;
Styled.WithLabel = styled(WithLabel)`
Styled.WithLabel = styled(WithLabel)<{ disabled?: boolean }>`
${formMixins.inputLabel}
label {
cursor: text;
${({ disabled }) => !disabled && 'cursor: text;'}
padding: var(--form-input-paddingY) var(--form-input-paddingX) 0;
}
`;
+1 -1
View File
@@ -4,7 +4,7 @@ import { Icon, IconName } from '@/components/Icon';
import { StoryWrapper } from '.ladle/components';
export const IconStory: Story<Parameters<typeof Icon>> = (args) => {
export const IconStory: Story<Parameters<typeof Icon>[0]> = (args) => {
return (
<StoryWrapper>
<Icon {...args} />
+18
View File
@@ -13,6 +13,7 @@ import {
CaretIcon,
CautionCircleStrokeIcon,
CautionCircleIcon,
ChaosLabsIcon,
ChatIcon,
CheckIcon,
CheckCircleIcon,
@@ -24,6 +25,7 @@ import {
CoinsIcon,
CommentIcon,
CopyIcon,
CurrencySignIcon,
DepositIcon,
DepthChartIcon,
DiscordIcon,
@@ -34,6 +36,7 @@ import {
FundingChartIcon,
GearIcon,
GiftboxIcon,
GovernanceIcon,
HelpCircleIcon,
HideIcon,
HistoryIcon,
@@ -44,6 +47,7 @@ import {
MenuIcon,
MigrateIcon,
MintscanIcon,
MoonIcon,
OrderbookIcon,
OrderCanceledIcon,
OrderFilledIcon,
@@ -66,7 +70,9 @@ import {
ShowIcon,
TogglesMenuIcon,
StarIcon,
SunIcon,
TerminalIcon,
TokenIcon,
TradeIcon,
TransferIcon,
TriangleIcon,
@@ -88,6 +94,7 @@ export enum IconName {
Caret = 'Caret',
CautionCircle = 'CautionCircle',
CautionCircleStroked = 'CautionCircleStroked',
ChaosLabs = 'ChaosLabs',
Chat = 'Chat',
Check = 'Check',
CheckCircle = 'CheckCircle',
@@ -99,6 +106,7 @@ export enum IconName {
Coins = 'Coins',
Comment = 'Comment',
Copy = 'Copy',
CurrencySign = 'CurrencySign',
Deposit = 'Deposit',
DepthChart = 'DepthChart',
Discord = 'Discord',
@@ -109,6 +117,7 @@ export enum IconName {
FundingChart = 'FundingChart',
Gear = 'Gear',
Giftbox = 'Giftbox',
Governance = 'Governance',
HelpCircle = 'HelpCircle',
Hide = 'Hide',
History = 'History',
@@ -119,6 +128,7 @@ export enum IconName {
Menu = 'Menu',
Migrate = 'Migrate',
Mintscan = 'Mintscan',
Moon = 'Moon',
Onboarding = 'Onboarding',
Orderbook = 'OrderbookIcon',
OrderCanceled = 'OrderCanceled',
@@ -141,8 +151,10 @@ export enum IconName {
Share = 'Share',
Show = 'Show',
Star = 'Star',
Sun = 'Sun',
Terminal = 'Terminal',
TogglesMenu = 'TogglesMenu',
Token = 'Token',
Trade = 'Trade',
Transfer = 'Transfer',
Triangle = 'Triangle',
@@ -164,6 +176,7 @@ const icons = {
[IconName.Caret]: CaretIcon,
[IconName.CautionCircle]: CautionCircleIcon,
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
[IconName.ChaosLabs]: ChaosLabsIcon,
[IconName.Chat]: ChatIcon,
[IconName.Check]: CheckIcon,
[IconName.CheckCircle]: CheckCircleIcon,
@@ -175,6 +188,7 @@ const icons = {
[IconName.Coins]: CoinsIcon,
[IconName.Comment]: CommentIcon,
[IconName.Copy]: CopyIcon,
[IconName.CurrencySign]: CurrencySignIcon,
[IconName.Deposit]: DepositIcon,
[IconName.DepthChart]: DepthChartIcon,
[IconName.Discord]: DiscordIcon,
@@ -185,6 +199,7 @@ const icons = {
[IconName.FundingChart]: FundingChartIcon,
[IconName.Gear]: GearIcon,
[IconName.Giftbox]: GiftboxIcon,
[IconName.Governance]: GovernanceIcon,
[IconName.HelpCircle]: HelpCircleIcon,
[IconName.Hide]: HideIcon,
[IconName.History]: HistoryIcon,
@@ -195,6 +210,7 @@ const icons = {
[IconName.Menu]: MenuIcon,
[IconName.Migrate]: MigrateIcon,
[IconName.Mintscan]: MintscanIcon,
[IconName.Moon]: MoonIcon,
[IconName.Orderbook]: OrderbookIcon,
[IconName.OrderCanceled]: OrderCanceledIcon,
[IconName.OrderFilled]: OrderFilledIcon,
@@ -216,8 +232,10 @@ const icons = {
[IconName.Share]: ShareIcon,
[IconName.Show]: ShowIcon,
[IconName.Star]: StarIcon,
[IconName.Sun]: SunIcon,
[IconName.Terminal]: TerminalIcon,
[IconName.TogglesMenu]: TogglesMenuIcon,
[IconName.Token]: TokenIcon,
[IconName.Trade]: TradeIcon,
[IconName.Transfer]: TransferIcon,
[IconName.Triangle]: TriangleIcon,
+1 -1
View File
@@ -6,7 +6,7 @@ import { Link } from '@/components/Link';
import { StoryWrapper } from '.ladle/components';
import { layoutMixins } from '@/styles/layoutMixins';
export const LinkStory: Story<Parameters<typeof Link>> = (args) => {
export const LinkStory: Story<Parameters<typeof Link>[0]> = (args) => {
return (
<StoryWrapper>
<Styled.Container>
@@ -11,6 +11,7 @@ export const LoadingSpinner: React.FC<{
return (
<Styled.Spinner className={className}>
<Styled.LoadingSpinnerSvg
id={id}
width="38"
height="38"
viewBox="0 0 38 38"
+1 -1
View File
@@ -1,11 +1,11 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { Ring } from '@/components/Ring';
import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = {
value: number;
+19 -17
View File
@@ -5,8 +5,24 @@ import { HashRouter } from 'react-router-dom';
import { StoryWrapper } from '.ladle/components';
export const NavigationMenuStory: Story<Parameters<typeof NavigationMenu>> = (args) => {
const exampleItems = [
export const NavigationMenuStory: Story<
Pick<Parameters<typeof NavigationMenu>[0], 'items' | 'orientation' | 'submenuPlacement'>
> = (args) => {
return (
<StoryWrapper>
<HashRouter
children={
<NavigationMenu {...args}>
<span>Menu</span>
</NavigationMenu>
}
/>
</StoryWrapper>
);
};
NavigationMenuStory.args = {
items: [
{
group: 'navigation',
groupLabel: 'Views',
@@ -93,23 +109,9 @@ export const NavigationMenuStory: Story<Parameters<typeof NavigationMenu>> = (ar
},
],
},
];
return (
<StoryWrapper>
<HashRouter
children={
<NavigationMenu {...args} items={exampleItems}>
<span>Menu</span>
</NavigationMenu>
}
/>
</StoryWrapper>
);
],
};
NavigationMenuStory.args = {};
NavigationMenuStory.argTypes = {
orientation: {
options: ['vertical', 'horizontal'],
+11 -1
View File
@@ -65,6 +65,9 @@ type ElementProps = {
resolution?: number;
stripRelativeWords?: boolean;
};
timeOptions?: {
useUTC?: boolean;
};
tag?: React.ReactNode;
withParentheses?: boolean;
locale?: string;
@@ -89,6 +92,7 @@ export const Output = ({
relativeTimeFormatOptions = {
format: 'singleCharacter',
},
timeOptions,
tag,
withParentheses,
locale = navigator.language || 'en-US',
@@ -116,6 +120,7 @@ export const Output = ({
{value?.toString() ?? null}
{tag && <Tag>{tag}</Tag>}
{slotRight}
</Styled.Text>
);
}
@@ -165,16 +170,21 @@ export const Output = ({
if ((typeof value !== 'string' && typeof value !== 'number') || !value) return null;
const date = new Date(value);
const dateString = {
[OutputType.Date]: date.toLocaleString(selectedLocale, { dateStyle: 'medium' }),
[OutputType.Date]: date.toLocaleString(selectedLocale, {
dateStyle: 'medium',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
[OutputType.DateTime]: date.toLocaleString(selectedLocale, {
dateStyle: 'short',
timeStyle: 'short',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
[OutputType.Time]: date.toLocaleString(selectedLocale, {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZone: timeOptions?.useUTC ? 'UTC' : undefined,
}),
}[type];
+5 -3
View File
@@ -1,10 +1,10 @@
import type { Story } from '@ladle/react';
import { Panel } from '@/components/Panel';
import { Panel, PanelProps } from '@/components/Panel';
import { StoryWrapper } from '.ladle/components';
export const PanelStory: Story<{ slotHeader: React.ReactNode, children?: React.ReactNode }> = (args) => {
export const PanelStory: Story<PanelProps> = (args) => {
return (
<StoryWrapper>
<Panel {...args} />
@@ -13,6 +13,8 @@ export const PanelStory: Story<{ slotHeader: React.ReactNode, children?: React.R
};
PanelStory.args = {
slotHeader: 'Header',
slotHeaderContent: 'Header',
children: 'Content',
slotRight: '1️⃣',
hasSeparator: true,
};
+6 -4
View File
@@ -6,8 +6,8 @@ import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
type PanelProps = {
slotHeaderContent?: string;
type ElementProps = {
slotHeaderContent?: React.ReactNode;
slotHeader?: React.ReactNode;
slotRight?: React.ReactNode;
children?: React.ReactNode;
@@ -16,11 +16,13 @@ type PanelProps = {
onClick?: () => void;
};
type PanelStyleProps = {
type StyleProps = {
className?: string;
hasSeparator?: boolean;
};
export type PanelProps = ElementProps & StyleProps;
export const Panel = ({
slotHeaderContent,
slotHeader,
@@ -31,7 +33,7 @@ export const Panel = ({
onClick,
hasSeparator,
className,
}: PanelProps & PanelStyleProps) => (
}: PanelProps) => (
<Styled.Panel onClick={onClick} className={className}>
<Styled.Left>
{href ? (
+3 -2
View File
@@ -11,6 +11,7 @@ type ElementProps = {
};
type StyleProps = {
className?: string;
hasLogo?: boolean;
size?: number;
};
@@ -18,7 +19,7 @@ type StyleProps = {
const DARK_LOGO_MARK_URL = '/logos/logo-mark-dark.svg';
const LIGHT_LOGO_MARK_URL = '/logos/logo-mark-light.svg';
export const QrCode = ({ value, hasLogo, size = 300 }: ElementProps & StyleProps) => {
export const QrCode = ({ className, value, hasLogo, size = 300 }: ElementProps & StyleProps) => {
const ref = useRef<HTMLDivElement>(null);
const appTheme: AppTheme = useSelector(getAppTheme);
@@ -74,7 +75,7 @@ export const QrCode = ({ value, hasLogo, size = 300 }: ElementProps & StyleProps
}
}, [appTheme, hasLogo]);
return <Styled.QrCode ref={ref} />;
return <Styled.QrCode className={className} ref={ref} />;
};
const Styled: Record<string, AnyStyledComponent> = {};
+1 -1
View File
@@ -7,7 +7,7 @@ import { StoryWrapper } from '.ladle/components';
import { layoutMixins } from '@/styles/layoutMixins';
import { InputType } from './Input';
export const SearchInputStory: Story<Parameters<typeof SearchInput>> = (args) => (
export const SearchInputStory: Story<Parameters<typeof SearchInput>[0]> = (args) => (
<StoryWrapper>
<Styled.Container>
<SearchInput placeholder="Search something..." type={InputType.Search} />
+2 -5
View File
@@ -6,7 +6,7 @@ import { SearchSelectMenu } from '@/components/SearchSelectMenu';
import { StoryWrapper } from '.ladle/components';
export const SearchSelectMenuStory: Story<Parameters<typeof SearchSelectMenu>> = (args) => {
export const SearchSelectMenuStory: Story<Parameters<typeof SearchSelectMenu>[0]> = (args) => {
const [selectedItem, setSelectedItem] = useState<string>();
const exampleItems = [
@@ -24,10 +24,7 @@ export const SearchSelectMenuStory: Story<Parameters<typeof SearchSelectMenu>> =
return (
<StoryWrapper>
<Container>
<SearchSelectMenu
{...args}
items={exampleItems}
>
<SearchSelectMenu {...args} items={exampleItems}>
{!selectedItem ? <span>Search and Select</span> : <span>{selectedItem}</span>}
</SearchSelectMenu>
</Container>
+1 -1
View File
@@ -26,7 +26,7 @@ const exampleItems: { value: string; label: string }[] = [
},
];
export const SelectMenuStory: Story<Parameters<typeof SelectMenu>> = (args) => {
export const SelectMenuStory: Story<Parameters<typeof SelectMenu>[0]> = (args) => {
const [value, setValue] = useState(exampleItems[0].value);
const [value2, setValue2] = useState(exampleItems[2].value);
+88 -9
View File
@@ -33,17 +33,20 @@ import {
import { useAsyncList } from 'react-stately';
import { useBreakpoints } from '@/hooks';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
import { Checkbox } from '@/components/Checkbox';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { CaretIcon } from '@/icons';
import { STRING_KEYS } from '@/constants/localization';
import { MustBigNumber } from '@/lib/numbers';
import { Icon, IconName } from './Icon';
import { Tag } from './Tag';
import { MustBigNumber } from '@/lib/numbers';
import { Button } from './Button';
export { TableCell } from './Table/TableCell';
export { TableColumnHeader } from './Table/TableColumnHeader';
@@ -80,7 +83,7 @@ export type ColumnDef<TableRowData extends object> = {
width?: ColumnSize;
};
type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
export type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
label?: string;
columns: ColumnDef<TableRowData>[];
data: TableRowData[];
@@ -92,6 +95,10 @@ type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey ext
selectionBehavior?: 'replace' | 'toggle';
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
slotEmpty?: React.ReactNode;
viewMoreConfig?: {
initialNumRowsToShow: number;
numRowsPerPage?: number;
};
// collection: TableCollection<string>;
// children: React.ReactNode;
};
@@ -121,6 +128,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
selectionMode = 'single',
selectionBehavior = 'toggle',
slotEmpty,
viewMoreConfig,
// shouldRowRender,
// collection,
@@ -136,6 +144,18 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
style,
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
const [numRowsToShow, setNumRowsToShow] = useState(viewMoreConfig?.initialNumRowsToShow);
const enableViewMore = viewMoreConfig !== undefined;
const onViewMoreClick = () => {
if (!viewMoreConfig) return;
const { numRowsPerPage } = viewMoreConfig;
if (numRowsPerPage) {
setNumRowsToShow((prev) => (prev ?? 0) + numRowsPerPage);
} else {
setNumRowsToShow(data.length);
}
};
const currentBreakpoints = useBreakpoints();
const shownColumns = columns.filter(
@@ -209,6 +229,10 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
onRowAction &&
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
}
numColumns={shownColumns.length}
onViewMoreClick={
enableViewMore && numRowsToShow! < data.length ? onViewMoreClick : undefined
}
// shouldRowRender={shouldRowRender}
hideHeader={hideHeader}
withGradientCardRows={withGradientCardRows}
@@ -233,7 +257,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
)}
</TableHeader>
<TableBody items={list.items}>
<TableBody items={enableViewMore ? list.items.slice(0, numRowsToShow) : list.items}>
{(item) => (
<Row key={getRowKey(item)}>
{(columnKey) => (
@@ -267,6 +291,8 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
onRowAction?: (key: TableRowKey) => void;
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
children: CollectionChildren<TableRowData>;
numColumns: number;
onViewMoreClick?: () => void;
hideHeader?: boolean;
withGradientCardRows?: boolean;
@@ -276,7 +302,7 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
withScrollSnapColumns?: boolean;
withScrollSnapRows?: boolean;
}) => {
const { selectionMode, selectionBehavior } = props;
const { selectionMode, selectionBehavior, numColumns, onViewMoreClick } = props;
const state = useTableState<TableRowData>({
...props,
@@ -337,6 +363,7 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
<TableBodyRowGroup
withGradientCardRows={props.withGradientCardRows}
withInnerBorders={props.withInnerBorders}
withOuterBorder={props.withOuterBorder}
>
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
{[...collection.body.childNodes].map((row) =>
@@ -382,6 +409,9 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
</TableRow>
)
)}
{onViewMoreClick ? (
<ViewMoreRow colSpan={numColumns} onClick={onViewMoreClick} />
) : undefined}
</TableBodyRowGroup>
</Styled.Table>
);
@@ -415,6 +445,7 @@ const TableBodyRowGroup = ({
children,
withGradientCardRows,
withInnerBorders,
withOuterBorder,
}: { children: React.ReactNode } & StyleProps) => {
const { rowGroupProps } = useTableRowGroup();
@@ -423,6 +454,7 @@ const TableBodyRowGroup = ({
{...rowGroupProps}
withGradientCardRows={withGradientCardRows}
withInnerBorders={withInnerBorders}
withOuterBorder={withOuterBorder}
>
{children}
</Styled.Tbody>
@@ -489,6 +521,23 @@ const TableColumnHeader = <TableRowData extends object>({
);
};
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
const stringGetter = useStringGetter();
return (
<Styled.ViewMoreTr key="viewmore">
<Styled.Td
colSpan={colSpan}
onMouseDown={(e: MouseEvent) => e.preventDefault()}
onPointerDown={(e: MouseEvent) => e.preventDefault()}
>
<Styled.ViewMoreButton slotRight={<CaretIcon />} onClick={onClick}>
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
</Styled.ViewMoreButton>
</Styled.Td>
</Styled.ViewMoreTr>
);
};
export const TableRow = <TableRowData extends object>({
item,
children,
@@ -635,6 +684,8 @@ Styled.TableWrapper = styled.div<{
--table-lastColumn-cell-align: end; // start | center | end | var(--table-cell-align)
--tableCell-padding: 0 1rem;
--tableViewMore-borderColor: inherit;
// Rules
flex: 1;
@@ -660,7 +711,7 @@ Styled.Empty = styled.div<{ withOuterBorder: boolean }>`
justify-items: center;
align-content: center;
padding: 2rem;
padding: 4rem;
gap: 0.75em;
color: var(--color-text-0);
@@ -744,7 +795,7 @@ Styled.Tr = styled.tr<{
&:focus-visible,
&:focus-within {
--tableRow-currentBackgroundColor: var(--tableRow-hover-backgroundColor);
filter: brightness(1.1);
filter: brightness(var(--hover-filter-base));
}
`};
@@ -868,6 +919,18 @@ Styled.Tbody = styled.tbody<StyleProps>`
--stickyArea2-paddingLeft: var(--border-width);
--stickyArea2-paddingRight: var(--border-width);
tr:first-of-type {
box-shadow: 0 calc(var(--border-width)) 0 0 var(--border-color);
}
`}
${({ withOuterBorder }) =>
withOuterBorder &&
css`
tr:last-of-type:not(:only-of-type) {
box-shadow: 0 calc(-1 * var(--border-width)) 0 0 var(--border-color);
}
tr:first-of-type {
box-shadow: none;
}
@@ -922,3 +985,19 @@ Styled.Row = styled.div`
${layoutMixins.inlineRow}
padding: var(--tableCell-padding);
`;
Styled.ViewMoreButton = styled(Button)`
--button-backgroundColor: var(--color-layer-2);
--button-textColor: var(--color-text-1);
width: 100%;
svg {
width: 0.675rem;
margin-left: 0.5ch;
}
`;
Styled.ViewMoreTr = styled(Styled.Tr)`
--border-color: var(--tableViewMore-borderColor);
`;
+19 -20
View File
@@ -13,29 +13,11 @@ enum TabItem {
Item3 = 'Item3',
}
const TabItems = [
{
value: TabItem.Item1,
label: 'Item 1',
content: <div>Item 1 Content</div>,
},
{
value: TabItem.Item2,
label: 'Item 2',
content: <div>Item 2 Content</div>,
},
{
value: TabItem.Item3,
label: 'Item 3',
content: <div>Item 3 Content</div>,
},
];
export const TabsStory: Story<Parameters<typeof Tabs>> = (args) => {
export const TabsStory: Story<Parameters<typeof Tabs>[0]> = (args) => {
return (
<StoryWrapper>
<Styled.Container>
<Tabs items={TabItems} {...args} />
<Tabs {...args} />
</Styled.Container>
</StoryWrapper>
);
@@ -43,6 +25,23 @@ export const TabsStory: Story<Parameters<typeof Tabs>> = (args) => {
TabsStory.args = {
fullWidthTabs: false,
items: [
{
value: TabItem.Item1,
label: 'Item 1',
content: <div>Item 1 Content</div>,
},
{
value: TabItem.Item2,
label: 'Item 2',
content: <div>Item 2 Content</div>,
},
{
value: TabItem.Item3,
label: 'Item 3',
content: <div>Item 3 Content</div>,
},
],
};
TabsStory.argTypes = {
+21 -2
View File
@@ -1,10 +1,10 @@
import type { Story } from '@ladle/react';
import { Tag } from '@/components/Tag';
import { Tag, TagSign, TagSize, TagType } from '@/components/Tag';
import { StoryWrapper } from '.ladle/components';
export const TagStory: Story<Parameters<typeof Tag>> = (args) => {
export const TagStory: Story<Parameters<typeof Tag>[0]> = (args) => {
return (
<StoryWrapper>
<Tag {...args} />
@@ -14,4 +14,23 @@ export const TagStory: Story<Parameters<typeof Tag>> = (args) => {
TagStory.args = {
children: 'USDC',
isHighlighted: false,
};
TagStory.argTypes = {
size: {
options: Object.values(TagSize),
control: { type: 'select' },
defaultValue: TagSize.Small,
},
type: {
options: [...Object.values(TagType), undefined],
control: { type: 'select' },
defaultValue: undefined,
},
sign: {
options: [...Object.values(TagSign), undefined],
control: { type: 'select' },
defaultValue: undefined,
},
};
+8 -8
View File
@@ -1,20 +1,20 @@
import styled, { css } from 'styled-components';
export enum TagSize {
Small,
Medium,
Small = 'Small',
Medium = 'Medium',
}
export enum TagType {
Asset,
Side,
Number,
Asset = 'Asset',
Side = 'Side',
Number = 'Number',
}
export enum TagSign {
Positive,
Negative,
Neutral,
Positive = 'Positive',
Negative = 'Negative',
Neutral = 'Neutral',
}
type StyleProps = {
+4
View File
@@ -8,6 +8,7 @@ import { Button, type ButtonStateConfig, type ButtonProps } from '@/components/B
type ElementProps = {
timeoutInSeconds: number;
slotFinal?: ReactNode;
} & ButtonProps;
export type TimeoutButtonProps = ElementProps;
@@ -15,6 +16,7 @@ export type TimeoutButtonProps = ElementProps;
export const TimeoutButton = ({
children,
timeoutInSeconds,
slotFinal,
...otherProps
}: TimeoutButtonProps) => {
const [timeoutDeadline] = useState(Date.now() + timeoutInSeconds * 1000);
@@ -23,6 +25,8 @@ export const TimeoutButton = ({
const secondsLeft = Math.max(0, (timeoutDeadline - now) / 1000);
if (slotFinal && secondsLeft <= 0) return slotFinal;
return (
<Button
{...otherProps}
+1 -1
View File
@@ -5,7 +5,7 @@ import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { StoryWrapper } from '.ladle/components';
import { ToggleButton } from './ToggleButton';
export const ToggleButtonStory: Story<Parameters<typeof ToggleButton>> = (args) => (
export const ToggleButtonStory: Story<Parameters<typeof ToggleButton>[0]> = (args) => (
<StoryWrapper>
<ToggleButton {...args}>Toggle me</ToggleButton>
</StoryWrapper>
+8 -2
View File
@@ -21,16 +21,22 @@ const ToggleGroupItems = [
},
];
export const ToggleGroupStory: Story<Parameters<typeof ToggleGroup>> = (args) => {
export const ToggleGroupStory: Story<
Pick<Parameters<typeof ToggleGroup>[0], 'items' | 'size' | 'shape'>
> = (args) => {
const [value, setValue] = useState('0');
return (
<StoryWrapper>
<ToggleGroup items={ToggleGroupItems} value={value} onValueChange={setValue} {...args} />
<ToggleGroup value={value} onValueChange={setValue} {...args} />
</StoryWrapper>
);
};
ToggleGroupStory.args = {
items: ToggleGroupItems,
};
ToggleGroupStory.argTypes = {
size: {
options: Object.values(ButtonSize),
+2 -2
View File
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = {
value: number;
};
+2 -2
View File
@@ -117,7 +117,7 @@ Styled.ConfirmButton = styled(Styled.IconButton)`
--button-backgroundColor: hsla(203, 25%, 19%, 1);
svg {
color: var(--color-positive);
color: var(--color-green);
}
`;
@@ -125,7 +125,7 @@ Styled.CancelButton = styled(Styled.IconButton)`
--button-backgroundColor: hsla(296, 16%, 18%, 1);
svg {
color: var(--color-negative);
color: var(--color-red);
width: 0.8em;
height: 0.8em;
+1 -1
View File
@@ -40,5 +40,5 @@ Styled.Details = styled(Details)`
padding: 0.375rem 0.75rem 0.25rem;
font-size: 0.8125em;
font-size: var(--details-item-fontSize, 0.8125em);
`;
+1 -1
View File
@@ -9,7 +9,7 @@ import { StoryWrapper } from '.ladle/components';
import styled, { type AnyStyledComponent } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
export const WithLabelStory: Story<Parameters<typeof WithLabel>> = (args) => {
export const WithLabelStory: Story<Parameters<typeof WithLabel>[0]> = (args) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
+19 -22
View File
@@ -3,32 +3,29 @@ import type { Story } from '@ladle/react';
import { Button } from '@/components/Button';
import { WithReceipt } from '@/components/WithReceipt';
import { type DetailsItem } from './Details';
import { StoryWrapper } from '.ladle/components';
const items: DetailsItem[] = [
{
key: 'item-1',
label: 'Item 1',
value: 'Value 1',
},
{
key: 'item-2',
label: 'Item 2',
value: 'Value 2',
},
{
key: 'item-3',
label: 'Item 3',
value: 'Value 3',
},
];
export const WithReceiptStory: Story<Parameters<WithReceipt>> = (args) => (
export const WithReceiptStory: Story<Omit<Parameters<typeof WithReceipt>[0], 'slotReceipt'>> = (
args
) => (
<StoryWrapper>
<div style={{ width: 200 }}>
<WithReceipt {...args}>
<WithReceipt
slotReceipt={
<div
style={{
padding: '1rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
Receipt Content
</div>
}
{...args}
>
<Button>Hello there</Button>
</WithReceipt>
</div>
@@ -36,7 +33,7 @@ export const WithReceiptStory: Story<Parameters<WithReceipt>> = (args) => (
);
WithReceiptStory.args = {
items,
hideReceipt: false,
};
WithReceiptStory.argTypes = {
+1 -2
View File
@@ -106,8 +106,6 @@ Styled.Container = styled.div`
--stickyArea1-leftGap: var(--border-width);
min-height: var(--stickyArea-height);
${layoutMixins.withOuterAndInnerBorders}
display: grid;
grid-template: var(--withSidebar-gridTemplate);
`;
@@ -120,6 +118,7 @@ Styled.Side = styled.aside`
${layoutMixins.sticky}
max-height: var(--stickyArea-height);
backdrop-filter: none;
background-color: var(--color-layer-2);
${layoutMixins.stack}
`;
+1 -1
View File
@@ -6,7 +6,7 @@ import { tooltipStrings } from '@/constants/tooltips';
import { StoryWrapper } from '.ladle/components';
export const Tooltip: Story<Parameters<typeof WithTooltip>> = (args) => {
export const Tooltip: Story<Parameters<typeof WithTooltip>[0]> = (args) => {
return (
<StoryWrapper>
<WithTooltip {...args}>
+1 -6
View File
@@ -110,12 +110,7 @@ Styled.Abbr = styled.abbr`
Styled.Content = styled(Content)`
--tooltip-backgroundColor: var(--color-layer-4);
--tooltip-backgroundColor: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) + 4%),
0.66
);
--tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground};
${popoverMixins.popover}
--popover-backgroundColor: var(--tooltip-backgroundColor);
+1 -6
View File
@@ -27,12 +27,7 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
${popoverMixins.popover}
--popover-radius: 0.5rem;
--popover-background-color: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness)),
0.9
);
--popover-background-color: ${({ theme }) => theme.popoverBackground};
--popover-backdrop-filter: saturate(120%) blur(12px);
display: grid;
+35 -1
View File
@@ -119,6 +119,13 @@ export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.Sel
// ------ Wallet ------ //
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance;
export type TradingRewards = Abacus.exchange.dydx.abacus.output.TradingRewards;
export type HistoricalTradingReward = Abacus.exchange.dydx.abacus.output.HistoricalTradingReward;
export const HistoricalTradingRewardsPeriod =
Abacus.exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod;
const historicalTradingRewardsPeriod = [...HistoricalTradingRewardsPeriod.values()] as const;
export type HistoricalTradingRewardsPeriods = (typeof historicalTradingRewardsPeriod)[number];
export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount;
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition;
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
@@ -202,6 +209,9 @@ export const RestrictionType = Abacus.exchange.dydx.abacus.output.Restriction;
const restrictionTypes = [...RestrictionType.values()] as const;
export type RestrictionTypes = (typeof restrictionTypes)[number];
// ------ Api data ------ //
export const ApiData = Abacus.exchange.dydx.abacus.state.manager.ApiData;
// ------ Enum Conversions ------ //
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? A
@@ -236,6 +246,15 @@ export const HISTORICAL_PNL_PERIODS: Record<
[HistoricalPnlPeriod.Period90d.name]: HistoricalPnlPeriod.Period90d,
};
export const HISTORICAL_TRADING_REWARDS_PERIODS: Record<
KotlinIrEnumValues<typeof HistoricalTradingRewardsPeriod>,
HistoricalTradingRewardsPeriods
> = {
[HistoricalTradingRewardsPeriod.MONTHLY.name]: HistoricalTradingRewardsPeriod.MONTHLY,
[HistoricalTradingRewardsPeriod.WEEKLY.name]: HistoricalTradingRewardsPeriod.WEEKLY,
[HistoricalTradingRewardsPeriod.DAILY.name]: HistoricalTradingRewardsPeriod.DAILY,
};
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,
@@ -282,13 +301,22 @@ export const TRADE_TYPES: Record<
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
[AbacusOrderType.liquidated.name]: null,
[AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidated.rawValue]: null,
[AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidation.rawValue]: null,
[AbacusOrderType.trailingStop.name]: null,
[AbacusOrderType.trailingStop.rawValue]: null,
[AbacusOrderType.offsetting.name]: null,
[AbacusOrderType.offsetting.rawValue]: null,
[AbacusOrderType.deleveraged.name]: null,
[AbacusOrderType.deleveraged.rawValue]: null,
[AbacusOrderType.finalSettlement.name]: null,
[AbacusOrderType.finalSettlement.rawValue]: null,
};
// Custom types involving Abacus
@@ -307,3 +335,9 @@ export type NetworkConfig = Partial<{
}>;
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
export type PerpetualMarketOrderbookLevel = OrderbookLine & {
side?: 'ask' | 'bid';
mine: number;
key: string;
};
+2
View File
@@ -173,3 +173,5 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
validatorUrl: string;
}
: never;
export const DEFAULT_TRANSACTION_MEMO = 'dYdX Frontend (web)';
+4
View File
@@ -2,8 +2,10 @@ export enum DialogTypes {
ClosePosition = 'ClosePosition',
Deposit = 'Deposit',
DisconnectWallet = 'DisconnectWallet',
DisplaySettings = 'DisplaySettings',
ExchangeOffline = 'ExchangeOffline',
ExternalLink = 'ExternalLink',
ExternalNavStride = 'ExternalNavStride',
FillDetails = 'FillDetails',
Help = 'Help',
ExternalNavKeplr = 'ExternalNavKeplr',
@@ -19,6 +21,8 @@ export enum DialogTypes {
Transfer = 'Transfer',
Withdraw = 'Withdraw',
ManageFunds = 'ManageFunds',
NewMarketMessageDetails = 'NewMarketMessageDetails',
NewMarketAgreement = 'NewMarketAgreement',
}
export enum TradeBoxDialogTypes {
+23
View File
@@ -0,0 +1,23 @@
/**
* Temporary Indexer types
* remove when Indexer type lib is available through @dydxprotocol/v4-client-js
*/
export type PerpetualMarketResponse = {
clobPairId: string;
ticker: string;
status: string;
oraclePrice: string;
priceChange24H: string;
volume24H: string;
trades24H: number;
nextFundingRate: string;
initialMarginFraction: string;
maintenanceMarginFraction: string;
openInterest: string;
atomicResolution: number;
quantumConversionExponent: number;
tickSize: string;
stepSize: string;
stepBaseQuantums: number;
subticksPerTick: number;
};
+1
View File
@@ -20,6 +20,7 @@ export enum LocalStorageKey {
SelectedLocale = 'dydx.SelectedLocale',
SelectedNetwork = 'dydx.SelectedNetwork',
SelectedTheme = 'dydx.SelectedTheme',
SelectedColorMode = 'dydx.SelectedColorMode',
SelectedTradeLayout = 'dydx.SelectedTradeLayout',
TradingViewChartConfig = 'dydx.TradingViewChartConfig',
HasSeenLaunchIncentives = 'dydx.HasSeenLaunchIncentives',
+1 -1
View File
@@ -95,7 +95,7 @@ export type TooltipStrings = {
stringParams?: any;
urlConfigs?: LinksConfigs;
}) => {
title: string;
title?: string;
body: string;
learnMoreLink?: string;
};
+15
View File
@@ -0,0 +1,15 @@
/**
* @description Orderbook display constants
*/
export const ORDERBOOK_MAX_ROWS_PER_SIDE = 30;
export const ORDERBOOK_ANIMATION_DURATION = 400;
/**
* @description Orderbook pixel constants
* @note ORDERBOOK_ROW_HEIGHT should be a divisor of ORDERBOOK_HEIGHT so that we do not have a partial row at the bottom
* @note To change the Orderbook width, --orderbook-trades-width and ORDERBOOK_WIDTH must be changed to the same value
*/
export const ORDERBOOK_HEIGHT = 800;
export const ORDERBOOK_WIDTH = 300;
export const ORDERBOOK_ROW_HEIGHT = 24;
export const ORDERBOOK_ROW_PADDING_RIGHT = 8;
+25
View File
@@ -0,0 +1,25 @@
export type ExchangeConfigItem = {
exchangeName: string;
ticker: string;
adjustByMarket?: string;
};
export type PotentialMarketItem = {
baseAsset: string;
referencePrice: string;
numOracles: number;
liquidityTier: number;
assetName: string;
p: number;
atomicResolution: number;
minExchanges: number;
minPriceChangePpm: number;
priceExponent: number;
stepBaseQuantum: number;
ticksizeExponent: number;
subticksPerTick: number;
minOrderSize: number;
quantumConversionExponent: number;
};
export const NUM_ORACLES_TO_QUALIFY_AS_SAFE = 6;
+10
View File
@@ -11,6 +11,10 @@ export enum AppRoute {
Privacy = '/privacy',
}
export enum MarketsRoute {
New = 'new',
}
export enum PortfolioRoute {
Fees = 'fees',
History = 'history',
@@ -25,6 +29,12 @@ export enum HistoryRoute {
Payments = 'payments',
}
export enum TokenRoute {
TradingRewards = 'trading-rewards',
StakingRewards = 'staking-rewards',
Governance = 'governance',
}
export enum MobileSettingsRoute {
Language = 'language',
Notifications = 'notifications',
+76
View File
@@ -0,0 +1,76 @@
export enum ColorToken {
Black = '#000000',
White = '#FFFFFF',
LightGray0 = '#FAFAFA',
LightGray1 = '#F7F8FA',
LightGray2 = '#FAFAFD',
LightGray3 = '#F5F5F5',
LightGray4 = '#EDF0F2',
LightGray5 = '#EDEDED',
LightGray6 = '#E9ECEE',
LightGray7 = '#E6E6E6',
LightGray8 = '#DBDBDB',
LightGray9 = '#D5DADF',
LightGray10 = '#D1D9E0',
MediumGray0 = '#CACACE',
MediumGray1 = '#C2CBD3',
DarkGray0 = '#888891',
DarkGray1 = '#737373',
DarkGray2 = '#56565C',
DarkGray3 = '#4B4B4B',
DarkGray4 = '#494950',
DarkGray5 = '#3B3B3F',
DarkGray6 = '#303033',
DarkGray7 = '#313135',
DarkGray8 = '#292929',
DarkGray9 = '#23232F',
DarkGray10 = '#212124',
DarkGray11 = '#18181B',
DarkGray12 = '#181818',
DarkGray13 = '#0C0C0D',
GrayBlue0 = '#464659',
GrayBlue1 = '#38384D',
GrayBlue2 = '#303045',
GrayBlue3 = '#29293D',
GrayBlue4 = '#212131',
GrayBlue5 = '#181825',
GrayBlue6 = '#101018',
GrayBlue7 = '#08080C',
GrayPurple0 = '#DFDADF',
GrayPurple1 = '#C8C7D8',
GrayPurple2 = '#807E98',
Purple0 = '#7774FF',
Purple1 = '#6966FF',
Green0 = '#1AFFB9',
Green1 = '#3ED9A4',
Green2 = '#2CCC98',
Green3 = '#3EB68A',
Yellow0 = '#FFCC48',
Yellow1 = '#FFB647',
Red0 = '#FF5C5C',
Red1 = '#E76565',
Red2 = '#E45555',
}
/** Maps opacity to corresponding hex value */
export enum OpacityToken {
Opacity16 = '29',
Opacity20 = '33',
Opacity66 = 'A8',
Opacity90 = 'E6',
}
export enum BrightnessFilterToken {
Darken10 = '0.9',
Darken5 = '0.95',
Lighten10 = '1.1',
}
+104
View File
@@ -0,0 +1,104 @@
import { AppColorMode } from '@/state/configs';
export type Theme = {
[AppColorMode.GreenUp]: ThemeColorBase;
[AppColorMode.RedUp]: ThemeColorBase;
};
export type ThemeColorBase = BaseColors &
LayerColors &
BorderColors &
TextColors &
GradientColors &
AccentColors &
StatusColors &
DirectionalColors &
RiskColors &
IconColors &
ComponentColors &
Filters;
type BaseColors = {
white: string;
green: string;
red: string;
};
type LayerColors = {
layer0: string;
layer1: string;
layer2: string;
layer3: string;
layer4: string;
layer5: string;
layer6: string;
layer7: string;
};
type BorderColors = {
borderDefault: string;
borderDestructive: string;
borderButton: string;
};
type TextColors = {
textPrimary: string;
textSecondary: string;
textTertiary: string;
textButton: string;
};
type GradientColors = {
gradientBase0: string;
gradientBase1: string;
};
type AccentColors = {
accent: string;
accentFaded: string;
favorite: string;
};
type StatusColors = {
success: string;
warning: string;
error: string;
successFaded: string;
warningFaded: string;
errorFaded: string;
};
/** ##InvertDirectionalColors
* When adding colors here, make sure to update linked function to invert colors for AppColorMode. */
type DirectionalColors = {
positive: string;
negative: string;
positiveFaded: string;
negativeFaded: string;
};
type RiskColors = {
riskLow: string;
riskMedium: string;
riskHigh: string;
};
type IconColors = {
logoFill: string;
profileYellow: string;
profileRed: string;
};
type ComponentColors = {
inputBackground: string;
popoverBackground: string;
toggleBackground: string;
tooltipBackground: string;
};
type Filters = {
hoverFilterBase: string;
hoverFilterVariant: string;
activeFilter: string;
};
+20 -2
View File
@@ -57,10 +57,9 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_BODY }),
}),
'initial-margin-fraction': ({ stringGetter, urlConfigs }) => ({
'initial-margin-fraction': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_BODY }),
learnMoreLink: urlConfigs?.initialMarginFractionLearnMore,
}),
'initial-stop': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_STOP_TITLE }),
@@ -138,6 +137,10 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_BODY }),
}),
'post-only-timeinforce-gtt': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TIMEINFORCE_GTT_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TIMEINFORCE_GTT_BODY }),
}),
'price-impact': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_BODY }),
@@ -151,6 +154,18 @@ export const tradeTooltips: TooltipStrings = {
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_BODY }),
learnMoreLink: urlConfigs?.reduceOnlyLearnMore,
}),
'reduce-only-execution-ioc-fok': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_EXECUTION_IOC_FOK_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_EXECUTION_IOC_FOK_BODY }),
}),
'reduce-only-timeinforce-ioc-fok': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TIMEINFORCE_IOC_FOK_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TIMEINFORCE_IOC_FOK_BODY }),
}),
'reference-price': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REFERENCE_PRICE_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REFERENCE_PRICE_BODY }),
}),
spread: () => ({
title: 'Spread',
body: 'The difference in price between the highest bid (the price a buyer is willing to buy for) and lowest ask (the price a seller is willing to sell for) an asset.',
@@ -183,4 +198,7 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.UNREALIZED_PNL_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.UNREALIZED_PNL_BODY }),
}),
'reward-history': ({ stringGetter }) => ({
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REWARD_HISTORY_BODY }),
}),
} as const;
+13 -1
View File
@@ -33,6 +33,7 @@ export enum WalletConnectionType {
CosmosSigner = 'CosmosSigner',
InjectedEip1193 = 'injectedEip1193',
WalletConnect2 = 'walletConnect2',
TestWallet = 'TestWallet',
}
export enum WalletErrorType {
@@ -68,6 +69,9 @@ export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectio
[WalletConnectionType.CosmosSigner]: {
name: 'CosmosSigner',
},
[WalletConnectionType.TestWallet]: {
name: 'TestWallet',
},
};
// Wallets
@@ -88,7 +92,7 @@ export enum WalletType {
TokenPocket = 'TOKEN_POCKET',
TrustWallet = 'TRUST_WALLET',
WalletConnect2 = 'WALLETCONNECT_2',
// TestWallet = 'TEST_WALLET',
TestWallet = 'TEST_WALLET',
OtherWallet = 'OTHER_WALLET',
}
@@ -243,6 +247,12 @@ export const wallets: Record<WalletType, WalletConfig> = {
icon: KeplrIcon,
connectionTypes: [WalletConnectionType.CosmosSigner],
},
[WalletType.TestWallet]: {
type: WalletType.TestWallet,
stringKey: STRING_KEYS.TEST_WALLET,
icon: GenericWalletIcon,
connectionTypes: [WalletConnectionType.TestWallet],
},
};
// Injected EIP-1193 Providers
@@ -354,3 +364,5 @@ export enum DydxChainAsset {
USDC = 'usdc',
CHAINTOKEN = 'chain',
}
export const TEST_WALLET_EVM_ADDRESS: EvmAddress = '0x0000000000000000000000000000000000000000';
+4
View File
@@ -0,0 +1,4 @@
export * from './useCenterOrderbook';
export * from './useSpreadRowScrollListener';
export * from './useDrawOrderbook';
export * from './useOrderbookValues';
+22
View File
@@ -0,0 +1,22 @@
import { useEffect, type RefObject } from 'react';
type Props = {
marketId: string;
orderbookRef: RefObject<HTMLDivElement>;
};
/**
* @description Center Orderbook on load and market change
* Assumed that the two sides are the same height
*/
export const useCenterOrderbook = ({ marketId, orderbookRef }: Props) => {
const orderbookEl = orderbookRef.current;
const { clientHeight, scrollHeight } = orderbookEl ?? {};
const shouldScroll = scrollHeight && clientHeight && scrollHeight > clientHeight;
useEffect(() => {
if (orderbookEl && shouldScroll) {
orderbookEl.scrollTo({ top: (scrollHeight - clientHeight) / 2 });
}
}, [shouldScroll, marketId]);
};
+373
View File
@@ -0,0 +1,373 @@
import { useEffect, useMemo, useRef } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { SMALL_USD_DECIMALS, TOKEN_DECIMALS } from '@/constants/numbers';
import {
ORDERBOOK_ANIMATION_DURATION,
ORDERBOOK_HEIGHT,
ORDERBOOK_ROW_HEIGHT,
ORDERBOOK_ROW_PADDING_RIGHT,
ORDERBOOK_WIDTH,
} from '@/constants/orderbook';
import { useAppThemeAndColorModeContext } from '@/hooks/useAppThemeAndColorMode';
import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors';
import { MustBigNumber } from '@/lib/numbers';
import {
getHistogramXValues,
getRektFromIdx,
getXByColumn,
getYForElements,
} from '@/lib/orderbookHelpers';
type ElementProps = {
data: Array<PerpetualMarketOrderbookLevel | undefined>;
histogramRange: number;
side: PerpetualMarketOrderbookLevel['side'];
};
type StyleProps = {
histogramSide: 'left' | 'right';
};
enum OrderbookRowAnimationType {
REMOVE,
NEW,
NONE,
}
export const useDrawOrderbook = ({
data,
histogramRange,
histogramSide,
side,
}: ElementProps & StyleProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvas = canvasRef.current;
const currentOrderbookMap = useSelector(getCurrentMarketOrderbookMap, shallowEqual);
const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } =
useSelector(getCurrentMarketConfig, shallowEqual) || {};
const prevData = useRef<typeof data>(data);
const theme = useAppThemeAndColorModeContext();
/**
* Scale canvas using device pixel ratio to unblur drawn text
* @url https://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas/65124939#65124939
* @returns adjusted canvas width/height/rowHeight used in coordinates for drawing
**/
const { canvasWidth, canvasHeight, rowHeight } = useMemo(() => {
const ratio = window.devicePixelRatio || 1;
if (!canvas) {
return {
canvasWidth: ORDERBOOK_WIDTH / devicePixelRatio,
canvasHeight: ORDERBOOK_HEIGHT / devicePixelRatio,
rowHeight: ORDERBOOK_ROW_HEIGHT / devicePixelRatio,
};
}
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
if (ctx) {
ctx.scale(ratio, ratio);
ctx.font = '12px Satoshi';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.imageSmoothingQuality = 'high';
}
return {
canvasWidth: canvas.width / ratio,
canvasHeight: canvas.height / ratio,
rowHeight: ORDERBOOK_ROW_HEIGHT,
};
}, [canvas]);
const drawBars = ({
barType,
ctx,
depthOrSizeValue,
gradientMultiplier,
histogramAccentColor,
histogramSide,
idx,
}: {
barType: 'depth' | 'size';
ctx: CanvasRenderingContext2D;
depthOrSizeValue: number;
gradientMultiplier: number;
histogramAccentColor: string;
histogramSide: 'left' | 'right';
idx: number;
}) => {
const { x1, x2, y1, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
// X values
const maxHistogramBarWidth = x2 - x1 - (barType === 'size' ? 8 : 2);
const barWidth = depthOrSizeValue
? Math.min((depthOrSizeValue / histogramRange) * maxHistogramBarWidth, maxHistogramBarWidth)
: 0;
const { gradient, bar } = getHistogramXValues({
barWidth,
canvasWidth,
gradientMultiplier,
histogramSide,
});
// Gradient
let linearGradient;
try {
linearGradient = ctx.createLinearGradient(gradient.x1, y1, gradient.x2, y2);
linearGradient.addColorStop(0, histogramAccentColor);
linearGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = linearGradient;
} catch (err) {
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
}
ctx.beginPath();
// Bar
const { bar: y } = getYForElements({ y: y1, rowHeight });
if (ctx.roundRect) {
ctx.roundRect(
bar.x1,
y,
bar.x2,
rowHeight - 2,
histogramSide === 'right' ? [2, 0, 0, 2] : [0, 2, 2, 0]
);
} else {
ctx.rect(bar.x1, y, bar.x2, rowHeight - 2);
}
ctx.fill();
};
const drawText = ({
animationType = OrderbookRowAnimationType.NONE,
ctx,
idx,
mine,
price,
size,
}: {
animationType?: OrderbookRowAnimationType;
ctx: CanvasRenderingContext2D;
idx: number;
mine?: number;
price?: number;
size?: number;
}) => {
const { y1 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
const { text: y } = getYForElements({ y: y1, rowHeight });
let textColor: string;
let updatedTextColor: string | undefined;
switch (animationType) {
case OrderbookRowAnimationType.REMOVE: {
textColor = theme.textSecondary;
break;
}
case OrderbookRowAnimationType.NEW: {
updatedTextColor = side === 'bid' ? theme.positive : theme.negative;
textColor = theme.textPrimary;
break;
}
default: {
textColor = theme.textPrimary;
break;
}
}
// Size text
if (size) {
ctx.fillStyle = updatedTextColor ?? textColor;
ctx.fillText(
MustBigNumber(size).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 0 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
// Price text
if (price) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(price).toFixed(tickSizeDecimals ?? SMALL_USD_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 1 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
// Mine text
if (mine) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(mine).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 2 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
};
const drawOrderbookRow = ({
ctx,
idx,
rowToRender,
animationType = OrderbookRowAnimationType.NONE,
}: {
ctx: CanvasRenderingContext2D;
idx: number;
rowToRender?: PerpetualMarketOrderbookLevel;
animationType?: OrderbookRowAnimationType;
}) => {
if (!rowToRender) return;
const { depth, mine, price, size } = rowToRender;
const histogramAccentColor = side === 'bid' ? theme.positiveFaded : theme.negativeFaded;
// Depth Bar
if (depth) {
drawBars({
barType: 'depth',
ctx,
depthOrSizeValue: depth,
gradientMultiplier: 1.3,
histogramAccentColor,
histogramSide,
idx,
});
}
// Size Bar
drawBars({
barType: 'size',
ctx,
depthOrSizeValue: size,
gradientMultiplier: 5,
histogramAccentColor,
histogramSide,
idx,
});
// Size, Price, Mine
drawText({
animationType,
ctx,
idx,
mine,
price,
size,
});
};
// Update histograms and row contents on data change
useEffect(() => {
const ctx = canvas?.getContext('2d');
if (!canvas || !ctx) return;
// Clear canvas before redraw
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// Animate row removal and update
const mapOfOrderbookPriceLevels =
side && currentOrderbookMap?.[side === 'ask' ? 'asks' : 'bids'];
const empty: number[] = [];
const removed: number[] = [];
const updated: number[] = [];
prevData.current.forEach((row, idx) => {
if (!row) {
empty.push(idx);
return;
}
if (mapOfOrderbookPriceLevels?.[row.price] === 0) {
removed.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.REMOVE,
});
} else if (mapOfOrderbookPriceLevels?.[row.price] === row?.size) {
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
animationType: OrderbookRowAnimationType.NONE,
});
} else {
updated.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.NEW,
});
}
});
setTimeout(() => {
[...empty, ...removed, ...updated].forEach((idx) => {
const { x1, y1, x2, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
});
});
}, ORDERBOOK_ANIMATION_DURATION);
prevData.current = data;
}, [
canvasHeight,
canvasWidth,
rowHeight,
data,
histogramRange,
stepSizeDecimals,
tickSizeDecimals,
histogramSide,
side,
theme,
currentOrderbookMap,
]);
return { canvasRef };
};
+126
View File
@@ -0,0 +1,126 @@
import { useMemo } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getSubaccountOpenOrdersBySideAndPrice } from '@/state/accountSelectors';
import { MustBigNumber } from '@/lib/numbers';
export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const openOrdersBySideAndPrice =
useSelector(getSubaccountOpenOrdersBySideAndPrice, shallowEqual) || {};
return useMemo(() => {
const asks: Array<PerpetualMarketOrderbookLevel | undefined> = (
orderbook?.asks?.toArray() ?? []
)
.map(
(row, idx: number) =>
({
key: `ask-${idx}`,
side: 'ask',
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
...row,
} as PerpetualMarketOrderbookLevel)
)
.slice(0, maxRowsPerSide);
const bids: Array<PerpetualMarketOrderbookLevel | undefined> = (
orderbook?.bids?.toArray() ?? []
)
.map(
(row, idx: number) =>
({
key: `bid-${idx}`,
side: 'bid',
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
...row,
} as PerpetualMarketOrderbookLevel)
)
.slice(0, maxRowsPerSide);
// Prevent the bid/ask sides from crossing by using the offsets.
// While the books are crossing...
while (asks[0] && bids[0] && bids[0]!.price >= asks[0].price) {
// Drop the order on the side with the lower offset.
// The offset of the other side is higher and so supercedes.
if (bids[0]!.offset === asks[0].offset) {
// If offsets are the same, give precedence to the larger size. In this case,
// one of the sizes *should* be zero, but we simply check for the larger size.
if (bids[0]!.size > asks[0].size) {
asks.shift();
} else {
bids.pop();
}
} else {
// Offsets are not equal. Give precedence to the larger offset.
if (bids[0]!.offset > asks[0].offset) {
asks.shift();
} else {
bids.pop();
}
}
}
const spread =
asks[0]?.price && bids[0]?.price ? MustBigNumber(asks[0].price).minus(bids[0].price) : null;
const spreadPercent = orderbook?.spreadPercent;
const histogramRange = Math.max(
isNaN(Number(bids[bids.length - 1]?.depth)) ? 0 : Number(bids[bids.length - 1]?.depth),
isNaN(Number(asks[asks.length - 1]?.depth)) ? 0 : Number(asks[asks.length - 1]?.depth)
);
return {
asks,
bids,
spread,
spreadPercent,
histogramRange,
hasOrderbook: !!orderbook,
};
}, [orderbook, openOrdersBySideAndPrice]);
};
export const useOrderbookValuesForDepthChart = () => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
return useMemo(() => {
const bids = (orderbook?.bids?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
const asks = (orderbook?.asks?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
const lowestBid = bids[bids.length - 1];
const highestBid = bids[0];
const lowestAsk = asks[0];
const highestAsk = asks[asks.length - 1];
const midMarketPrice = orderbook?.midPrice;
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
const spreadPercent = orderbook?.spreadPercent;
return {
bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
orderbook,
};
}, [orderbook]);
};
@@ -0,0 +1,39 @@
import { type RefObject, useEffect, useState } from 'react';
export const useSpreadRowScrollListener = ({
orderbookRef,
spreadRowRef,
}: {
orderbookRef: RefObject<HTMLDivElement>;
spreadRowRef: RefObject<HTMLDivElement>;
}) => {
const [displaySide, setDisplaySide] = useState<string>();
useEffect(() => {
const onScroll = () => {
if (spreadRowRef.current && orderbookRef.current) {
const { clientHeight } = orderbookRef.current;
const parent = orderbookRef.current.getBoundingClientRect();
const spread = spreadRowRef.current.getBoundingClientRect();
const spreadTop = spread.top - parent.top;
const spreadBottom = spread.bottom - parent.top;
if (spreadBottom > clientHeight) {
setDisplaySide('bottom');
} else if (spreadTop < 0) {
setDisplaySide('top');
} else {
setDisplaySide(undefined);
}
}
};
orderbookRef.current?.addEventListener('scroll', onScroll, false);
return () => {
orderbookRef.current?.removeEventListener('scroll', onScroll, false);
};
}, [orderbookRef.current, spreadRowRef.current]);
return displaySide;
};
+4
View File
@@ -7,6 +7,7 @@ import { useDebounce } from './useDebounce';
import { useInterval } from './useInterval';
import { useDocumentTitle } from './useDocumentTitle';
import { useDydxClient } from './useDydxClient';
import { useGovernanceVariables } from './useGovernanceVariables';
import { useAccountBalance } from './useAccountBalance';
import { useAccounts } from './useAccounts';
import { useAnalytics } from './useAnalytics';
@@ -14,6 +15,7 @@ import { useInitializePage } from './useInitializePage';
import { useIsFirstRender } from './useIsFirstRender';
import { useLocaleSeparators } from './useLocaleSeparators';
import { useLocalStorage } from './useLocalStorage';
import { useNextClobPairId } from './useNextClobPairId';
import { useNow } from './useNow';
import { useOnClickOutside } from './useOnClickOutside';
import { usePageTitlePriceUpdates } from './usePageTitlePriceUpdates';
@@ -34,6 +36,7 @@ export {
useDebounce,
useDocumentTitle,
useDydxClient,
useGovernanceVariables,
useAccountBalance,
useAccounts,
useAnalytics,
@@ -42,6 +45,7 @@ export {
useIsFirstRender,
useLocaleSeparators,
useLocalStorage,
useNextClobPairId,
useNow,
useOnClickOutside,
usePageTitlePriceUpdates,
+11 -3
View File
@@ -11,7 +11,7 @@ import { useDydxClient, useLocalStorage } from '@/hooks';
import { store } from '@/state/_store';
import { getSelectedNetwork } from '@/state/appSelectors';
import { getAppTheme } from '@/state/configsSelectors';
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
import { getSelectedLocale } from '@/state/localizationSelectors';
import { getCurrentMarketId, getMarketIds } from '@/state/perpetualsSelectors';
@@ -30,6 +30,7 @@ export const useTradingView = ({
}) => {
const marketId = useSelector(getCurrentMarketId);
const appTheme = useSelector(getAppTheme);
const appColorMode = useSelector(getAppColorMode);
const marketIds = useSelector(getMarketIds, shallowEqual);
const selectedLocale = useSelector(getSelectedLocale);
const selectedNetwork = useSelector(getSelectedNetwork);
@@ -46,7 +47,7 @@ export const useTradingView = ({
useEffect(() => {
if (hasMarkets && isClientConnected && marketId) {
const widgetOptions = getWidgetOptions();
const widgetOverrides = getWidgetOverrides(appTheme);
const widgetOverrides = getWidgetOverrides({ appTheme, appColorMode });
const options = {
// debug: true,
...widgetOptions,
@@ -75,7 +76,14 @@ export const useTradingView = ({
tvWidgetRef.current = null;
setIsChartReady(false);
};
}, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork, !!marketId]);
}, [
getCandlesForDatafeed,
isClientConnected,
hasMarkets,
selectedLocale,
selectedNetwork,
!!marketId,
]);
return { savedResolution };
};

Some files were not shown because too many files have changed in this diff Show More