Compare commits

...
Author SHA1 Message Date
mulan xia e01f763481 add transparency to button + destructive borders 2024-01-24 12:56:20 -05:00
mulan xia 697b97233b small lint things 2024-01-24 11:21:55 -05:00
mulan xia 16136c3617 remove colors.css file 2024-01-23 18:17:12 -05:00
mulan xia 13da52f31c fix logo in light mode 2024-01-23 17:19:31 -05:00
mulan xia 1fd31497eb move files, fix text colors 2024-01-23 15:21:34 -05:00
mulan xia 89c487c2b0 move usage style helper to lib/styles 2024-01-23 13:54:08 -05:00
mulan xia 6e531abf04 fix eol lint 2024-01-23 12:21:39 -05:00
mulan xia b32a6c8c05 update to use tokens, small fixes 2024-01-23 11:32:56 -05:00
mulan xia 1e9209028e fix lint error 2024-01-22 16:42:19 -05:00
mulan xia 2861286bdb introduce tokens file + theme type 2024-01-22 16:28:37 -05:00
Bill e93850484b CCTP deposit/withdraw to throw error if nobleClient is not initialized (#215)
* CCTP deposit/withdraw to throw error if nobleClient is not initialized

* update error str
2024-01-22 09:55:26 -08:00
aleka 521b6d6135 test flag for sign in option (#236) 2024-01-17 16:22:17 -08:00
aleka 94772bf4dc bump abacus to remove base position notional (#235) 2024-01-17 15:52:02 -08:00
Adam Fraser 149f21e7e0 Add trailing blocks to NetworkStatus analytics event (#230) 2024-01-17 10:34:04 -05:00
aleka 0a3ef3a7ab bump abacus (#233) 2024-01-17 10:33:29 -05:00
Bill fef9935883 Support notification for trading rewards (#224)
* Support notification for trading rewards

* bump abacus

* Stack notifications based on notification type

* address comments
2024-01-16 10:19:48 -08:00
Bill c7a5b706e1 Fix toAmount/toAmountMin on withdraw (#232) 2024-01-15 10:31:23 -08:00
moo-onthelawn f304bb069a TRCL-1979 Show open positions / orders in portfolio sidebar (#228)
* add tags for open orders + positions

* clean up, use new num selector function

* update logic for open orders
2024-01-12 14:44:09 -05:00
Bill ad6aa1af49 Bump abacus, fix onboarding toAmount (#229) 2024-01-11 11:52:02 -08:00
Jared Vu 55b2c3fb4a Update Funding Chart Tooltip (#225)
* 🚧 depth chart

*  Fixed types and shortened FundingRateTooltip

* 💄 update depth chart color scale to use css var

* 💄 use layer-6 instead of text-1

* 🌐 Add localization, fix nits
2024-01-11 10:31:53 -08:00
moo-onthelawn 83bcde00eb TRCL-1623 Update Positions icon in nav sidebar (#226)
* update icon

* remove unused cube icon
2024-01-11 11:02:23 -05:00
Jared Vu e8024b9337 Add Sepolia (#222) 2024-01-09 16:35:19 -08:00
Jared Vu 8280e512bc Remove BasePositionNotional DetailItem (#223) 2024-01-09 14:01:38 -08:00
aleka b2234cb969 reorganize rewards vs. profile, update panel and accordion styling (#221)
* reorganize rewards vs. profile, update panel and accordion styling

* feedback
2024-01-09 16:57:02 -05:00
Bill 4bcb1f64ac Add Kava support for onboarding, and add token symbol to token select (#220)
* Add Kava support for onboarding, and add token symbol to token select

* bump abacus
2024-01-09 11:51:31 -08:00
BillandAleka Cheung 4a64d59546 Update launch incentives to season 2 (#218)
* Update launch incentives to season 2

* update api url

---------

Co-authored-by: Aleka Cheung <aforaleka@gmail.com>
2024-01-04 19:14:23 -08:00
Jared Vu 3fc43e8a12 Remove base from baseInitialMarginFraction (#209) 2024-01-04 15:35:05 -08:00
Jared Vu c24a9229a6 Imperator testnet indexer to be scaled down (#219) 2024-01-04 15:34:18 -08:00
aleka 90329d44d0 update lock file (#217) 2024-01-03 13:57:21 -08:00
Bill 9a02bd3f1c remove testnet cctp chains (#216) 2024-01-03 12:55:58 -08:00
aleka adba11b462 add accordion component and rewards help panel (#214) 2024-01-03 14:20:30 -05:00
Jared Vu 29526b927f Update deposit and withdrawal receipts (#212)
* Update receipts

* Remove duplicate const

* Restore env

* format

* Try using raw

* Use 3 spaces T.T

* copy from main

* Update abacus, display proper toAmount and toAmountMin

* nits
2024-01-03 09:56:01 -08:00
Jared Vu 503e089efa temp hide fees for cctp route (#213) 2023-12-23 01:17:41 -08:00
Jared Vu c3495f462c Additional Withdraw/Deposit validation (#211)
* Add cctp amount check and price impact check

* 2.5% -> 2%
2023-12-22 17:31:32 -08:00
Billandjaredvu 6da6eeb2a4 Enable CCTP (#194)
* Enable CCTP

* Handle base64 encode

* Update localization package

---------

Co-authored-by: jaredvu <jaredvu@gmail.com>
2023-12-22 11:12:41 -08:00
mike-dydx 0cf3e81bd7 Create documentation.json (#205) 2023-12-21 15:08:08 -05:00
aleka f8cf1d364b update coinbase connect app name (#208) 2023-12-21 10:58:57 -08:00
Jared Vu 6f344c6b5d Update comparison function in Table (#207) 2023-12-21 10:53:56 -08:00
Jared Vu 133856afca Export secret phrase help article (#206)
* Update config with correct article link

* Remove auto-format of json
2023-12-21 10:49:52 -08:00
Jared Vu 33c543daaf Update bridgeFee in Withdraw/Deposit forms (#202)
* Update Withdraw form receipt

* Bump abacus 1.1.27

* pnpm-lock

* bump v4-localization
2023-12-19 23:36:13 -08:00
Jared Vu c77e727d2b TRCL-3406: firefox mobile position table row has stretched icon (#204)
* Fix PositionsTable assetIcon

* Mobile width of HelpDialog
2023-12-19 23:33:36 -08:00
Jared Vu b94538711d Lazy load Pages (#203)
* Lazy import pages

* Fix import order
2023-12-19 23:30:51 -08:00
Jared Vu b83479f45c Handle New Markets added by Gov (#201)
* Add celestia assets

* Handle symbol not yet added
2023-12-19 23:25:14 -08:00
Jared Vu 001aad3abc Navigate to the Markets page if the current marketId is not included in subscribed markets (#198)
* Navigate to markets if current url is invalid

* nits
2023-12-19 15:58:49 -08:00
Jared Vu f8db5e1ba0 Update abacus to 1.1.25: Good til time fix (#199) 2023-12-19 09:49:51 -08:00
Jared Vu f8bec0fb3d Update Withdraw form receipt (#200) 2023-12-19 09:49:37 -08:00
Bill 8bf23b7e9c bump abacus (#197) 2023-12-15 15:12:35 -08:00
Jared Vu 3f10e045f4 bump abacus & wagmi (#196) 2023-12-15 09:26:41 -08:00
Bill 71d1a0321b Do not block website if NobleClient cannot init, and other onboarding fixes (#193) 2023-12-13 08:11:19 -08:00
Bill 0f16396c8d Implement CCTP withdraw (#188)
* Implement CCTP withdraw

* bump packages

* address comments
2023-12-12 10:12:37 -08:00
Jared Vu 2709d79f59 Firefox issues (#190)
* remove row flicker on hover

* Fix border for AccountInfoConnectedState on mobile

* Remove sticky position to allow button click

* Fix: AccountInfo not displaying on mobile firefox

* Fix css var name

* Restore filter on Table row hover

* Remove PerspectiveArea. broken on firefox

* Symbol no longer exists on AbacusType

* HorizontalPanel: memoize tabConfig

* input: firefox cant click between chars
2023-12-12 08:37:50 -08:00
Bill ab0eec5d4f Update noble tesnet rpc (#192) 2023-12-12 11:35:21 -05:00
Jared Vu 4a044da38f Size inputs (#191)
* use formattedValue to update redux state

* Separate updateLeverage and onLeverageInput

* Remove console.log
2023-12-12 07:11:51 -08:00
Jared Vu fbbfbc6580 Bump v4-abacus and v4-localization (#189) 2023-12-08 15:41:07 -08:00
Rui 2714c2b084 Fix path to chainLogo (#187) 2023-12-07 13:03:44 -08:00
Jared Vu 9979bc8a16 Prevent Spread row from moving when orderbook updates (#186)
* Add dummy rows to ensure even row count

* Reverse bids for horizontal layout

* disable pointer-events for dummy rows
2023-12-06 10:26:47 -08:00
aleka a958a59ea2 Display more markets 12/06 (#178)
* add more markets 12/06
2023-12-06 11:38:49 -05:00
Jared Vu 2bf9e66da5 Fix inputs for comma locales (#171)
* Fix for comma decimal separator locales

* Accept tickSizeDecimals 0

* Use FloatValue as value for Size Input

* Use formattedValue to handle prefix/suffix

* Use MustBigNumber.toString()
2023-12-05 16:22:41 -08:00
Bill 2980144e40 Enable CCTP via TestFlags (#185)
* Enable CCTP via Env Var

* Use test flags instead
2023-12-05 14:48:47 -08:00
aleka 5387fe8632 Display more markets 12/05 (#177)
* add more markets 12/05
2023-12-05 09:52:12 -05:00
Bill 1132ea3bbd Do not init nobleClient if nobleValidatorUrl does not exist (#184) 2023-12-04 11:19:29 -08:00
aleka 9188e08a4f prevent 1password fill (#183) 2023-12-04 11:06:49 -05:00
aleka 941ef7f9dd Display more markets 12/04 (#176)
* add more markets 11/30

* add more markets 12/01

* add more markets 12/04
2023-12-04 10:53:25 -05:00
Bill b1ffa2e219 Support CCTP and noble auto deposits on testnet (#180)
* Support CCTP and noble auto deposits on testnet

* slippage

* fix fees

* update isCctp, and comments

* bump packages

* cctp.json

* Bump abacus

* fix error

* fix loading button
2023-12-01 15:18:50 -08:00
aleka 91ea68dc53 Add chaos labs launch incentives panel (#182)
* add chaos lab incentives panel

* add seen state

* external link dialog

* better disconnected state
2023-12-01 15:27:19 -05:00
aleka cf79528d05 Display more markets 12/01 (#175)
* add more markets 12/01
2023-12-01 09:59:04 -05:00
Bill ef81f9da57 Add nobleValidator to config (#181) 2023-11-30 16:11:55 -08:00
aleka 60d59cd211 add more markets 11/30 (#174) 2023-11-30 10:06:25 -05:00
aleka 19d38378f3 display more markets (#173) 2023-11-29 10:33:02 -05:00
Bill 15a64232c0 Fix USDT approval (#170)
* Fix USDT approval

* address comment
2023-11-28 11:44:21 -08:00
aleka 28d3be0370 Release notification (#169)
* add release notif
2023-11-28 12:36:29 -05:00
Rui 90ff9ccc5a Add token address to cctp.json (#167)
* Add token address to cctp.json

* Update field name to tokenAddress
2023-11-27 17:55:52 -08:00
Jared Vu 33516422e7 Filter markets and add test flag (#168) 2023-11-27 17:05:19 -08:00
Rui eb31610a38 Add CCTP supported chains to cctp.json (#166) 2023-11-27 14:27:42 -08:00
Jared Vu b0e1655018 Bump Abacus@1.0.30 (#165) 2023-11-27 13:01:25 -08:00
Jared Vu b4cc5f87ec Add href prop to button component (#164)
* Add href prop to button component

* Use ButtonType.Link
2023-11-27 12:39:57 -08:00
Bill 549ae558e7 Default Locale to EN if not supported (#163) 2023-11-27 08:57:57 -08:00
Jared Vu 5b06110e2d Update Abacus@1.0.28 (#161) 2023-11-22 14:08:03 -08:00
aleka e3da2ee4b1 update copy and other polish (#157)
* update copy and other polish

* fix mobile nav menu nested buttons

* feedback
2023-11-22 15:06:21 -05:00
Rui 6b38d68b09 Remove universal link for trust wallet since it's no longer valid. (#160) 2023-11-21 19:25:03 -08:00
Jared Vu 1dedc93a56 Mobile friendly Toast: Collapse/Close (#159) 2023-11-21 19:24:19 -08:00
Jared Vu 1427b55728 MarketsTable: Sort OpenInterest by notional, Display 24h volume as fiat value (#158)
* Fix Sort by notional, Display fiat for volume

* OpenInterest notional on top
2023-11-21 16:10:23 -08:00
Jared Vu 20521b5b3f Add button to stack/unstack Toast notifications (#149)
* Add button to stack/unstack

* Button -> Toggle button, add offset for notifs
2023-11-20 15:12:38 -08:00
Jared Vu 532df49a06 Use new redux tradeFormInputs object for TradingForm input display values (#144)
* Redux slice for TradeFormInputs

* Move sideEffects from TradeForm to hook in Trade
2023-11-20 11:56:09 -08:00
aleka fb65db633a move buttons to footer in ack terms (#155) 2023-11-20 13:21:06 -05:00
aleka 279ef6b4f9 bump abacus (#153) 2023-11-13 10:57:40 -05:00
aleka 0def18db07 fix sorting 24h price change in markets table (#152) 2023-11-13 09:36:32 -05:00
Jared Vu 50bd860ced Bump Abacus 1.0.25 (#151) 2023-11-13 09:36:16 -05:00
Jared Vu 818574cedb Change dep in hook to fix input bug (#150) 2023-11-13 09:11:54 -05:00
aleka 3be0429a60 add back border to toggle buttons (#148) 2023-11-11 20:46:04 -08:00
aleka b8e72246b9 add manage funds dialog and transfers form styling (#147)
* add manage funds dialog and transfers form styling

* add sticky footer backdrop

* add testnet deposit toggle
2023-11-10 20:56:27 -08:00
Jared VuandBill He 99e708a984 Notifications Refresh (#135)
* Remove X button from tablet/mobile Notification menu

* Toast styling

* missed in merge

* .env.example

* Update abacus@1.0.9

* Add notif strings, and abaucs notif state

* Update Notification hooks, add Notification Preference controls

* Consolidate Notification into single component

* withLabel -> withTrigger prop

* nits

* OrderStatusChanged -> AbacusGenerated

* Notification OnClick

* Move Notification -> Components

* withAnimation -> $withAnimation

* Notification styling and click handle

* load state, onClick, localStorage

* add error count

* Rez localStorage Notifications

* Update TransferStatus UI

* Use Notification component for other abacusNotifs

* fix uselocalnotif query

* Default to Notification from NotificationMenu

---------

Co-authored-by: Bill He <bill@dydx.exchange>
2023-11-10 20:55:34 -08:00
aleka 876f620ada Account menu and components improvements (#145)
* better copy button

* hide sign into mobile for production and use new copy icon button

* new checkbox
2023-11-10 22:07:29 -05:00
Bill b9550890da Add additional onboarding entry points (#146)
* Add additional onboarding entry points

* flex-wrap
2023-11-10 19:05:46 -08:00
aleka 3732b955f5 add sample terms of use (#138)
* add sample terms of use

* add back article mixin
2023-11-10 14:58:26 -05:00
aleka 253bfc0eff update nav menu dropdown behavior to click instead of hover to open (#142) 2023-11-10 14:58:17 -05:00
Bill d4bcfc0428 Fix status empty bug (#143) 2023-11-10 09:55:54 -08:00
aleka 7dec32df5e fix 24h sort in market dropdown, table styling (#141) 2023-11-09 16:40:27 -05:00
aleka e09d574d5b show deposit form in mainnet onboarding (#140) 2023-11-09 15:33:16 -05:00
Bill 944fc6dc95 Handle squid rout errors (#139)
* Handle squid route errors

* bump abacus

* add dep

* address comments

* fix max

* disable slippage editor

* parse error message in abacus
2023-11-09 10:34:22 -08:00
aleka 91a97a1c68 add network select menu in restriction modals (dev) (#137) 2023-11-09 09:10:12 -05:00
aleka c5f736d805 fix deposit dialog logic (#136) 2023-11-08 14:01:16 -05:00
aleka 3769f7078c update readme re env mode (#133) 2023-11-07 20:28:56 -08:00
Jared Vu edc9fadfee Add TestFlags provider and class (#132)
* Add TestFlags provider and class

* import order nit

* markets is an object and not array

* Use null coalescing op
2023-11-07 20:28:42 -08:00
aleka 5e2d426cd1 Send native tokens inclusive fee (#118)
* send dv4tnt inclusive of fees
2023-11-07 22:42:41 -05:00
Bill 766209baf1 Onboarding fixes (#131)
* Onboarding fixes

* address comments

* rebase
2023-11-07 17:18:16 -08:00
aleka 906faa554d Surface close position input errors (#130)
* show abacus close position validation errors

* bump abacus version
2023-11-07 19:11:08 -05:00
John Huang c339eeccde TRCL-3077 Added an environment for testing. Expected behavior - when "v4 Staging Forced Update" is selected in iOS app, it should switch to the forced update screen (#99) 2023-11-07 13:33:06 -08:00
aleka bd03fb73ca update mintscan tx link on transfer history table (#128)
* update mintscan tx link on transfer history table

* bump localization + fix icon button padding in header
2023-11-07 16:00:22 -05:00
Rui 31825a8a52 Change "iOS" to "ios" for consistency (#129) 2023-11-07 11:01:41 -08:00
John Huang 8d5bf1a721 TRCL-3128 TradePlaceOrder and TradeCancelOrder are now tracked by Abacus (#115) 2023-11-07 10:34:06 -08:00
aleka 7f3d57b46a support multiple modes (#125) 2023-11-07 12:13:59 -05:00
Bill c292c84c2c Fix decimal overflow bug (#127)
* Fix decimal overflow bug

* use decimals:
2023-11-06 17:48:19 -08:00
Bill 2d37ff676b do not use TESTNET_CHAIN_ID (#126) 2023-11-06 17:39:02 -08:00
Rui 68f3552569 Add app configuration to env.json (#121) 2023-11-06 13:02:38 -08:00
aleka 18b12591ca Update LICENSE (#123) 2023-11-06 11:10:31 -08:00
Bill 3f963ffb07 Add Env var descriptions to readme (#124)
* Add Env var descriptions to readme

* update

* remove examples

* grammar
2023-11-06 10:50:45 -08:00
aleka aac024fd5e showing negative buying power as - (#119) 2023-11-06 13:31:41 -05:00
Christopher-Li 4913c690ec Update all dev4 endpoints to use https and wss (#117)
* Update all dev endpoints to use https and wss

* dev4
2023-11-06 09:33:22 -08:00
Bill b0db577b28 Configurable squid integrator id (#120)
* Configurable squid integrator id

* bump abacus
2023-11-03 14:18:15 -07:00
Bill 9815437a80 Account dropdown onboarding entrypoints (#113)
* Account dropdown onboarding entrypoints

* Update based on latest designs
2023-10-31 14:29:36 -07:00
aleka 890a4b2c0e Padding fixes, markets data null fix, update .env.example (#112)
* update .env.example for token migration

* bump icon size in icon button

* add padding in rewards / fees page, panel changes

* fix markets data crashing

* fix market details overflow on tablet

* fix market filters

* prevent fees table overflow

* remove notifications close button on mobile

* fix nested button console warning

* update ToggleGroup value change logic

* panel hover impact button hover

* update dropdown icon in header
2023-10-31 17:11:46 -04:00
Bill 8d3cc76d14 Configurable URLs (#107)
* Configurable URLs

* add learnmore to tooltip

* Learnmore -> LearnMore
2023-10-31 11:02:45 -07:00
Adam Fraser c7ad4b3028 Remove DSRV from Testnet node config options (#108)
* Remove DSRV from Testnet node config options

* Add Bware to testnet node config
2023-10-31 10:52:49 -07:00
Rui c88d5dd411 Add Android config to wallets.json (#114) 2023-10-31 09:35:59 -07:00
Jared Vu d6ab547055 Update README.md (#111)
* Add README in scripts directory

* Add Cloudflare instructions to README
2023-10-30 15:17:12 -07:00
aleka 2fe6c2bb3b reorder header, center align header, update collapse caret icons (#110) 2023-10-27 23:32:51 -04:00
aleka e6858ca917 show loading state instead of "Unavailable" for place order button (#104)
* show loading instead of unavailable for place order button
2023-10-27 17:39:12 -04:00
aleka d92b49f4fa bump packages, fix missing asset symbol (#106) 2023-10-27 16:47:34 -04:00
aleka e7da21962a add entry point to token migration (#98)
* update migrate panel

* address feedback
2023-10-27 16:14:34 -04:00
aleka b479531e34 update v4 docs link (#102) 2023-10-27 16:13:48 -04:00
Bill 0d4c5a547a Abort key derivation if second signature fails (#109) 2023-10-27 11:41:00 -07:00
Rui 5b235ee53f Add link to help page (#103) 2023-10-27 09:15:31 -07:00
Adam Fraser 18dee0586b Add Polkachu as RPC Node for Testnet (#105)
* Update env.json

* Add Polkachu option to environments

* Fix trailing comma error
2023-10-25 18:21:27 -04:00
mikemag-dev 9f1f5987ee Update apple-app-site-association (#100) 2023-10-24 18:33:41 -04:00
James Jia 7f531516ca v1.0: Update LICENSE and README (#101)
* Update LICENSE and README

* Update README, package.json

* Update package.json
2023-10-24 14:14:49 -07:00
aleka 3fd71558c0 fix trading view error (#96)
* fix trading view error

* only init trading view if market id is first defined, and nits re fills table with empty asset
2023-10-24 17:06:00 -04:00
aleka 575856b77b remove asset symbol for account balance logic (#95) 2023-10-23 17:51:13 -04:00
John Huang e3609782ed Indexer and validator are on https for DEV (#97) 2023-10-23 14:49:03 -07:00
237 changed files with 10616 additions and 4813 deletions
+7 -2
View File
@@ -4,6 +4,11 @@ VITE_ALCHEMY_API_KEY=
VITE_PK_ENCRYPTION_KEY= VITE_PK_ENCRYPTION_KEY=
VITE_WALLETCONNECT2_PROJECT_ID= VITE_V3_TOKEN_ADDRESS=
VITE_TOKEN_MIGRATION_URI=
VITE_V3_TOKEN_ADDRESS= AMPLITUDE_API_KEY=
BUGSNAG_API_KEY=
IOS_APP_ID=
INTERCOM_APP_ID=
STATUS_PAGE_SCRIPT_URI=
+18 -9
View File
@@ -5,29 +5,35 @@ import styled from 'styled-components';
import { store } from '@/state/_store'; import { store } from '@/state/_store';
import { GlobalStyle } from '@/styles/globalStyle';
import { SelectMenu, SelectItem } from '@/components/SelectMenu'; import { SelectMenu, SelectItem } from '@/components/SelectMenu';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { AppTheme, setAppTheme } from '@/state/configs';
import { setLocaleLoaded } from '@/state/localization'; import { setLocaleLoaded } from '@/state/localization';
import '@/index.css'; import '@/index.css';
import './ladle.css'; import './ladle.css';
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState('Default theme'); const [theme, setTheme] = useState(AppTheme.Classic);
useEffect(() => { useEffect(() => {
store.dispatch(setAppTheme(theme));
switch (theme) { switch (theme) {
case 'Dark theme': { case AppTheme.Dark: {
document?.documentElement?.classList.remove('theme-light'); document?.documentElement?.classList.remove('theme-light');
document?.documentElement?.classList.add('theme-dark'); document?.documentElement?.classList.add('theme-dark');
break; break;
} }
case 'Light theme': { case AppTheme.Light: {
document?.documentElement?.classList.remove('theme-dark'); document?.documentElement?.classList.remove('theme-dark');
document?.documentElement?.classList.add('theme-light'); document?.documentElement?.classList.add('theme-light');
break; break;
} }
default: { case AppTheme.Classic: {
document?.documentElement?.classList.remove('theme-dark', 'theme-light'); document?.documentElement?.classList.remove('theme-dark', 'theme-light');
break; break;
} }
@@ -48,15 +54,15 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
> >
{[ {[
{ {
value: 'Default theme', value: AppTheme.Classic,
label: 'Default theme', label: 'Default theme',
}, },
{ {
value: 'Dark theme', value: AppTheme.Dark,
label: 'Dark theme', label: 'Dark theme',
}, },
{ {
value: 'Light theme', value: AppTheme.Light,
label: 'Light theme', label: 'Light theme',
}, },
].map(({ value, label }) => ( ].map(({ value, label }) => (
@@ -69,8 +75,11 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
</SelectMenu> </SelectMenu>
</StoryHeader> </StoryHeader>
<hr /> <hr />
<StoryContent>{children}</StoryContent> <AppThemeProvider>
</Provider> <GlobalStyle />
<StoryContent>{children}</StoryContent>
</AppThemeProvider>
</Provider>
); );
}; };
+2 -41
View File
@@ -1,48 +1,9 @@
Subject to your compliance with applicable law, you are granted the right to use the Licensed Work (defined below) under the terms of the below licenses; provided, however, that if you violate any such applicable law in your use of the Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Licensed Work will automatically and immediately terminate.
Use of this software before the Change Date specified in the LICENSE file is governed by the Business Source License included in the LICENSE file and at https://spdx.org/licenses/BUSL-1.1.html.
On and after the Change Date specified in the LICENSE file, use of this software will be governed by the Change License (GNU Affero GPL v3) as specified in the LICENSE file.
Business Source License 1.1
Business Source License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
Copyright (C) 2023 dYdX Trading Inc. Copyright (C) 2023 dYdX Trading Inc.
Parameters Subject to your compliance with applicable law and the v4 Terms of Use, available at dydx.exchange/legal, you are granted the right to use the Program or Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Program or Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Program or Licensed Work will automatically and immediately terminate.
Licensor: dYdX Trading Inc.
Licensed Work: dydxprotocol/cosmos-sdk, dydxprotocol/cometbft, dydxprotocol/v4-chain, dydxprotocol/v4-clients, dydxprotocol/v4-web, dydxprotocol/v4-abacus, dydxprotocol/v4-localization, dydxprotocol/v4-documentation, and any dYdX or dYdX Trading Inc. github repository reflecting a copy of this license, or link to this license.
Additional Use Grant: None
Change Date: On the later to occur of (i) the release of the Licensed Work labeled “dYdX Version 1.0 Affero GPL”, and which includes a document uploaded to the relevant github repository stating “The software in this repository is Version 1.0, subject to Affero GPL license”, and (ii) September 30, 2023 (approximate projected release of “Version 1.0” of this software).
Change License: GNU Affero GPL License v3
License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source License” is a trademark of MariaDB plc.
Terms
The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use.
Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.
If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor.
You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this Licenses text to license your works, and to refer to it using the trademark “Business Source License”, as long as you comply with the Covenants of Licensor below.
Covenants of Licensor
In consideration of the right to use this Licenses text and the “Business Source License” name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor:
To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where “compatible” means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation.
To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the right granted in this License, as the Additional Use Grant; or (b) insert the text “None” to specify a Change Date. Not to modify this License in any other way.
Notice
The Business Source License (this document, or the “License”) is not an Open Source license. However, the Licensed Work will eventually be made available under an Open Source License, as stated in this License.
The “Program” or “Licensed Work” shall mean any of the following: dydxprotocol/cosmos-sdk, dydxprotocol/cometbft, dydxprotocol/v4-chain, dydxprotocol/v4-clients, dydxprotocol/v4-web, dydxprotocol/v4-abacus, dydxprotocol/v4-localization, dydxprotocol/v4-documentation, and any dYdX or dYdX Trading Inc. repository reflecting a copy of, or link to, this license.
OPEN SOURCE LICENSE BELOW - TO BE APPLIED AFTER THE CHANGE DATE.
Copyright (C) 2023 dYdX Trading Inc.
Subject to your compliance with applicable law, you are granted the right to use the Licensed Work (defined below) under the terms of the GNU Affero General Public License as set forth below; provided, however, that if you violate any such applicable law in your use of the Licensed Work, all of your rights and licenses to use (including any rights to reproduce, distribute, install or modify) the Licensed Work will automatically and immediately terminate.
The GNU Affero General Public License The GNU Affero General Public License
+43 -6
View File
@@ -1,10 +1,10 @@
<p align="center"><img src="https://dydx.exchange/icon.svg?" width="256" /></p> <p align="center"><img src="https://dydx.exchange/icon.svg?" width="256" /></p>
<h1 align="center">v4 Web</h1> <h1 align="center">dYdX Chain Web</h1>
<div align="center"> <div align="center">
<a href='https://github.com/dydxprotocol/v4-web/blob/main/LICENSE'> <a href='https://github.com/dydxprotocol/v4-web/blob/main/LICENSE'>
<img src='https://img.shields.io/badge/License-BSL_1.1-blue' alt='License' /> <img src='https://img.shields.io/badge/License-AGPL_v3-blue.svg' alt='License' />
</a> </a>
</div> </div>
@@ -26,7 +26,7 @@ Alternatively, follow the [IPFS Kubo installation guide](https://docs.ipfs.tech
## Part 1: Setting up your local environment ## Part 1: Setting up your local environment
### Step 1: Clone the repo ### 1. Clone the repo
Clone the repository and navigate to its directory: Clone the repository and navigate to its directory:
@@ -35,7 +35,7 @@ git clone https://github.com/dydxprotocol/v4-web.git
cd v4-web cd v4-web
``` ```
### Step 2: Install pnpm and dependencies ### 2. Install pnpm and dependencies
Install pnpm and the project dependencies: Install pnpm and the project dependencies:
@@ -69,7 +69,24 @@ You'll need to provide a Wallet Connect project id to enable onboarding and wall
- Create a project on https://cloud.walletconnect.com/app - Create a project on https://cloud.walletconnect.com/app
- Copy over the project ID into this [field](https://github.com/dydxprotocol/v4-web/blob/67ecbd75b43e0c264b7b4d2d9b3d969830b0621c/public/configs/env.json#L822C33-L822C46) - Copy over the project ID into this [field](https://github.com/dydxprotocol/v4-web/blob/67ecbd75b43e0c264b7b4d2d9b3d969830b0621c/public/configs/env.json#L822C33-L822C46)
## Part 4: Deploying with Vercel ## Part 4: Set Enviornment variables
Set environment variables via `.env`.
- `VITE_BASE_URL` (required): The base URL of the deployment (e.g., `https://www.example.com`).
- `VITE_ALCHEMY_API_KEY` (optional): Add an Alchemy API key for EVM interactions; the app will fall back to public RPCs if not provided.
- `VITE_PK_ENCRYPTION_KEY` (optional): AES encryption key used for signature obfuscation; necessary for enabling the "Remember Me" feature.
- `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`.
- `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`.
# Deployments
## Deploying with Vercel
### Step 1: Connect your repository to Vercel ### Step 1: Connect your repository to Vercel
@@ -81,12 +98,17 @@ For the "Build & Development Settings", we recommend the following:
- Framework Preset: `Vite` - Framework Preset: `Vite`
- Build Command (override): `pnpm run build` - Build Command (override): `pnpm run build`
By default, the dev server runs in development mode and the build command runs in production mode. To override the default mode, you can pass in the `--mode` option flag. For example, if you want to build your app for testnet:
```
pnpm run build --mode testnet
```
If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our scripts: 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. `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.
For more details, check out Vercel's [official documentation](https://vercel.com/docs). For more details, check out Vercel's [official documentation](https://vercel.com/docs).
## Part 5: Deploying to IPFS ## Deploying to IPFS
### web3.storage: deploy to IPFS via web3.storage using the provided script ### web3.storage: deploy to IPFS via web3.storage using the provided script
@@ -118,3 +140,18 @@ To access your content on IPFS:
2. **Public IPFS gateway:** Access your content via a public IPFS gateway, such as [https://dweb.link](https://dweb.link/) or [https://w3s.link/](https://w3s.link/). Use the gateway URL with your CID appended, like `https://dweb.link/ipfs/your_cid`. 2. **Public IPFS gateway:** Access your content via a public IPFS gateway, such as [https://dweb.link](https://dweb.link/) or [https://w3s.link/](https://w3s.link/). Use the gateway URL with your CID appended, like `https://dweb.link/ipfs/your_cid`.
Replace `your_cid` with the actual CID. Replace `your_cid` with the actual CID.
## Cloudflare Settings
We recommend that you add your website to Cloudflare for additional security settings.
To block OFAC Sanctioned countries:
1. Navigate Websites > Domain > Security > WAF
2. Create Rule with the following settings:
* If incoming requests match
`(ip.geoip.country eq "CU") or (ip.geoip.country eq "IR") or (ip.geoip.country eq "KP") or (ip.geoip.country eq "SY") or (ip.geoip.country eq "MM") or (ip.geoip.subdivision_1_iso_code eq "UA-09") or (ip.geoip.subdivision_1_iso_code eq "UA-14") or (ip.geoip.subdivision_1_iso_code eq "UA-43")`
* This rule will bring up a Cloudflare page when a restricted geography tries to access your site. You will have the option to display:
1. Custom Text
- (e.g. `Because you appear to be a resident of, or trading from, a jurisdiction that violates our terms of use, or have engaged in activity that violates our terms of use, you have been blocked. You may withdraw your funds from the protocol at any time.`)
2. Default Cloudflare WAF block page
+11 -7
View File
@@ -1,9 +1,9 @@
{ {
"name": "dydx-chain-web", "name": "dydx-chain-web",
"private": true, "private": true,
"version": "0.0.1", "version": "1.0.0",
"type": "module", "type": "module",
"license": "BSL-1.1", "license": "AGPL-3.0",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@@ -39,14 +39,17 @@
"@cosmjs/proto-signing": "^0.31.0", "@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0", "@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0", "@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^0.7.6", "@dydxprotocol/v4-abacus": "^1.2.5",
"@dydxprotocol/v4-client-js": "^0.40.3", "@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^0.1.32", "@dydxprotocol/v4-localization": "^1.1.11",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3", "@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-navigation-menu": "^1.1.3", "@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3",
@@ -96,6 +99,7 @@
"long": "^5.2.3", "long": "^5.2.3",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"qr-code-styling": "1.6.0-rc.1", "qr-code-styling": "1.6.0-rc.1",
"query-string": "^8.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-aria": "^3.25.0", "react-aria": "^3.25.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@@ -107,8 +111,8 @@
"reselect": "^4.1.8", "reselect": "^4.1.8",
"styled-components": "^5.3.11", "styled-components": "^5.3.11",
"use-latest": "^1.2.1", "use-latest": "^1.2.1",
"viem": "^1.12.2", "viem": "^1.20.0",
"wagmi": "^1.3.7" "wagmi": "^1.4.12"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.5", "@babel/core": "^7.22.5",
+1117 -404
View File
File diff suppressed because it is too large Load Diff
+13 -37
View File
@@ -8,67 +8,43 @@
], ],
"components": [ "components": [
{ {
"/": "/trade/*" "/": "/"
}, },
{ {
"/": "/r/*" "/": "/#/"
}, },
{ {
"/": "/portfolio/overview" "/": "*/markets"
}, },
{ {
"/": "/portfolio/positions" "/": "*/trade"
}, },
{ {
"/": "/portfolio/orders" "/": "*/trade/*"
}, },
{ {
"/": "/portfolio/fees" "/": "*/portfolio"
}, },
{ {
"/": "/portfolio/history" "/": "*/portfolio/overview"
}, },
{ {
"/": "/portfolio/history/trades" "/": "*/portfolio/positions"
}, },
{ {
"/": "/portfolio/history/transfers" "/": "*/portfolio/orders"
}, },
{ {
"/": "/portfolio/history/funding" "/": "*/portfolio/fees"
}, },
{ {
"/": "/rewards" "/": "*/portfolio/history/trades"
}, },
{ {
"/": "/my-profile" "/": "*/portfolio/history/transfers"
}, },
{ {
"/": "/rankings/hedgies" "/": "*/portfolio/history/funding"
},
{
"/": "/rankings/competition"
},
{
"/": "/rankings/diamond"
},
{
"/": "/rankings/platinum"
},
{
"/": "/rankings/gold"
},
{
"/": "/rankings/silver"
},
{
"/": "/rankings/bronze"
},
{
"/": "/rankings/pnl-absolute"
},
{
"/": "/rankings/pnl-percent"
}, },
{ {
"/": "/walletsegue", "/": "/walletsegue",
+12
View File
@@ -0,0 +1,12 @@
[
{
"chainId": "1",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Ethereum"
},
{
"chainId": "5",
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
"name": "Ethereum Goerli"
}
]
+16
View File
@@ -0,0 +1,16 @@
{
"tradingRewardsFAQs": [
{
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_WHO_IS_ELIGIBLE_QUESTION",
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_WHO_IS_ELIGIBLE_ANSWER"
},
{
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_TRADING_REWARDS_WORK_QUESTION",
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_TRADING_REWARDS_WORK_ANSWER"
},
{
"questionLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_QUESTION",
"answerLocalizationKey": "APP.TRADING_REWARDS.FAQ_HOW_DO_I_CLAIM_MY_REWARDS_ANSWER"
}
]
}
+440 -128
View File
@@ -1,4 +1,9 @@
{ {
"apps": {
"ios": {
"scheme": "dydxv4"
}
},
"deployments": { "deployments": {
"MAINNET": { "MAINNET": {
"environments": [ "environments": [
@@ -22,10 +27,11 @@
"dydxprotocol-staging-west", "dydxprotocol-staging-west",
"dydxprotocol-testnet", "dydxprotocol-testnet",
"dydxprotocol-testnet-dydx", "dydxprotocol-testnet-dydx",
"dydxprotocol-testnet-dsrv",
"dydxprotocol-testnet-nodefleet", "dydxprotocol-testnet-nodefleet",
"dydxprotocol-testnet-kingnodes", "dydxprotocol-testnet-kingnodes",
"dydxprotocol-testnet-liquify" "dydxprotocol-testnet-liquify",
"dydxprotocol-testnet-polkachu",
"dydxprotocol-testnet-bware"
], ],
"default": "dydxprotocol-testnet" "default": "dydxprotocol-testnet"
} }
@@ -33,10 +39,11 @@
"environments": { "environments": {
"dydxprotocol-dev": { "dydxprotocol-dev": {
"name": "v4 Dev", "name": "v4 Dev",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -56,21 +63,34 @@
"endpoints": { "endpoints": {
"indexers": [ "indexers": [
{ {
"api": "http://indexer.v4dev.dydx.exchange", "api": "https://indexer.v4dev.dydx.exchange",
"socket": "wss://indexer.v4dev.dydx.exchange" "socket": "wss://indexer.v4dev.dydx.exchange"
} }
], ],
"validators": [ "validators": [
"http://validator.v4dev.dydx.exchange" "https://validator.v4dev.dydx.exchange"
], ],
"0xsquid": "https://testnet.api.0xsquid.com", "0xsquid": "https://testnet.api.0xsquid.com",
"faucet": "http://faucet.v4dev.dydx.exchange" "nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4dev.dydx.exchange"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -93,10 +113,11 @@
}, },
"dydxprotocol-dev-2": { "dydxprotocol-dev-2": {
"name": "v4 Dev 2", "name": "v4 Dev 2",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -123,13 +144,26 @@
"validators": [ "validators": [
"http://54.92.118.111" "http://54.92.118.111"
], ],
"0xsquid": "https://testnet.api.0xsquid.com" "0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -152,10 +186,11 @@
}, },
"dydxprotocol-dev-4": { "dydxprotocol-dev-4": {
"name": "v4 Dev 4", "name": "v4 Dev 4",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -175,20 +210,33 @@
"endpoints": { "endpoints": {
"indexers": [ "indexers": [
{ {
"api": "http://indexer.v4dev4.dydx.exchange", "api": "https://indexer.v4dev4.dydx.exchange",
"socket": "ws://indexer.v4dev4.dydx.exchange" "socket": "wss://indexer.v4dev4.dydx.exchange"
} }
], ],
"validators": [ "validators": [
"http://validator.v4dev4.dydx.exchange" "http://validator.v4dev4.dydx.exchange"
], ],
"0xsquid": "https://testnet.api.0xsquid.com" "0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -211,10 +259,11 @@
}, },
"dydxprotocol-dev-5": { "dydxprotocol-dev-5": {
"name": "v4 Dev 5", "name": "v4 Dev 5",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -241,13 +290,26 @@
"validators": [ "validators": [
"http://18.223.78.50" "http://18.223.78.50"
], ],
"0xsquid": "https://testnet.api.0xsquid.com" "0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -270,10 +332,11 @@
}, },
"dydxprotocol-staging": { "dydxprotocol-staging": {
"name": "v4 Staging", "name": "v4 Staging",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -301,12 +364,89 @@
"validators": [ "validators": [
"https://validator.v4staging.dydx.exchange" "https://validator.v4staging.dydx.exchange"
], ],
"0xsquid": "https://testnet.api.squidrouter.com" "0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"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",
"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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-staging-forced-update": {
"name": "v4 Staging Forced Update",
"ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"tokens": {
"chain": {
"name": "Dv4TNT",
"denom": "adv4tnt",
"decimals": 18,
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"decimals": 6,
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4staging.dydx.exchange",
"socket": "wss://indexer.v4staging.dydx.exchange"
}
],
"faucet": "https://faucet.v4staging.dydx.exchange",
"validators": [
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://v4-teacher.vercel.app/", "documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx", "community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform", "feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
@@ -329,14 +469,22 @@
"images": "/wallets/", "images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding", "signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain" "signTypedDataDomainName": "dYdX Chain"
},
"apps": {
"ios": {
"minimalVersion": "1.0",
"build":40000,
"url": "https://apps.apple.com/app/dydx/id1564787350"
}
} }
}, },
"dydxprotocol-staging-west": { "dydxprotocol-staging-west": {
"name": "v4 Staging West", "name": "v4 Staging West",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydxprotocol-testnet", "dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -364,16 +512,27 @@
"validators": [ "validators": [
"https://validator-uswest1.v4staging.dydx.exchange" "https://validator-uswest1.v4staging.dydx.exchange"
], ],
"0xsquid": "https://testnet.api.squidrouter.com" "0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/", "documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx", "community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform", "feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -396,10 +555,11 @@
}, },
"dydxprotocol-testnet": { "dydxprotocol-testnet": {
"name": "v4 Public Testnet", "name": "v4 Public Testnet",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -419,26 +579,39 @@
"endpoints": { "endpoints": {
"indexers": [ "indexers": [
{ {
"api": "https://dydx-testnet.imperator.co", "api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://dydx-testnet.imperator.co" "socket": "wss://indexer.v4testnet.dydx.exchange"
} }
], ],
"validators": [ "validators": [
"https://dydx-testnet-full-rpc.public.blastapi.io/",
"https://dydx-testnet-rpc.polkachu.com/",
"https://dydx-testnet.nodefleet.org", "https://dydx-testnet.nodefleet.org",
"https://test-dydx.kingnodes.com", "https://test-dydx.kingnodes.com",
"https://dydx-rpc.liquify.com/api=8878132/dydx" "https://dydx-rpc.liquify.com/api=8878132/dydx"
], ],
"0xsquid": "https://testnet.api.squidrouter.com", "0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange" "faucet": "https://faucet.v4testnet.dydx.exchange"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/", "documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx", "community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform", "feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -461,10 +634,11 @@
}, },
"dydxprotocol-testnet-dydx": { "dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX", "name": "v4 Public Testnet/dYdX",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -492,6 +666,7 @@
"https://validator.v4testnet.dydx.exchange" "https://validator.v4testnet.dydx.exchange"
], ],
"0xsquid": "https://testnet.api.squidrouter.com", "0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange" "faucet": "https://faucet.v4testnet.dydx.exchange"
}, },
"links": { "links": {
@@ -499,74 +674,20 @@
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/", "statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/", "documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx", "community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform", "feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog" "blogs": "https://www.dydx.foundation/blog",
}, "foundation": "https://www.dydx.foundation",
"wallets": { "help": "https://help.dydx.exchange/",
"walletconnect": { "initialMarginFractionLearnMore": "https://help.dydx.exchange/articles/5232637-maximum-position-sizes",
"client": { "reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
"name": "dYdX v4", "mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"description": "dYdX v4 App", "governanceLearnMore": "https://help.dydx.exchange",
"iconUrl": "/logos/dydx-x.png" "stakingLearnMore": "https://help.dydx.exchange",
}, "keplrDashboard": "https://testnet.keplr.app/",
"v2": { "accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e" "walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-dsrv": {
"name": "v4 Public Testnet/DSRV",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "Dv4TNT",
"denom": "adv4tnt",
"decimals": 18,
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"decimals": 6,
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-testnet-archive.allthatnode.com:26657/XZvMM41hESf8PJrEQiTzbCOMVyFca79R"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"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"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -589,10 +710,11 @@
}, },
"dydxprotocol-testnet-nodefleet": { "dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet", "name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -612,14 +734,15 @@
"endpoints": { "endpoints": {
"indexers": [ "indexers": [
{ {
"api": "https://dydx-testnet.imperator.co", "api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://dydx-testnet.imperator.co" "socket": "wss://indexer.v4testnet.dydx.exchange"
} }
], ],
"validators": [ "validators": [
"https://dydx-testnet.nodefleet.org" "https://dydx-testnet.nodefleet.org"
], ],
"0xsquid": "https://testnet.api.squidrouter.com", "0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange" "faucet": "https://faucet.v4testnet.dydx.exchange"
}, },
"links": { "links": {
@@ -627,10 +750,20 @@
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/", "statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/", "documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx", "community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform", "feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -653,10 +786,11 @@
}, },
"dydxprotocol-testnet-kingnodes": { "dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes", "name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -676,14 +810,15 @@
"endpoints": { "endpoints": {
"indexers": [ "indexers": [
{ {
"api": "https://dydx-testnet.imperator.co", "api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://dydx-testnet.imperator.co" "socket": "wss://indexer.v4testnet.dydx.exchange"
} }
], ],
"validators": [ "validators": [
"https://test-dydx.kingnodes.com" "https://test-dydx.kingnodes.com"
], ],
"0xsquid": "https://testnet.api.squidrouter.com", "0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange" "faucet": "https://faucet.v4testnet.dydx.exchange"
}, },
"links": { "links": {
@@ -691,7 +826,160 @@
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/", "statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/", "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",
"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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"tokens": {
"chain": {
"name": "Dv4TNT",
"denom": "adv4tnt",
"decimals": 18,
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"decimals": 6,
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"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",
"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",
"stakingLearnMore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-polkachu": {
"name": "v4 Public Testnet/Polkahcu",
"ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"tokens": {
"chain": {
"name": "Dv4TNT",
"denom": "adv4tnt",
"decimals": 18,
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"decimals": 6,
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://dydx-testnet-rpc.polkachu.com/"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx", "community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform", "feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog" "blogs": "https://www.dydx.foundation/blog"
@@ -715,12 +1003,13 @@
"signTypedDataDomainName": "dYdX V4" "signTypedDataDomainName": "dYdX V4"
} }
}, },
"dydxprotocol-testnet-liquify": { "dydxprotocol-testnet-bware": {
"name": "v4 Public Testnet/Liquify", "name": "v4 Public Testnet/BWare",
"ethereumChainId": "5", "ethereumChainId": "11155111",
"dydxChainId": "dydx-testnet-4", "dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
@@ -740,14 +1029,15 @@
"endpoints": { "endpoints": {
"indexers": [ "indexers": [
{ {
"api": "https://dydx-testnet.imperator.co", "api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://dydx-testnet.imperator.co" "socket": "wss://indexer.v4testnet.dydx.exchange"
} }
], ],
"validators": [ "validators": [
"https://dydx-rpc.liquify.com/api=8878132/dydx" "https://dydx-testnet-full-rpc.public.blastapi.io/"
], ],
"0xsquid": "https://testnet.api.squidrouter.com", "0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4testnet.dydx.exchange" "faucet": "https://faucet.v4testnet.dydx.exchange"
}, },
"links": { "links": {
@@ -755,10 +1045,20 @@
"privacy": "https://dydx.exchange/privacy", "privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/", "statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}", "mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/", "documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx", "community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform", "feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog" "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",
"stakingLearnmore": "https://help.dydx.exchange",
"keplrDashboard": "https://testnet.keplr.app/",
"accountExportLearnmore": "https://help.dydx.exchange",
"walletLearnmore": "https://www.dydx.academy/video/defi-wallet"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
@@ -784,7 +1084,8 @@
"ethereumChainId": "1", "ethereumChainId": "1",
"dydxChainId": "[mainnet chain id]", "dydxChainId": "[mainnet chain id]",
"chainName": "dYdX Chain", "chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png", "chainLogo": "/dydx-chain.png",
"squidIntegratorId": "[mainnet squid integrator id]",
"isMainNet": true, "isMainNet": true,
"tokens": { "tokens": {
"comment": "Change according to mainnet release", "comment": "Change according to mainnet release",
@@ -813,16 +1114,27 @@
"[Validator endpoint 1", "[Validator endpoint 1",
"[Validator endpoint n]" "[Validator endpoint n]"
], ],
"0xsquid": "[0xSquid endpoint for mainnet]" "0xsquid": "[0xSquid endpoint for mainnet]",
"nobleValidator": "[noble validator endpoint for mainnet]"
}, },
"links": { "links": {
"tos": "[HTTP link to TOS]", "tos": "[HTTP link to TOS]",
"privacy": "[HTTP link to Privacy Policy]", "privacy": "[HTTP link to Privacy Policy]",
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]", "mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
"mintscanBase": "[HTTP link to TOS mintscan base url]",
"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]", "documentation": "[HTTP link to documentation, can be null]",
"community": "[HTTP link to community, can be null]", "community": "[HTTP link to community, can be null]",
"feedback": "[HTTP link to feedback form, can be null]", "help": "[HTTP link to help page, can be null]",
"blogs": "[HTTP link to blogs, can be null]" "governanceLearnMore": "[HTTP link to governance learn more, can be null]",
"stakingLearnMore": "[HTTP link to staking learn more, can be null]",
"keplrDashboard": "[HTTP link to keplr dashboard, can be null]",
"accountExportLearnMore": "[HTTP link to account export learn more, can be null]",
"walletLearnMore": "[HTTP link to wallet learn more, can be null]"
}, },
"wallets": { "wallets": {
"walletconnect": { "walletconnect": {
+359 -453
View File
@@ -1,454 +1,360 @@
{ {
"1INCH-USD":{ "1INCH-USD": {
"name":"1inch", "name": "1inch",
"tags":[ "tags": ["Defi"],
"Defi" "websiteLink": "https://1inch.io/",
], "whitepaperLink": "https://github.com/1inch",
"websiteLink":"https://1inch.io/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/1inch/"
"whitepaperLink":"https://github.com/1inch", },
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/1inch/" "AAVE-USD": {
}, "name": "Aave",
"AAVE-USD":{ "tags": ["Defi"],
"name":"Aave", "websiteLink": "https://aave.com/",
"tags":[ "whitepaperLink": "https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
"Defi" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/aave/"
], },
"websiteLink":"https://aave.com/", "ADA-USD": {
"whitepaperLink":"https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf", "name": "Cardano",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aave/" "tags": ["Layer 1"],
}, "websiteLink": "https://cardano.org/",
"ADA-USD":{ "whitepaperLink": "https://why.cardano.org/en/introduction/motivation/",
"name":"Cardano", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/cardano/"
"tags":[ },
"Layer 1" "ALGO-USD": {
], "name": "Algorand",
"websiteLink":"https://cardano.org/", "tags": ["Layer 1"],
"whitepaperLink":"https://why.cardano.org/en/introduction/motivation/", "websiteLink": "https://algorand.com/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cardano/" "whitepaperLink": "https://algorand.com/technology/white-papers",
}, "coinMarketCapsLink": "https://coinmarketcap.com/currencies/algorand/"
"ALGO-USD":{ },
"name":"Algorand", "APE-USD": {
"tags":[ "name": "ApeCoin",
"Layer 1" "tags": [],
], "websiteLink": "https://apecoin.com/",
"websiteLink":"https://algorand.com/", "whitepaperLink": "https://apecoin.com/about",
"whitepaperLink":"https://algorand.com/technology/white-papers", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/apecoin-ape/"
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/algorand/" },
}, "APT-USD": {
"APE-USD":{ "name": "Aptos",
"name":"ApeCoin", "tags": ["Layer 1"],
"tags":[ "websiteLink": "https://aptoslabs.com/",
"whitepaperLink": "https://aptos.dev/aptos-white-paper/",
], "coinMarketCapsLink": "https://coinmarketcap.com/currencies/aptos/"
"websiteLink":"https://apecoin.com/", },
"whitepaperLink":"https://apecoin.com/about", "ARB-USD": {
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/apecoin-ape/" "name": "Arbitrum",
}, "tags": [],
"APT-USD":{ "websiteLink": "https://arbitrum.io/",
"name":"Aptos", "whitepaperLink": "https://github.com/OffchainLabs/nitro",
"tags":[ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/arbitrum/"
"Layer 1" },
], "ATOM-USD": {
"websiteLink":"https://aptoslabs.com/", "name": "Cosmos",
"whitepaperLink":"https://aptos.dev/aptos-white-paper/", "tags": ["Layer 1"],
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aptos/" "websiteLink": "https://cosmos.network/",
}, "whitepaperLink": "https://v1.cosmos.network/resources/whitepaper",
"ARB-USD":{ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/cosmos/"
"name":"Arbitrum", },
"tags":[ "AVAX-USD": {
"name": "Avalanche",
], "tags": ["Layer 1"],
"websiteLink":"https://arbitrum.io/", "websiteLink": "https://www.avalabs.org/",
"whitepaperLink":"https://github.com/OffchainLabs/nitro", "whitepaperLink": "https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/arbitrum/" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/avalanche/"
}, },
"ATOM-USD":{ "BCH-USD": {
"name":"Cosmos", "name": "Bitcoin Cash",
"tags":[ "tags": ["Layer 1"],
"Layer 1" "websiteLink": "https://bitcoincash.org/",
], "whitepaperLink": "https://bitcoincash.org/",
"websiteLink":"https://cosmos.network/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin-cash/"
"whitepaperLink":"https://v1.cosmos.network/resources/whitepaper", },
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cosmos/" "BLUR-USD": {
}, "name": "Blur",
"AVAX-USD":{ "tags": [],
"name":"Avalanche", "websiteLink": "https://blur.io/",
"tags":[ "whitepaperLink": "https://docs.blur.foundation/",
"Layer 1" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/blur-token/"
], },
"websiteLink":"https://www.avalabs.org/", "CELO-USD": {
"whitepaperLink":"https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf", "name": "Celo",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/avalanche/" "tags": [],
}, "websiteLink": "https://celo.org",
"BCH-USD":{ "whitepaperLink": "https://docs.celo.org",
"name":"Bitcoin Cash", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/celo/"
"tags":[ },
"Layer 1" "BTC-USD": {
], "name": "Bitcoin",
"websiteLink":"https://bitcoincash.org/", "tags": ["Layer 1"],
"whitepaperLink":"https://bitcoincash.org/", "websiteLink": "https://bitcoin.org/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin-cash/" "whitepaperLink": "https://bitcoin.org/bitcoin.pdf",
}, "coinMarketCapsLink": "https://coinmarketcap.com/currencies/bitcoin/"
"BLUR-USD":{ },
"name":"Blur", "COMP-USD": {
"tags":[ "name": "Compound",
"tags": ["Defi"],
], "websiteLink": "https://compound.finance/",
"websiteLink":"https://blur.io/", "whitepaperLink": "https://compound.finance/documents/Compound.Whitepaper.pdf",
"whitepaperLink":"https://docs.blur.foundation/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/compound/"
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/blur-token/" },
}, "CRV-USD": {
"CELO-USD":{ "name": "Curve",
"name":"Celo", "tags": ["Governance"],
"tags":[ "websiteLink": "https://curve.fi/",
"whitepaperLink": "https://curve.fi/whitepaper",
], "coinMarketCapsLink": "https://coinmarketcap.com/currencies/curve-dao-token/"
"websiteLink":"https://celo.org", },
"whitepaperLink":"https://docs.celo.org", "DOGE-USD": {
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/celo/" "name": "Dogecoin",
}, "tags": ["Layer 1"],
"BTC-USD":{ "websiteLink": "https://dogecoin.com/",
"name":"Bitcoin", "whitepaperLink": "https://github.com/dogecoin/dogecoin",
"tags":[ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/dogecoin/"
"Layer 1" },
], "DOT-USD": {
"websiteLink":"https://bitcoin.org/", "name": "Polkadot",
"whitepaperLink":"https://bitcoin.org/bitcoin.pdf", "tags": ["Layer 1"],
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin/" "websiteLink": "https://polkadot.network/",
}, "whitepaperLink": "https://polkadot.network/PolkaDotPaper.pdf",
"COMP-USD":{ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/polkadot-new/"
"name":"Compound", },
"tags":[ "ENJ-USD": {
"Defi" "name": "Enjin",
], "tags": [],
"websiteLink":"https://compound.finance/", "websiteLink": "https://enjin.io/",
"whitepaperLink":"https://compound.finance/documents/Compound.Whitepaper.pdf", "whitepaperLink": "https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/compound/" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/enjin-coin/"
}, },
"CRV-USD":{ "EOS-USD": {
"name":"Curve", "name": "EOS",
"tags":[ "tags": ["Layer 1"],
"Governance" "websiteLink": "https://eos.io/",
], "whitepaperLink": "https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
"websiteLink":"https://curve.fi/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/eos/"
"whitepaperLink":"https://curve.fi/whitepaper", },
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/curve-dao-token/" "ETC-USD": {
}, "name": "Ethereum Classic",
"DOGE-USD":{ "tags": ["Layer 1"],
"name":"Dogecoin", "websiteLink": "https://ethereumclassic.org/",
"tags":[ "whitepaperLink": "https://ethereumclassic.org/why-classic",
"Layer 1" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum-classic/"
], },
"websiteLink":"https://dogecoin.com/", "ETH-USD": {
"whitepaperLink":"https://github.com/dogecoin/dogecoin", "name": "Ethereum",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/dogecoin/" "tags": ["Layer 1"],
}, "websiteLink": "https://ethereum.org/",
"DOT-USD":{ "whitepaperLink": "https://ethereum.org/whitepaper/",
"name":"Polkadot", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum/",
"tags":[ "displayStepSize": "0.001",
"Layer 1" "displayTickSize": "0.1"
], },
"websiteLink":"https://polkadot.network/", "FIL-USD": {
"whitepaperLink":"https://polkadot.network/PolkaDotPaper.pdf", "name": "Filecoin",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polkadot-new/" "tags": ["Layer 1"],
}, "websiteLink": "https://filecoin.io/",
"ENJ-USD":{ "whitepaperLink": "https://filecoin.io/filecoin.pdf",
"name":"Enjin", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/filecoin/"
"tags":[ },
"HNT-USD": {
], "name": "Helium",
"websiteLink":"https://enjin.io/", "tags": ["Layer 1"],
"whitepaperLink":"https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/", "websiteLink": "https://www.helium.com",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/enjin-coin/" "whitepaperLink": "http://whitepaper.helium.com",
}, "coinMarketCapsLink": "https://coinmarketcap.com/currencies/helium/"
"EOS-USD":{ },
"name":"EOS", "ICP-USD": {
"tags":[ "name": "Internet Computer",
"Layer 1" "tags": ["Layer 1"],
], "websiteLink": "https://dfinity.org",
"websiteLink":"https://eos.io/", "whitepaperLink": "https://dfinity.org/whitepaper.pdf",
"whitepaperLink":"https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/internet-computer/"
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/eos/" },
}, "LDO-USD": {
"ETC-USD":{ "name": "Lido DAO",
"name":"Ethereum Classic", "tags": ["Defi"],
"tags":[ "websiteLink": "https://lido.fi/",
"Layer 1" "whitepaperLink": "https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
], "coinMarketCapsLink": "https://coinmarketcap.com/currencies/lido-dao/"
"websiteLink":"https://ethereumclassic.org/", },
"whitepaperLink":"https://ethereumclassic.org/why-classic", "LINK-USD": {
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum-classic/" "name": "Chainlink",
}, "tags": ["Defi"],
"ETH-USD":{ "websiteLink": "https://chain.link/",
"name":"Ethereum", "whitepaperLink": "https://link.smartcontract.com/whitepaper",
"tags":[ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/chainlink/"
"Layer 1" },
], "LTC-USD": {
"websiteLink":"https://ethereum.org/", "name": "Litecoin",
"whitepaperLink":"https://ethereum.org/whitepaper/", "tags": ["Layer 1"],
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum/", "websiteLink": "https://litecoin.org/",
"displayStepSize":"0.001", "whitepaperLink": "https://litecoin.info/index.php/Main_Page",
"displayTickSize":"0.1" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/litecoin/"
}, },
"FIL-USD":{ "MATIC-USD": {
"name":"Filecoin", "name": "Polygon",
"tags":[ "tags": ["Layer 2"],
"Layer 1" "websiteLink": "https://polygon.technology/",
], "whitepaperLink": "https://polygon.technology/lightpaper-polygon.pdf",
"websiteLink":"https://filecoin.io/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/polygon/"
"whitepaperLink":"https://filecoin.io/filecoin.pdf", },
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/filecoin/" "MKR-USD": {
}, "name": "Maker",
"HNT-USD":{ "tags": ["Governance"],
"name":"Helium", "websiteLink": "https://makerdao.com",
"tags":[ "whitepaperLink": "https://makerdao.com/whitepaper",
"Layer 1" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/maker/"
], },
"websiteLink":"https://www.helium.com", "NEAR-USD": {
"whitepaperLink":"http://whitepaper.helium.com", "name": "NEAR Protocol",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/helium/" "tags": ["Layer 1"],
}, "websiteLink": "https://near.org",
"ICP-USD":{ "whitepaperLink": "https://near.org/papers/the-official-near-white-paper/",
"name":"Internet Computer", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/near-protocol/"
"tags":[ },
"Layer 1" "OP-USD": {
], "name": "Optimism",
"websiteLink":"https://dfinity.org", "tags": [],
"whitepaperLink":"https://dfinity.org/whitepaper.pdf", "websiteLink": "https://www.optimism.io/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/internet-computer/" "whitepaperLink": "https://github.com/ethereum-optimism",
}, "coinMarketCapsLink": "https://coinmarketcap.com/currencies/optimism-ethereum/"
"LDO-USD":{ },
"name":"Lido DAO", "PEPE-USD": {
"tags":[ "name": "Pepe",
"Defi" "tags": [],
], "websiteLink": "https://www.pepe.vip/",
"websiteLink":"https://lido.fi/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/pepe/"
"whitepaperLink":"https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf", },
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/lido-dao/" "RUNE-USD": {
}, "name": "THORChain",
"LINK-USD":{ "tags": ["Layer 1"],
"name":"Chainlink", "websiteLink": "https://thorchain.org",
"tags":[ "whitepaperLink": "https://whitepaper.io/document/709/thorchain-whitepaper",
"Defi" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/thorchain/"
], },
"websiteLink":"https://chain.link/", "SEI-USD": {
"whitepaperLink":"https://link.smartcontract.com/whitepaper", "name": "Sei",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/chainlink/" "tags": ["Layer 1", "Defi"],
}, "websiteLink": "https://www.sei.io/",
"LTC-USD":{ "whitepaperLink": "https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
"name":"Litecoin", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/sei/"
"tags":[ },
"Layer 1" "SHIB-USD": {
], "name": "Shiba Inu",
"websiteLink":"https://litecoin.org/", "tags": [],
"whitepaperLink":"https://litecoin.info/index.php/Main_Page", "websiteLink": "https://shibatoken.com/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/litecoin/" "whitepaperLink": "https://docs.shibatoken.com/",
}, "coinMarketCapsLink": "https://coinmarketcap.com/currencies/shiba-inu/"
"MATIC-USD":{ },
"name":"Polygon", "SNX-USD": {
"tags":[ "name": "Synthetix",
"Layer 2" "tags": ["Defi"],
], "websiteLink": "https://synthetix.io/",
"websiteLink":"https://polygon.technology/", "whitepaperLink": "https://docs.synthetix.io/litepaper",
"whitepaperLink":"https://polygon.technology/lightpaper-polygon.pdf", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/synthetix-network-token/"
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polygon/" },
}, "SOL-USD": {
"MKR-USD":{ "name": "Solana",
"name":"Maker", "tags": ["Layer 1"],
"tags":[ "websiteLink": "https://solana.com/",
"Governance" "whitepaperLink": "https://solana.com/solana-whitepaper.pdf",
], "coinMarketCapsLink": "https://coinmarketcap.com/currencies/solana/"
"websiteLink":"https://makerdao.com", },
"whitepaperLink":"https://makerdao.com/whitepaper", "SUI-USD": {
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/maker/" "name": "Sui",
}, "tags": ["Layer 1"],
"NEAR-USD":{ "websiteLink": "https://sui.io/",
"name":"NEAR Protocol", "whitepaperLink": "https://github.com/MystenLabs",
"tags":[ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/sui/"
"Layer 1" },
], "SUSHI-USD": {
"websiteLink":"https://near.org", "name": "Sushi",
"whitepaperLink":"https://near.org/papers/the-official-near-white-paper/", "tags": ["Defi"],
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/near-protocol/" "websiteLink": "https://sushi.com/",
}, "whitepaperLink": "https://docs.sushi.com/",
"OP-USD":{ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/sushiswap/"
"name":"Optimism", },
"tags":[ "TIA-USD": {
"name": "Celestia",
], "tags": ["Layer 1"],
"websiteLink":"https://www.optimism.io/", "websiteLink": "https://celestia.org/",
"whitepaperLink":"https://github.com/ethereum-optimism", "whitepaperLink": "https://arxiv.org/pdf/1905.09274.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/optimism-ethereum/" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/celestia/"
}, },
"PEPE-USD":{ "TRX-USD": {
"name":"Pepe", "name": "TRON",
"tags":[ "tags": ["Defi"],
"websiteLink": "https://tron.network/",
], "whitepaperLink": "https://tron.network/static/doc/white_paper_v_2_0.pdf",
"websiteLink":"https://www.pepe.vip/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/tron/"
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/pepe/" },
}, "UMA-USD": {
"RUNE-USD":{ "name": "UMA",
"name":"THORChain", "tags": ["Defi"],
"tags":[ "websiteLink": "https://umaproject.org/",
"Layer 1" "whitepaperLink": "https://github.com/UMAprotocol/whitepaper",
], "coinMarketCapsLink": "https://coinmarketcap.com/currencies/uma/"
"websiteLink":"https://thorchain.org", },
"whitepaperLink":"https://whitepaper.io/document/709/thorchain-whitepaper", "UNI-USD": {
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/thorchain/" "name": "Uniswap",
}, "tags": ["Defi"],
"SEI-USD":{ "websiteLink": "https://uniswap.org/",
"name":"Sei", "whitepaperLink": "https://uniswap.org/whitepaper-v3.pdf",
"tags":[ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/uniswap/"
"Layer 1", },
"Defi" "WLD-USD": {
], "name": "Worldcoin",
"websiteLink":"https://www.sei.io/", "tags": [],
"whitepaperLink":"https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf", "websiteLink": "https://worldcoin.org/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sei/" "whitepaperLink": "https://whitepaper.worldcoin.org/",
}, "coinMarketCapsLink": "https://coinmarketcap.com/currencies/worldcoin-org/"
"SHIB-USD":{ },
"name":"Shiba Inu", "XLM-USD": {
"tags":[ "name": "Stellar",
"tags": ["Layer 1"],
], "websiteLink": "https://www.stellar.org/",
"websiteLink":"https://shibatoken.com/", "whitepaperLink": "https://www.stellar.org/papers/stellar-consensus-protocol",
"whitepaperLink":"https://docs.shibatoken.com/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/stellar/"
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/shiba-inu/" },
}, "XMR-USD": {
"SNX-USD":{ "name": "Monero",
"name":"Synthetix", "tags": ["Layer 1"],
"tags":[ "websiteLink": "https://www.getmonero.org/",
"Defi" "whitepaperLink": "https://www.getmonero.org/resources/research-lab/",
], "coinMarketCapsLink": "https://coinmarketcap.com/currencies/monero/"
"websiteLink":"https://synthetix.io/", },
"whitepaperLink":"https://docs.synthetix.io/litepaper", "XRP-USD": {
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/synthetix-network-token/" "name": "Ripple",
}, "tags": ["Layer 1"],
"SOL-USD":{ "websiteLink": "https://ripple.com/currency/",
"name":"Solana", "whitepaperLink": "https://github.com/ripple",
"tags":[ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/xrp/"
"Layer 1" },
], "XTZ-USD": {
"websiteLink":"https://solana.com/", "name": "Tezos",
"whitepaperLink":"https://solana.com/solana-whitepaper.pdf", "tags": ["Layer 1"],
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/solana/" "websiteLink": "https://tezos.com",
}, "whitepaperLink": "https://tezos.com/whitepaper.pdf",
"SUI-USD":{ "coinMarketCapsLink": "https://coinmarketcap.com/currencies/tezos/"
"name":"Sui", },
"tags":[ "YFI-USD": {
"Layer 1" "name": "Yearn",
], "tags": ["Defi"],
"websiteLink":"https://sui.io/", "websiteLink": "https://yearn.finance/",
"whitepaperLink":"https://github.com/MystenLabs", "whitepaperLink": "https://docs.yearn.finance/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sui/" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/yearn-finance/"
}, },
"SUSHI-USD":{ "ZEC-USD": {
"name":"Sushi", "name": "Zcash",
"tags":[ "tags": ["Layer 1"],
"Defi" "websiteLink": "https://z.cash/",
], "whitepaperLink": "https://z.cash/technology/",
"websiteLink":"https://sushi.com/", "coinMarketCapsLink": "https://coinmarketcap.com/currencies/zcash/"
"whitepaperLink":"https://docs.sushi.com/", },
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sushiswap/" "ZRX-USD": {
}, "name": "0x",
"TRX-USD":{ "tags": ["Defi"],
"name":"TRON", "websiteLink": "https://0x.org/",
"tags":[ "whitepaperLink": "https://0x.org/pdfs/0x_white_paper.pdf",
"Defi" "coinMarketCapsLink": "https://coinmarketcap.com/currencies/0x/"
], }
"websiteLink":"https://tron.network/", }
"whitepaperLink":"https://tron.network/static/doc/white_paper_v_2_0.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tron/"
},
"UMA-USD":{
"name":"UMA",
"tags":[
"Defi"
],
"websiteLink":"https://umaproject.org/",
"whitepaperLink":"https://github.com/UMAprotocol/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uma/"
},
"UNI-USD":{
"name":"Uniswap",
"tags":[
"Defi"
],
"websiteLink":"https://uniswap.org/",
"whitepaperLink":"https://uniswap.org/whitepaper-v3.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uniswap/"
},
"WLD-USD":{
"name":"Worldcoin",
"tags":[
],
"websiteLink":"https://worldcoin.org/",
"whitepaperLink":"https://whitepaper.worldcoin.org/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/worldcoin-org/"
},
"XLM-USD":{
"name":"Stellar",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.stellar.org/",
"whitepaperLink":"https://www.stellar.org/papers/stellar-consensus-protocol",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/stellar/"
},
"XMR-USD":{
"name":"Monero",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.getmonero.org/",
"whitepaperLink":"https://www.getmonero.org/resources/research-lab/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/monero/"
},
"XRP-USD":{
"name":"Ripple",
"tags":[
"Layer 1"
],
"websiteLink":"https://ripple.com/currency/",
"whitepaperLink":"https://github.com/ripple",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/xrp/"
},
"XTZ-USD":{
"name":"Tezos",
"tags":[
"Layer 1"
],
"websiteLink":"https://tezos.com",
"whitepaperLink":"https://tezos.com/whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tezos/"
},
"YFI-USD":{
"name":"Yearn",
"tags":[
"Defi"
],
"websiteLink":"https://yearn.finance/",
"whitepaperLink":"https://docs.yearn.finance/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/yearn-finance/"
},
"ZEC-USD":{
"name":"Zcash",
"tags":[
"Layer 1"
],
"websiteLink":"https://z.cash/",
"whitepaperLink":"https://z.cash/technology/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/zcash/"
},
"ZRX-USD":{
"name":"0x",
"tags":[
"Defi"
],
"websiteLink":"https://0x.org/",
"whitepaperLink":"https://0x.org/pdfs/0x_white_paper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/0x/"
}
}
+9 -1
View File
@@ -40,6 +40,7 @@
"iosMinVersion": "0", "iosMinVersion": "0",
"imageUrl": "", "imageUrl": "",
"encoding": "=\"#%/<>?@\\^`{|}:&", "encoding": "=\"#%/<>?@\\^`{|}:&",
"androidPackage": "io.metamask",
"connections": [ "connections": [
{ {
"type": "walletConnectV2", "type": "walletConnectV2",
@@ -98,6 +99,7 @@
"iosMinVersion": "1.8.0", "iosMinVersion": "1.8.0",
"imageUrl": "", "imageUrl": "",
"encoding": "=\"#%/<>?@\\^`{|}:&", "encoding": "=\"#%/<>?@\\^`{|}:&",
"androidPackage": "org.toshi",
"connections": [ "connections": [
{ {
"type": "walletSegue", "type": "walletSegue",
@@ -155,6 +157,7 @@
"comment": "imToken", "comment": "imToken",
"iosMinVersion": "0", "iosMinVersion": "0",
"imageUrl": "", "imageUrl": "",
"androidPackage": "im.token.app",
"connections": [ "connections": [
{ {
"type": "walletConnectV2", "type": "walletConnectV2",
@@ -196,7 +199,7 @@
}, },
"mobile": { "mobile": {
"native": "trust:", "native": "trust:",
"universal": "https://link.trustwallet.com" "universal": ""
}, },
"desktop": { "desktop": {
"native": "", "native": "",
@@ -214,6 +217,7 @@
"iosMinVersion": "0", "iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&", "encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "", "imageUrl": "",
"androidPackage": "com.wallet.crypto.trustapp",
"connections": [ "connections": [
{ {
"type": "walletConnectV2", "type": "walletConnectV2",
@@ -271,6 +275,7 @@
"iosMinVersion": "0", "iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&", "encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "", "imageUrl": "",
"androidPackage": "io.zerion.android",
"connections": [ "connections": [
{ {
"type": "walletConnectV2", "type": "walletConnectV2",
@@ -327,6 +332,7 @@
"comment": "TokenPocket", "comment": "TokenPocket",
"iosMinVersion": "0", "iosMinVersion": "0",
"imageUrl": "", "imageUrl": "",
"androidPackage": "vip.mytokenpocket",
"connections": [ "connections": [
{ {
"type": "walletConnectV2", "type": "walletConnectV2",
@@ -383,6 +389,7 @@
"comment": "Rainbow", "comment": "Rainbow",
"iosMinVersion": "0", "iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&", "encoding": "=\"#%/<>?@\\^`{|}:&",
"androidPackage": "me.rainbow",
"backlinked": true, "backlinked": true,
"imageUrl": "", "imageUrl": "",
"connections": [ "connections": [
@@ -442,6 +449,7 @@
"comment": "1inch", "comment": "1inch",
"iosMinVersion": "0", "iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&", "encoding": "=\"#%/<>?@\\^`{|}:&",
"androidPackage": "io.oneinch.android",
"imageUrl": "", "imageUrl": "",
"connections": [ "connections": [
{ {
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+230
View File
@@ -0,0 +1,230 @@
<svg width="254" height="77" viewBox="0 0 254 77" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 1258">
<g id="Ellipse 9" opacity="0.4" filter="url(#filter0_f_20300_14972)">
<ellipse cx="86.5425" cy="-3.91219" rx="116.542" ry="20.0878" fill="#7774FF"/>
</g>
<g id="Ellipse 10" opacity="0.3" filter="url(#filter1_f_20300_14972)">
<circle cx="74.5734" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 12" opacity="0.3" filter="url(#filter2_f_20300_14972)">
<circle cx="82.1105" cy="7.64179" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 22" opacity="0.3" filter="url(#filter3_f_20300_14972)">
<circle cx="116.785" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 11" opacity="0.3" filter="url(#filter4_f_20300_14972)">
<circle cx="64.774" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 18" opacity="0.3" filter="url(#filter5_f_20300_14972)">
<circle cx="96.4322" cy="3.87363" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 20" opacity="0.3" filter="url(#filter6_f_20300_14972)">
<circle cx="76.0807" cy="14.9361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 21" opacity="0.3" filter="url(#filter7_f_20300_14972)">
<circle cx="104.725" cy="4.87754" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 23" opacity="0.3" filter="url(#filter8_f_20300_14972)">
<circle cx="114.021" cy="7.13925" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 24" opacity="0.3" filter="url(#filter9_f_20300_14972)">
<circle cx="109.75" cy="15.4361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 25" opacity="0.3" filter="url(#filter10_f_20300_14972)">
<circle cx="119.801" cy="12.6705" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 27" opacity="0.3" filter="url(#filter11_f_20300_14972)">
<circle cx="94.0885" cy="11.0182" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 30" opacity="0.3" filter="url(#filter12_f_20300_14972)">
<circle cx="154.475" cy="11.6666" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 29" opacity="0.3" filter="url(#filter13_f_20300_14972)">
<circle cx="146.435" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 15" opacity="0.3" filter="url(#filter14_f_20300_14972)">
<circle cx="24.0103" cy="4.44785" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 33" opacity="0.3" filter="url(#filter15_f_20300_14972)">
<circle cx="-1.05899" cy="13.6783" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 16" opacity="0.3" filter="url(#filter16_f_20300_14972)">
<circle cx="17.441" cy="16.4908" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 35" opacity="0.3" filter="url(#filter17_f_20300_14972)">
<circle cx="36.0553" cy="6.63535" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 32" opacity="0.3" filter="url(#filter18_f_20300_14972)">
<circle cx="10.8707" cy="14.2994" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 13" opacity="0.3" filter="url(#filter19_f_20300_14972)">
<circle cx="55.7274" cy="9.65615" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 19" opacity="0.3" filter="url(#filter20_f_20300_14972)">
<circle cx="87.3886" cy="20.7108" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 26" opacity="0.3" filter="url(#filter21_f_20300_14972)">
<circle cx="123.82" cy="15.1874" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 36" opacity="0.3" filter="url(#filter22_f_20300_14972)">
<circle cx="134.876" cy="14.4335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 28" opacity="0.3" filter="url(#filter23_f_20300_14972)">
<circle cx="129.6" cy="6.38662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 14" opacity="0.3" filter="url(#filter24_f_20300_14972)">
<circle cx="41.6571" cy="14.9335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 31" opacity="0.3" filter="url(#filter25_f_20300_14972)">
<circle cx="4.21767" cy="17.9491" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 34" opacity="0.3" filter="url(#filter26_f_20300_14972)">
<circle cx="12.2177" cy="6.88662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 17" opacity="0.3" filter="url(#filter27_f_20300_14972)">
<circle cx="50.2001" cy="3.6249" r="0.753803" fill="#9A9AFF"/>
</g>
</g>
<defs>
<filter id="filter0_f_20300_14972" x="-94" y="-88" width="361.085" height="168.176" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="32" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter1_f_20300_14972" x="72.5633" y="2.61993" width="4.01991" height="4.02186" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter2_f_20300_14972" x="80.6029" y="6.13418" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter3_f_20300_14972" x="115.278" y="3.12246" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter4_f_20300_14972" x="63.2664" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter5_f_20300_14972" x="94.9246" y="2.36602" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter6_f_20300_14972" x="74.5731" y="13.4285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter7_f_20300_14972" x="103.218" y="3.36993" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter8_f_20300_14972" x="112.513" y="5.63165" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter9_f_20300_14972" x="108.242" y="13.9285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter10_f_20300_14972" x="118.294" y="11.1629" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter11_f_20300_14972" x="92.5809" y="9.51055" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter12_f_20300_14972" x="153.47" y="10.6615" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter13_f_20300_14972" x="144.928" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter14_f_20300_14972" x="22.5027" y="2.94024" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter15_f_20300_14972" x="-2.56659" y="12.1707" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter16_f_20300_14972" x="15.9334" y="14.9832" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter17_f_20300_14972" x="35.0502" y="5.63028" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter18_f_20300_14972" x="9.86563" y="13.2943" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter19_f_20300_14972" x="53.9686" y="7.89727" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter20_f_20300_14972" x="85.6297" y="18.952" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter21_f_20300_14972" x="122.061" y="13.4285" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter22_f_20300_14972" x="133.117" y="12.6746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter23_f_20300_14972" x="127.842" y="4.62774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter24_f_20300_14972" x="39.8982" y="13.1746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter25_f_20300_14972" x="2.4588" y="16.1902" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter26_f_20300_14972" x="10.4588" y="5.12774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter27_f_20300_14972" x="48.4412" y="1.86602" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 300 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 817 B

After

Width:  |  Height:  |  Size: 297 B

+38
View File
@@ -0,0 +1,38 @@
## App Integrations
### Amplitude
Analytics service used to collect data to help the dYdX Product team make informed product decisions.
<b>To use with dydxprotocol/v4-web:</b>
1. Amplitude account with API key.
2. Add API key in Github > Secrets and Variables > Actions as `AMPLITUDE_API_KEY`
3. In your deploy scripts add `pnpm run build:inject-amplitude` after your pnpm build / vite build command.
### Bugsnag
Error handling service used to collect handled/unhandled errors within the app. The information collected is used to help debug and alert the engineering team of crashes/unhandled errors within the app to improve stability.
<b>To use with dydxprotocol/v4-web:</b>
1. Bugsnag account with API key.
2. Add API key in Github > Secrets and Variables > Actions as `BUGSNAG_API_KEY`
3. In your deploy scripts add `pnpm run build:inject-bugsnag` 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-amplitude && pnpm run build:inject-bugsnag`
### StatusPage
Service used to inform users of any status updates to our platform. These status updates are manually set and updated by the On Call engineers.
<b>To use with dydxprotocol/v4-web:</b>
1. StatusPage account and script URI
2. Add API key in Github > Secrets and Variables > Actions as `STATUS_PAGE_SCRIPT_URI`
3. In your deploy scripts add `pnpm run build:inject-statuspage` 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-amplitude && pnpm run build:inject-statuspage`
### Intercom
Service used for live customer support (chat/inbox) as well as home for Help Articles.
<b>To use with dydxprotocol/v4-web:</b>
1. Create Intercom account
2. In Intercom UI
Getting started > Set up Messenger > will give you your API Key on Step 2
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`
+52 -47
View File
@@ -1,4 +1,4 @@
import { Suspense } from 'react'; import { lazy, Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom'; import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent, css } from 'styled-components'; import styled, { AnyStyledComponent, css } from 'styled-components';
import { WagmiConfig } from 'wagmi'; import { WagmiConfig } from 'wagmi';
@@ -16,26 +16,16 @@ import {
} from '@/hooks'; } from '@/hooks';
import { DydxProvider } from '@/hooks/useDydxClient'; import { DydxProvider } from '@/hooks/useDydxClient';
import { AccountsProvider } from '@/hooks/useAccounts'; import { AccountsProvider } from '@/hooks/useAccounts';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea'; import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators'; import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications'; import { NotificationsProvider } from '@/hooks/useNotifications';
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications'; import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
import { RestrictionProvider } from '@/hooks/useRestrictions'; import { RestrictionProvider } from '@/hooks/useRestrictions';
import { SubaccountProvider } from '@/hooks/useSubaccount'; import { SubaccountProvider } from '@/hooks/useSubaccount';
import { SquidProvider } from '@/hooks/useSquid';
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute'; import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
import MarketsPage from '@/pages/markets/Markets';
import PortfolioPage from '@/pages/portfolio/Portfolio';
import { AlertsPage } from '@/pages/AlertsPage';
import ProfilePage from '@/pages/Profile';
import { SettingsPage } from '@/pages/settings/Settings';
import TradePage from '@/pages/trade/Trade';
import { RewardsPage } from '@/pages/rewards/RewardsPage';
import { TermsOfUsePage } from '@/pages/TermsOfUsePage';
import { PrivacyPolicyPage } from '@/pages/PrivacyPolicyPage';
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop'; import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
import { FooterDesktop } from '@/layout/Footer/FooterDesktop'; import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
import { FooterMobile } from '@/layout/Footer/FooterMobile'; import { FooterMobile } from '@/layout/Footer/FooterMobile';
@@ -46,12 +36,24 @@ import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
import { config } from '@/lib/wagmi'; import { config } from '@/lib/wagmi';
import { breakpoints } from '@/styles'; import { breakpoints } from '@/styles';
import { GlobalStyle } from '@/styles/globalStyle';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { LoadingSpace } from './components/Loading/LoadingSpinner';
import '@/styles/constants.css'; import '@/styles/constants.css';
import '@/styles/fonts.css'; import '@/styles/fonts.css';
import '@/styles/web3modal.css'; import '@/styles/web3modal.css';
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 queryClient = new QueryClient(); const queryClient = new QueryClient();
const Content = () => { const Content = () => {
@@ -66,49 +68,52 @@ const Content = () => {
const { chainTokenLabel } = useTokenConfigs(); const { chainTokenLabel } = useTokenConfigs();
return ( return (
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}> <>
{isNotTablet && <HeaderDesktop />} <GlobalStyle />
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<Styled.Main> <Styled.Main>
<Suspense fallback={null}> <Suspense fallback={<LoadingSpace id="main" />}>
<Routes> <Routes>
<Route path={AppRoute.Trade}> <Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} /> <Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} /> <Route path={AppRoute.Trade} element={<TradePage />} />
</Route> </Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} /> <Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} /> <Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && ( {isTablet && (
<> <>
<Route path={AppRoute.Alerts} element={<AlertsPage />} /> <Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} /> <Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} /> <Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</> </>
)} )}
<Route element={<GuardedMobileRoute />}> <Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} /> <Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route> </Route>
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} /> <Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} /> <Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} /> <Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes> </Routes>
</Suspense> </Suspense>
</Styled.Main> </Styled.Main>
{isTablet ? <FooterMobile /> : <FooterDesktop />} {isTablet ? <FooterMobile /> : <FooterDesktop />}
<Styled.NotificationsToastArea /> <Styled.NotificationsToastArea />
<Styled.DialogArea ref={setDialogArea}> <Styled.DialogArea ref={setDialogArea}>
<DialogManager /> <DialogManager />
</Styled.DialogArea> </Styled.DialogArea>
<GlobalCommandDialog /> <GlobalCommandDialog />
</Styled.Content> </Styled.Content>
</>
); );
}; };
@@ -128,10 +133,10 @@ const providers = [
wrapProvider(DydxProvider), wrapProvider(DydxProvider),
wrapProvider(AccountsProvider), wrapProvider(AccountsProvider),
wrapProvider(SubaccountProvider), wrapProvider(SubaccountProvider),
wrapProvider(SquidProvider),
wrapProvider(LocalNotificationsProvider), wrapProvider(LocalNotificationsProvider),
wrapProvider(NotificationsProvider), wrapProvider(NotificationsProvider),
wrapProvider(DialogAreaProvider), wrapProvider(DialogAreaProvider),
wrapProvider(AppThemeProvider),
]; ];
const App = () => { const App = () => {
+390
View File
@@ -0,0 +1,390 @@
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [{ "name": "", "type": "string" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [{ "name": "_upgradedAddress", "type": "address" }],
"name": "deprecate",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_spender", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "approve",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "deprecated",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [{ "name": "_evilUser", "type": "address" }],
"name": "addBlackList",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_from", "type": "address" },
{ "name": "_to", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "transferFrom",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "upgradedAddress",
"outputs": [{ "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "", "type": "address" }],
"name": "balances",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "maximumFee",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "_totalSupply",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "unpause",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_maker", "type": "address" }],
"name": "getBlackListStatus",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "name": "", "type": "address" },
{ "name": "", "type": "address" }
],
"name": "allowed",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "paused",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "who", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "pause",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getOwner",
"outputs": [{ "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [{ "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [{ "name": "", "type": "string" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_to", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "transfer",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "newBasisPoints", "type": "uint256" },
{ "name": "newMaxFee", "type": "uint256" }
],
"name": "setParams",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [{ "name": "amount", "type": "uint256" }],
"name": "issue",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [{ "name": "amount", "type": "uint256" }],
"name": "redeem",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "name": "_owner", "type": "address" },
{ "name": "_spender", "type": "address" }
],
"name": "allowance",
"outputs": [{ "name": "remaining", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "basisPointsRate",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "", "type": "address" }],
"name": "isBlackListed",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [{ "name": "_clearedUser", "type": "address" }],
"name": "removeBlackList",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MAX_UINT",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [{ "name": "newOwner", "type": "address" }],
"name": "transferOwnership",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [{ "name": "_blackListedUser", "type": "address" }],
"name": "destroyBlackFunds",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "name": "_initialSupply", "type": "uint256" },
{ "name": "_name", "type": "string" },
{ "name": "_symbol", "type": "string" },
{ "name": "_decimals", "type": "uint256" }
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }],
"name": "Issue",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "name": "amount", "type": "uint256" }],
"name": "Redeem",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "name": "newAddress", "type": "address" }],
"name": "Deprecate",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "name": "feeBasisPoints", "type": "uint256" },
{ "indexed": false, "name": "maxFee", "type": "uint256" }
],
"name": "Params",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "name": "_blackListedUser", "type": "address" },
{ "indexed": false, "name": "_balance", "type": "uint256" }
],
"name": "DestroyedBlackFunds",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "name": "_user", "type": "address" }],
"name": "AddedBlackList",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "name": "_user", "type": "address" }],
"name": "RemovedBlackList",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "owner", "type": "address" },
{ "indexed": true, "name": "spender", "type": "address" },
{ "indexed": false, "name": "value", "type": "uint256" }
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "from", "type": "address" },
{ "indexed": true, "name": "to", "type": "address" },
{ "indexed": false, "name": "value", "type": "uint256" }
],
"name": "Transfer",
"type": "event"
},
{ "anonymous": false, "inputs": [], "name": "Pause", "type": "event" },
{ "anonymous": false, "inputs": [], "name": "Unpause", "type": "event" }
]
+26
View File
@@ -0,0 +1,26 @@
import type { Story } from '@ladle/react';
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
import { StoryWrapper } from '.ladle/components';
export const Accordion: Story<AccordionProps> = (args) => {
return (
<StoryWrapper>
<AccordionComponent {...args} />
</StoryWrapper>
);
};
Accordion.args = {
items: [
{
header: 'Question 1?',
content: 'Answer 1.',
},
{
header: 'Question 2?',
content: 'Answer 2.',
},
],
};
+129
View File
@@ -0,0 +1,129 @@
import styled, { keyframes, type AnyStyledComponent } from 'styled-components';
import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
import { PlusIcon } from '@/icons';
export type AccordionItem = {
header: React.ReactNode;
content: React.ReactNode;
};
export type AccordionProps = {
items: AccordionItem[];
className?: string;
};
export const Accordion = ({ items, className }: AccordionProps) => (
<Styled.Root className={className} type="single" collapsible>
{items.map(({ header, content }, idx) => (
<Styled.Item key={idx} value={idx.toString()}>
<Header>
<Styled.Trigger>
{header}
<Styled.Icon>
<PlusIcon />
</Styled.Icon>
</Styled.Trigger>
</Header>
<Styled.Content>{content}</Styled.Content>
</Styled.Item>
))}
</Styled.Root>
);
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Root = styled(Root)`
--accordion-paddingY: 1rem;
--accordion-paddingX: 1rem;
@media ${breakpoints.notTablet} {
--accordion-paddingX: 1.5rem;
}
> *:not(:last-child) {
border-bottom: var(--border-width) solid var(--border-color);
}
`;
Styled.Item = styled(Item)``;
Styled.Icon = styled.div`
display: inline-flex;
justify-content: center;
align-items: center;
width: 2.25rem;
height: 2.25rem;
--color-border: var(--color-layer-6);
color: var(--color-text-1);
background-color: var(--color-layer-5);
border: solid var(--border-width) var(--color-border);
border-radius: 50%;
font: var(--font-small-book);
svg {
height: 1.125em;
width: 1.125em;
}
`;
Styled.Trigger = styled(Trigger)`
${layoutMixins.spacedRow}
width: 100%;
padding: var(--accordion-paddingY) var(--accordion-paddingX);
gap: 0.5rem;
color: var(--color-text-1);
text-align: start;
&:hover {
${Styled.Icon} {
color: var(--color-text-2);
filter: brightness(1.1);
}
}
svg {
color: var(--color-text-0);
transition: transform 0.3s var(--ease-out-expo);
}
&[data-state='open'] svg {
transform: rotate(45deg);
}
`;
Styled.Content = styled(Content)`
overflow: hidden;
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
color: var(--color-text-0);
&[data-state='open'] {
animation: ${keyframes`
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
`} 0.3s var(--ease-out-expo);
}
&[data-state='closed'] {
animation: ${keyframes`
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
`} 0.1s var(--ease-in-expo);
}
`;
+8 -2
View File
@@ -45,6 +45,7 @@ const assetIcons = {
SOL: '/currencies/sol.png', SOL: '/currencies/sol.png',
SUI: '/currencies/sui.png', SUI: '/currencies/sui.png',
SUSHI: '/currencies/sushi.png', SUSHI: '/currencies/sushi.png',
TIA: '/currencies/tia.png',
TRX: '/currencies/trx.png', TRX: '/currencies/trx.png',
UMA: '/currencies/uma.png', UMA: '/currencies/uma.png',
UNI: '/currencies/uni.png', UNI: '/currencies/uni.png',
@@ -71,8 +72,13 @@ export const AssetIcon = ({
}: { }: {
symbol?: Nullable<string>; symbol?: Nullable<string>;
className?: string; className?: string;
}) => }) => (
isAssetSymbol(symbol) ? <Styled.Img src={assetIcons[symbol]} className={className} /> : null; <Styled.Img
src={isAssetSymbol(symbol) ? assetIcons[symbol] : '/currencies/unavailable.png'}
className={className}
alt={symbol}
/>
);
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
+1 -1
View File
@@ -61,7 +61,7 @@ export const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonPr
return ( return (
<StyledBaseButton <StyledBaseButton
disabled={state[ButtonState.Disabled]} disabled={state[ButtonState.Disabled] || state[ButtonState.Loading]}
{...{ ref, action, size, shape, state, ...otherProps }} {...{ ref, action, size, shape, state, ...otherProps }}
> >
{ {
+3 -1
View File
@@ -13,7 +13,9 @@ export const Checkboxes: Story<CheckboxProps> = (args) => {
<Checkbox <Checkbox
{...args} {...args}
checked={isChecked} checked={isChecked}
onClick={(e: React.ChangeEvent) => setIsChecked((e.target as HTMLInputElement).checked)} onCheckedChange={setIsChecked}
id="story-checkbox"
label="example label"
/> />
</StoryWrapper> </StoryWrapper>
); );
+45 -72
View File
@@ -1,10 +1,14 @@
import styled, { type AnyStyledComponent } from 'styled-components'; import styled, { type AnyStyledComponent } from 'styled-components';
import { Root, Indicator } from '@radix-ui/react-checkbox';
import { CheckIcon } from '@radix-ui/react-icons';
import { breakpoints } from '@/styles'; import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = { type ElementProps = {
checked: boolean; checked: boolean;
onClick: (e: React.ChangeEvent) => void; onCheckedChange: (checked: boolean) => void;
id?: string;
label?: React.ReactNode;
}; };
type StyleProps = { type StyleProps = {
@@ -13,85 +17,54 @@ type StyleProps = {
export type CheckboxProps = ElementProps & StyleProps; export type CheckboxProps = ElementProps & StyleProps;
export const Checkbox: React.FC<CheckboxProps> = ({ checked, className, onClick }) => { export const Checkbox: React.FC<CheckboxProps> = ({
return ( checked,
<Styled.CheckboxWrapper className={className}> className,
<Styled.Checkbox onCheckedChange,
type="checkbox" id,
checked={checked} label,
onChange={onClick} }) => (
/> <Styled.Container>
<Styled.CustomCheckbox /> <Styled.Root className={className} checked={checked} onCheckedChange={onCheckedChange} id={id}>
</Styled.CheckboxWrapper> <Styled.Indicator>
); <CheckIcon />
}; </Styled.Indicator>
</Styled.Root>
{label && <label htmlFor={id}>{label}</label>}
</Styled.Container>
);
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.CustomCheckbox = styled.span` Styled.Container = styled.div`
width: 1.25em; ${layoutMixins.row}
height: 1.25em; gap: 1ch;
font: var(--font-small-book);
position: absolute; label {
left: 0; cursor: pointer;
right: 0; }
top: 0; `;
bottom: 0;
Styled.Root = styled(Root)`
--checkbox-backgroundColor: var(--color-layer-0);
min-width: 1.25rem;
height: 1.25rem;
border-radius: 0.375rem;
border: var(--border-width) solid var(--color-border);
background-color: var(--checkbox-backgroundColor); background-color: var(--checkbox-backgroundColor);
&::after { &[data-state='checked'] {
position: absolute; --checkbox-backgroundColor: var(--color-accent);
content: '';
top: 0.25em;
left: 0.4375em;
width: 0.3125em;
height: 0.5em;
border: solid var(--checkbox-checkColor);
border-width: 0 0.125em 0.125em 0;
border-radius: 0.0625em;
opacity: 0;
transform: rotate(0deg) scale(0);
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
}
@media ${breakpoints.tablet} {
width: 1.5em;
height: 1.5em;
&::after {
top: 0.25em;
left: 0.4375em;
width: 0.4375em;
height: 0.625em;
}
} }
`; `;
Styled.CheckboxWrapper = styled.div` Styled.Indicator = styled(Indicator)`
--checkbox-backgroundColor: var(--color-layer-1);
--checkbox-checkColor: var(--color-text-1);
display: flex; display: flex;
border-radius: 0.25em; align-items: center;
overflow: hidden; justify-content: center;
position: relative;
cursor: pointer;
> input:checked ~ ${Styled.CustomCheckbox}::after { color: var(--color-text-2);
opacity: 1;
transform: rotate(40deg) scale(1);
}
`;
Styled.Checkbox = styled.input`
width: 1.25em;
height: 1.25em;
z-index: 1;
cursor: pointer;
opacity: 0;
@media ${breakpoints.tablet} {
width: 1.5em;
height: 1.5em;
}
`; `;
+5 -6
View File
@@ -1,16 +1,13 @@
import type { Story } from '@ladle/react'; import type { Story } from '@ladle/react';
import { Collapsible } from '@/components/Collapsible'; import { Collapsible, type CollapsibleProps } from '@/components/Collapsible';
import { StoryWrapper } from '.ladle/components'; import { StoryWrapper } from '.ladle/components';
import { IconName } from './Icon'; import { IconName } from './Icon';
export const CollapsibleStory: Story<Parameters<typeof Collapsible>> = (args) => ( export const CollapsibleStory: Story<CollapsibleProps> = (args) => (
<StoryWrapper> <StoryWrapper>
<Collapsible <Collapsible {...args}>
label="Collapsible List of Items"
{...args}
>
<ul> <ul>
<li>Collapsible Item 1</li> <li>Collapsible Item 1</li>
<li>Collapsible Item 2</li> <li>Collapsible Item 2</li>
@@ -23,6 +20,8 @@ export const CollapsibleStory: Story<Parameters<typeof Collapsible>> = (args) =>
CollapsibleStory.args = { CollapsibleStory.args = {
disabled: false, disabled: false,
withTrigger: true,
label: 'Collapsible List of Items',
}; };
CollapsibleStory.argTypes = { CollapsibleStory.argTypes = {
+25 -19
View File
@@ -14,6 +14,7 @@ type ElementProps = {
label: React.ReactNode; label: React.ReactNode;
triggerIcon?: IconName; triggerIcon?: IconName;
children: React.ReactNode; children: React.ReactNode;
withTrigger?: boolean;
}; };
type StyleProps = { type StyleProps = {
@@ -23,6 +24,8 @@ type StyleProps = {
fullWidth?: boolean; fullWidth?: boolean;
}; };
export type CollapsibleProps = ElementProps & StyleProps;
export const Collapsible = ({ export const Collapsible = ({
defaultOpen = false, defaultOpen = false,
disabled, disabled,
@@ -35,26 +38,29 @@ export const Collapsible = ({
triggerIconSide = 'left', triggerIconSide = 'left',
fullWidth, fullWidth,
className, className,
}: ElementProps & StyleProps) => ( withTrigger = true,
}: CollapsibleProps) => (
<Styled.Root defaultOpen={defaultOpen} open={open} onOpenChange={onOpenChange}> <Styled.Root defaultOpen={defaultOpen} open={open} onOpenChange={onOpenChange}>
<Styled.Trigger className={className} disabled={disabled}> {withTrigger && (
{triggerIconSide === 'right' && ( <Styled.Trigger className={className} disabled={disabled}>
<> {triggerIconSide === 'right' && (
{label} <>
{fullWidth && <HorizontalSeparatorFiller />} {label}
</> {fullWidth && <HorizontalSeparatorFiller />}
)} </>
)}
<Styled.TriggerIcon> <Styled.TriggerIcon>
<Icon iconName={triggerIcon} /> <Icon iconName={triggerIcon} />
</Styled.TriggerIcon> </Styled.TriggerIcon>
{triggerIconSide === 'left' && ( {triggerIconSide === 'left' && (
<> <>
{fullWidth && <HorizontalSeparatorFiller />} {fullWidth && <HorizontalSeparatorFiller />}
{label} {label}
</> </>
)} )}
</Styled.Trigger> </Styled.Trigger>
)}
<Styled.Content $transitionDuration={transitionDuration}>{children}</Styled.Content> <Styled.Content $transitionDuration={transitionDuration}>{children}</Styled.Content>
</Styled.Root> </Styled.Root>
); );
@@ -80,7 +86,7 @@ Styled.TriggerIcon = styled.span`
width: var(--trigger-icon-width); width: var(--trigger-icon-width);
display: inline-flex; display: inline-flex;
transition: transform 0.3s var(--ease-out-expo); transition: rotate 0.3s var(--ease-out-expo);
color: var(--trigger-icon-color); color: var(--trigger-icon-color);
${Styled.Trigger}[data-state='open'] & { ${Styled.Trigger}[data-state='open'] & {
+8 -5
View File
@@ -31,10 +31,12 @@ type ElementProps<TabItemsValue> = {
}; };
type StyleProps = { type StyleProps = {
fullWidthTabs?: boolean;
className?: string; className?: string;
fullWidthTabs?: boolean;
}; };
export type CollapsibleTabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
export const CollapsibleTabs = <TabItemsValue extends string>({ export const CollapsibleTabs = <TabItemsValue extends string>({
defaultValue, defaultValue,
items, items,
@@ -43,8 +45,9 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
onOpenChange, onOpenChange,
fullWidthTabs, fullWidthTabs,
className, className,
}: ElementProps<TabItemsValue> & StyleProps) => { }: CollapsibleTabsProps<TabItemsValue>) => {
const [value, setValue] = useState(defaultValue); const [value, setValue] = useState(defaultValue);
const currentItem = items.find((item) => item.value === value); const currentItem = items.find((item) => item.value === value);
@@ -86,8 +89,8 @@ export const CollapsibleTabs = <TabItemsValue extends string>({
</Styled.Header> </Styled.Header>
<Styled.CollapsibleContent> <Styled.CollapsibleContent>
{items.map(({ value, content }) => ( {items.map(({ asChild, value, content }) => (
<Styled.TabsContent key={value} value={value}> <Styled.TabsContent key={value} asChild={asChild} value={value}>
{content} {content}
</Styled.TabsContent> </Styled.TabsContent>
))} ))}
@@ -219,12 +222,12 @@ Styled.Header = styled.header`
Styled.CollapsibleContent = styled(CollapsibleContent)` Styled.CollapsibleContent = styled(CollapsibleContent)`
${layoutMixins.stack} ${layoutMixins.stack}
${layoutMixins.perspectiveArea}
box-shadow: none; box-shadow: none;
`; `;
Styled.IconButton = styled(IconButton)` Styled.IconButton = styled(IconButton)`
--button-icon-size: 1em;
${Styled.CollapsibleRoot}[data-state='closed'] & { ${Styled.CollapsibleRoot}[data-state='closed'] & {
rotate: -0.5turn; rotate: -0.5turn;
} }
+16 -2
View File
@@ -22,7 +22,12 @@ type PickComboxMenuProps<
MenuGroupValue extends string | number MenuGroupValue extends string | number
> = Pick< > = Pick<
ComboboxMenuProps<MenuItemValue, MenuGroupValue>, ComboboxMenuProps<MenuItemValue, MenuGroupValue>,
'inputPlaceholder' | 'onItemSelected' | 'slotEmpty' | 'withSearch' | 'withStickyLayout' | 'inputPlaceholder'
| 'onItemSelected'
| 'slotEmpty'
| 'withItemBorders'
| 'withSearch'
| 'withStickyLayout'
>; >;
type PickDialogProps = Pick< type PickDialogProps = Pick<
@@ -34,6 +39,7 @@ type PickDialogProps = Pick<
| 'slotHeaderInner' | 'slotHeaderInner'
| 'slotTrigger' | 'slotTrigger'
| 'slotFooter' | 'slotFooter'
| 'preventClose'
>; >;
export const ComboboxDialogMenu = < export const ComboboxDialogMenu = <
@@ -52,11 +58,13 @@ export const ComboboxDialogMenu = <
onItemSelected, onItemSelected,
inputPlaceholder, inputPlaceholder,
slotEmpty, slotEmpty,
withItemBorders,
withSearch, withSearch,
withStickyLayout = true, withStickyLayout = true,
children, children,
placement = DialogPlacement.Default, placement = DialogPlacement.Default,
preventClose,
className, className,
}: ElementProps<MenuItemValue, MenuGroupValue> & }: ElementProps<MenuItemValue, MenuGroupValue> &
PickComboxMenuProps<MenuItemValue, MenuGroupValue> & PickComboxMenuProps<MenuItemValue, MenuGroupValue> &
@@ -72,6 +80,7 @@ export const ComboboxDialogMenu = <
slotTrigger={slotTrigger} slotTrigger={slotTrigger}
slotFooter={slotFooter} slotFooter={slotFooter}
placement={placement} placement={placement}
preventClose={preventClose}
className={className} className={className}
> >
<Styled.ComboboxMenu <Styled.ComboboxMenu
@@ -80,6 +89,7 @@ export const ComboboxDialogMenu = <
title={title} title={title}
inputPlaceholder={inputPlaceholder} inputPlaceholder={inputPlaceholder}
slotEmpty={slotEmpty} slotEmpty={slotEmpty}
withItemBorders={withItemBorders}
withSearch={withSearch} withSearch={withSearch}
withStickyLayout={withStickyLayout} withStickyLayout={withStickyLayout}
/> />
@@ -92,6 +102,8 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.Dialog = styled(Dialog)` Styled.Dialog = styled(Dialog)`
/* Params */ /* Params */
--comboboxDialogMenu-backgroundColor: var(--color-layer-2); --comboboxDialogMenu-backgroundColor: var(--color-layer-2);
--comboboxDialogMenu-item-gap: 0.5rem;
--comboboxDialogMenu-item-padding: 0.5rem 1rem;
/* Overrides */ /* Overrides */
& { & {
@@ -110,10 +122,12 @@ Styled.Dialog = styled(Dialog)`
/* Net 0 sticky top inset (let stickyArea1 header stick to top) */ /* Net 0 sticky top inset (let stickyArea1 header stick to top) */
--stickyArea0-topGap: calc(-1 * var(--stickyArea0-topHeight)); --stickyArea0-topGap: calc(-1 * var(--stickyArea0-topHeight));
overflow-x: clip;
} }
`; `;
Styled.ComboboxMenu = styled(ComboboxMenu)` Styled.ComboboxMenu = styled(ComboboxMenu)`
--comboboxMenu-backgroundColor: var(--comboboxDialogMenu-backgroundColor); --comboboxMenu-backgroundColor: var(--comboboxDialogMenu-backgroundColor);
--comboboxMenu-item-gap: var(--comboxDialogMenu-item-gap, 0.5rem); --comboboxMenu-item-gap: var(--comboboxDialogMenu-item-gap);
--comboboxMenu-item-padding: var(--comboboxDialogMenu-item-padding);
`; `;
+56 -26
View File
@@ -2,7 +2,7 @@ import { Fragment, type ReactNode, useState } from 'react';
import styled, { type AnyStyledComponent, css } from 'styled-components'; import styled, { type AnyStyledComponent, css } from 'styled-components';
import { Command } from 'cmdk'; import { Command } from 'cmdk';
import { type MenuConfig } from '@/constants/menus'; import { MenuItem, type MenuConfig } from '@/constants/menus';
import { popoverMixins } from '@/styles/popoverMixins'; import { popoverMixins } from '@/styles/popoverMixins';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -20,6 +20,7 @@ type ElementProps<MenuItemValue extends string | number, MenuGroupValue extends
type StyleProps = { type StyleProps = {
className?: string; className?: string;
withItemBorders?: boolean;
withStickyLayout?: boolean; withStickyLayout?: boolean;
}; };
@@ -38,17 +39,11 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
withSearch = true, withSearch = true,
className, className,
withItemBorders,
withStickyLayout, withStickyLayout,
}: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => { }: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => {
const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>(); const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>();
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
// const inputRef = useRef<HTMLInputElement | null>(null);
// console.log({ commandValue: highlightedCommand });
// useEffect(() => {
// inputRef?.current?.focus();
// }, []);
return ( return (
<Styled.Command <Styled.Command
@@ -65,11 +60,14 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
{withSearch && ( {withSearch && (
<Styled.Header $withStickyLayout={withStickyLayout}> <Styled.Header $withStickyLayout={withStickyLayout}>
<Styled.Input <Styled.Input
// ref={inputRef} /**
* Mobile Issue: Search Input will always trigger mobile keyboard drawer. There is no fix.
* https://github.com/pacocoursey/cmdk/issues/127
*/
autoFocus
type="search" type="search"
value={searchValue} value={searchValue}
onValueChange={setSearchValue} onValueChange={setSearchValue}
autoFocus
placeholder={inputPlaceholder} placeholder={inputPlaceholder}
/> />
</Styled.Header> </Styled.Header>
@@ -80,6 +78,7 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
<Styled.Group <Styled.Group
key={group.group} key={group.group}
heading={group.groupLabel} heading={group.groupLabel}
$withItemBorders={withItemBorders}
$withStickyLayout={withStickyLayout} $withStickyLayout={withStickyLayout}
> >
{group.items.map((item) => ( {group.items.map((item) => (
@@ -99,22 +98,31 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
} }
}} }}
disabled={item.disabled} disabled={item.disabled}
$withItemBorders={withItemBorders}
> >
{item.slotBefore} {
<Styled.ItemLabel> <>
<span> {item.slotBefore}
{`${item.label}${item.subitems?.length ? '…' : ''}`} {item.slotCustomContent ?? (
{item.tag && ( <Styled.ItemLabel>
<> <span>
{' '} {typeof item.label === 'string'
<Tag>{item.tag}</Tag> ? `${item.label}${item.subitems?.length ? '…' : ''}`
</> : item.label}
{item.tag && (
<>
{' '}
<Tag>{item.tag}</Tag>
</>
)}
</span>
{item.description && <span>{item.description}</span>}
</Styled.ItemLabel>
)} )}
</span> {item.slotAfter}
{item.description && <span>{item.description}</span>} {item.subitems && '→'}
</Styled.ItemLabel> </>
{item.slotAfter} }
{item.subitems && '→'}
</Styled.Item> </Styled.Item>
{searchValue && {searchValue &&
@@ -137,6 +145,7 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
onItemSelected?.(); onItemSelected?.();
}} }}
disabled={subitem.disabled} disabled={subitem.disabled}
$withItemBorders={withItemBorders}
> >
{subitem.slotBefore} {subitem.slotBefore}
<Styled.ItemLabel> <Styled.ItemLabel>
@@ -169,14 +178,17 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>` Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
--comboboxMenu-backgroundColor: var(--color-layer-2); --comboboxMenu-backgroundColor: var(--color-layer-2);
--comboboxMenu-input-backgroundColor: var(--color-layer-3); --comboboxMenu-input-backgroundColor: var(--color-layer-3);
--comboboxMenu-input-height: 2.5rem; --comboboxMenu-input-height: 2.5rem;
--comboboxMenu-item-checked-backgroundColor: ; --comboboxMenu-item-checked-backgroundColor: ;
--comboboxMenu-item-checked-textColor: ; --comboboxMenu-item-checked-textColor: ;
--comboboxMenu-item-highlighted-backgroundColor: var(--color-layer-3); --comboboxMenu-item-highlighted-backgroundColor: var(--color-layer-3);
--comboboxMenu-item-highlighted-textColor: var(--color-text-1); --comboboxMenu-item-highlighted-textColor: var(--color-text-1);
--comboboxMenu-item-backgroundColor: ; --comboboxMenu-item-backgroundColor: ;
--comboboxMenu-item-gap: 0.5rem; --comboboxMenu-item-gap: 0.5rem;
--comboboxMenu-item-padding: 0.5em 1em;
display: grid; display: grid;
align-content: start; align-content: start;
@@ -225,7 +237,7 @@ Styled.Input = styled(Command.Input)`
gap: 0.5rem; gap: 0.5rem;
`; `;
Styled.Group = styled(Command.Group)<{ $withStickyLayout?: boolean }>` Styled.Group = styled(Command.Group)<{ $withItemBorders?: boolean; $withStickyLayout?: boolean }>`
color: var(--color-text-0); color: var(--color-text-0);
> [cmdk-group-heading] { > [cmdk-group-heading] {
@@ -242,12 +254,21 @@ Styled.Group = styled(Command.Group)<{ $withStickyLayout?: boolean }>`
> [cmdk-group-heading] { > [cmdk-group-heading] {
${layoutMixins.stickyHeader} ${layoutMixins.stickyHeader}
z-index: 2;
} }
> [cmdk-group-items] { > [cmdk-group-items] {
${layoutMixins.stickyArea3} ${layoutMixins.stickyArea3}
} }
`} `}
${({ $withItemBorders }) =>
$withItemBorders &&
css`
> [cmdk-group-items] {
padding: var(--border-width) 0;
}
`}
`; `;
Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>` Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
@@ -274,13 +295,14 @@ Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
`} `}
`; `;
Styled.Item = styled(Command.Item)` Styled.Item = styled(Command.Item)<{ $withItemBorders?: boolean }>`
${layoutMixins.scrollSnapItem} ${layoutMixins.scrollSnapItem}
${popoverMixins.item} ${popoverMixins.item}
--item-checked-backgroundColor: var(--comboboxMenu-item-checked-backgroundColor); --item-checked-backgroundColor: var(--comboboxMenu-item-checked-backgroundColor);
--item-checked-textColor: var(--comboboxMenu-item-checked-textColor); --item-checked-textColor: var(--comboboxMenu-item-checked-textColor);
--item-highlighted-textColor: var(--comboboxMenu-item-highlighted-textColor); --item-highlighted-textColor: var(--comboboxMenu-item-highlighted-textColor);
--item-gap: var(--comboboxMenu-item-gap); --item-gap: var(--comboboxMenu-item-gap);
--item-padding: var(--comboboxMenu-item-padding);
background-color: var(--comboboxMenu-backgroundColor, inherit); background-color: var(--comboboxMenu-backgroundColor, inherit);
@@ -291,6 +313,12 @@ Styled.Item = styled(Command.Item)`
opacity: 0.75; opacity: 0.75;
cursor: not-allowed; cursor: not-allowed;
} }
${({ $withItemBorders }) =>
$withItemBorders &&
css`
${layoutMixins.withOuterBorder}
`}
`; `;
Styled.ItemLabel = styled.div` Styled.ItemLabel = styled.div`
@@ -309,6 +337,8 @@ Styled.ItemLabel = styled.div`
opacity: 0.8; opacity: 0.8;
} }
} }
min-width: 0;
`; `;
Styled.Empty = styled(Command.Empty)` Styled.Empty = styled(Command.Empty)`
+3 -3
View File
@@ -15,10 +15,10 @@ CopyButtonStory.args = {
}; };
CopyButtonStory.argTypes = { CopyButtonStory.argTypes = {
shownAsText: { buttonType: {
options: [true, false], options: ["text", "icon", "default"],
control: { type: 'select' }, control: { type: 'select' },
defaultValue: false, defaultValue: "default",
}, },
children: { children: {
options: ['some text to copy'], options: ['some text to copy'],
+41 -4
View File
@@ -9,14 +9,21 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { Button, ButtonProps } from './Button'; import { Button, ButtonProps } from './Button';
import { Icon, IconName } from './Icon'; import { Icon, IconName } from './Icon';
import { IconButton } from './IconButton';
import { WithTooltip } from './WithTooltip';
export type CopyButtonProps = { export type CopyButtonProps = {
value?: string; value?: string;
shownAsText?: boolean; buttonType?: 'text' | 'icon' | 'default';
children?: React.ReactNode; children?: React.ReactNode;
} & ButtonProps; } & ButtonProps;
export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => { export const CopyButton = ({
value,
buttonType = 'default',
children,
...buttonProps
}: CopyButtonProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
@@ -28,11 +35,23 @@ export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: Cop
setTimeout(() => setCopied(false), 500); setTimeout(() => setCopied(false), 500);
}; };
return shownAsText ? ( return buttonType === 'text' ? (
<Styled.InlineRow onClick={onCopy} copied={copied}> <Styled.InlineRow onClick={onCopy} copied={copied}>
{children} {children}
<Icon iconName={IconName.Copy} /> <Styled.Icon copied={copied} iconName={copied ? IconName.Check : IconName.Copy} />
</Styled.InlineRow> </Styled.InlineRow>
) : buttonType === 'icon' ? (
<WithTooltip
tooltipString={stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })}
>
<Styled.IconButton
{...buttonProps}
copied={copied}
action={ButtonAction.Base}
iconName={copied ? IconName.Check : IconName.Copy}
onClick={onCopy}
/>
</WithTooltip>
) : ( ) : (
<Button <Button
{...buttonProps} {...buttonProps}
@@ -63,3 +82,21 @@ Styled.InlineRow = styled.div<{ copied: boolean }>`
} }
`} `}
`; `;
Styled.Icon = styled(Icon)<{ copied: boolean }>`
${({ copied }) =>
copied &&
css`
color: var(--color-positive);
`}
`;
Styled.IconButton = styled(IconButton)<{ copied: boolean }>`
${({ copied }) =>
copied &&
css`
svg {
color: var(--color-positive);
}
`}
`;
+38 -28
View File
@@ -38,6 +38,7 @@ type ElementProps = {
slotTrigger?: React.ReactNode; slotTrigger?: React.ReactNode;
slotHeaderInner?: React.ReactNode; slotHeaderInner?: React.ReactNode;
slotFooter?: React.ReactNode; slotFooter?: React.ReactNode;
withClose?: boolean;
}; };
type StyleProps = { type StyleProps = {
@@ -46,6 +47,7 @@ type StyleProps = {
hasHeaderBorder?: boolean; hasHeaderBorder?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
className?: string; className?: string;
withAnimation?: boolean;
}; };
export type DialogProps = ElementProps & StyleProps; export type DialogProps = ElementProps & StyleProps;
@@ -79,9 +81,11 @@ export const Dialog = ({
slotTrigger, slotTrigger,
slotHeaderInner, slotHeaderInner,
slotFooter, slotFooter,
withClose = true,
placement = DialogPlacement.Default, placement = DialogPlacement.Default,
portalContainer, portalContainer,
hasHeaderBorder = false, hasHeaderBorder = false,
withAnimation = false,
children, children,
className, className,
}: DialogProps) => { }: DialogProps) => {
@@ -105,6 +109,7 @@ export const Dialog = ({
e.preventDefault(); e.preventDefault();
} }
}} }}
$withAnimation={withAnimation}
> >
<Styled.Header $withBorder={hasHeaderBorder}> <Styled.Header $withBorder={hasHeaderBorder}>
<Styled.HeaderTopRow> <Styled.HeaderTopRow>
@@ -114,7 +119,7 @@ export const Dialog = ({
{title && <Styled.Title>{title}</Styled.Title>} {title && <Styled.Title>{title}</Styled.Title>}
{!preventClose && ( {!preventClose && withClose && (
<Styled.Close ref={closeButtonRef}> <Styled.Close ref={closeButtonRef}>
<Icon iconName={IconName.Close} /> <Icon iconName={IconName.Close} />
</Styled.Close> </Styled.Close>
@@ -168,7 +173,7 @@ Styled.Overlay = styled(Overlay)`
} }
`; `;
Styled.Container = styled(Content)<{ placement: DialogPlacement }>` Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?: boolean }>`
/* Params */ /* Params */
--dialog-inset: 1rem; --dialog-inset: 1rem;
--dialog-width: 30rem; --dialog-width: 30rem;
@@ -224,7 +229,7 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
outline: none; outline: none;
${({ placement }) => ${({ placement, $withAnimation }) =>
({ ({
[DialogPlacement.Default]: css` [DialogPlacement.Default]: css`
inset: var(--dialog-inset); inset: var(--dialog-inset);
@@ -260,9 +265,11 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
padding-bottom: var(--dialog-radius); */ padding-bottom: var(--dialog-radius); */
} }
@media (prefers-reduced-motion: no-preference) { ${$withAnimation &&
&[data-state='open'] { css`
animation: ${keyframes` @media (prefers-reduced-motion: no-preference) {
&[data-state='open'] {
animation: ${keyframes`
from { from {
opacity: 0; opacity: 0;
} }
@@ -270,18 +277,19 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
max-height: 0; max-height: 0;
} }
`} 0.15s var(--ease-out-expo); `} 0.15s var(--ease-out-expo);
} }
&[data-state='closed'] { &[data-state='closed'] {
animation: ${keyframes` animation: ${keyframes`
to { to {
opacity: 0; opacity: 0;
scale: 0.9; scale: 0.9;
max-height: 0; max-height: 0;
} }
`} 0.15s; `} 0.15s;
}
} }
} `}
`, `,
[DialogPlacement.Sidebar]: css` [DialogPlacement.Sidebar]: css`
--dialog-width: var(--sidebar-width); --dialog-width: var(--sidebar-width);
@@ -291,50 +299,52 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement }>`
margin-left: auto; margin-left: auto;
} }
@media (prefers-reduced-motion: no-preference) { ${$withAnimation &&
&[data-state='open'] { css`
animation: ${keyframes` @media (prefers-reduced-motion: no-preference) {
&[data-state='open'] {
animation: ${keyframes`
from { from {
translate: 100% 0; translate: 100% 0;
opacity: 0; opacity: 0;
} }
`} 0.15s var(--ease-out-expo); `} 0.15s var(--ease-out-expo);
} }
&[data-state='closed'] { &[data-state='closed'] {
animation: ${keyframes` animation: ${keyframes`
to { to {
translate: 100% 0; translate: 100% 0;
opacity: 0; opacity: 0;
} }
`} 0.15s var(--ease-out-expo); `} 0.15s var(--ease-out-expo);
}
} }
} `}
`, `,
[DialogPlacement.Inline]: css` [DialogPlacement.Inline]: css`
@media (prefers-reduced-motion: no-preference) { ${$withAnimation &&
&[data-state='open'] { css`
animation: ${keyframes` @media (prefers-reduced-motion: no-preference) {
&[data-state='open'] {
animation: ${keyframes`
from { from {
scale: 0.99; scale: 0.99;
opacity: 0; opacity: 0;
/* filter: blur(2px); */
/* backdrop-filter: none; */
} }
`} 0.15s var(--ease-out-expo); `} 0.15s var(--ease-out-expo);
} }
&[data-state='closed'] { &[data-state='closed'] {
animation: ${keyframes` animation: ${keyframes`
to { to {
scale: 0.99; scale: 0.99;
opacity: 0; opacity: 0;
/* filter: blur(2px); */
/* backdrop-filter: none; */
} }
`} 0.15s var(--ease-out-expo); `} 0.15s var(--ease-out-expo);
}
} }
} `}
`, `,
[DialogPlacement.FullScreen]: css` [DialogPlacement.FullScreen]: css`
--dialog-width: 100vw; --dialog-width: 100vw;
+3 -1
View File
@@ -33,9 +33,11 @@ export const DropdownHeaderMenu = <MenuItemValue extends string>({
return ( return (
<Root> <Root>
<Styled.Trigger className={className}> <Styled.Trigger className={className} asChild>
<div>
{children} {children}
<Styled.DropdownIconButton iconName={IconName.Caret} isToggle /> <Styled.DropdownIconButton iconName={IconName.Caret} isToggle />
</div>
</Styled.Trigger> </Styled.Trigger>
<Portal> <Portal>
<Styled.Content <Styled.Content
+4 -1
View File
@@ -9,6 +9,7 @@ import {
RadioItem, RadioItem,
Portal, Portal,
} from '@radix-ui/react-dropdown-menu'; } from '@radix-ui/react-dropdown-menu';
import { CheckIcon } from '@radix-ui/react-icons';
import { type MenuItem } from '@/constants/menus'; import { type MenuItem } from '@/constants/menus';
@@ -90,7 +91,9 @@ export const DropdownSelectMenu = <MenuItemValue extends string>({
{slotAfter} {slotAfter}
<Styled.ItemIndicator>{/* <CheckIcon /> */}</Styled.ItemIndicator> <Styled.ItemIndicator>
<CheckIcon />
</Styled.ItemIndicator>
</Styled.RadioItem> </Styled.RadioItem>
))} ))}
</RadioGroup> </RadioGroup>
+2 -29
View File
@@ -1,43 +1,16 @@
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
export const GreenCheckCircle = ({ className }: { className?: string }) => ( export const GreenCheckCircle = ({ className }: { className?: string }) => (
<Styled.GreenCheckCircle className={className}> <Styled.Icon className={className} iconName={IconName.CheckCircle} />
<Icon iconName={IconName.Check} />
</Styled.GreenCheckCircle>
); );
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.GreenCheckCircle = styled.div` Styled.Icon = styled(Icon)`
${layoutMixins.stack}
--icon-size: 1.25rem; --icon-size: 1.25rem;
--icon-border-width: 3px;
width: var(--icon-size); width: var(--icon-size);
height: var(--icon-size); height: var(--icon-size);
border-radius: 50%;
align-items: center;
&:before {
content: '';
width: calc(var(--icon-size) - var(--icon-border-width) * 2);
height: calc(var(--icon-size) - var(--icon-border-width) * 2);
border: solid var(--icon-border-width) var(--color-positive);
border-radius: 50%;
opacity: 0.6;
}
svg {
width: calc(var(--icon-size) / 3);
height: calc(var(--icon-size) / 3);
color: var(--color-positive);
justify-self: center;
}
`; `;
+25 -4
View File
@@ -15,6 +15,7 @@ import {
CautionCircleIcon, CautionCircleIcon,
ChatIcon, ChatIcon,
CheckIcon, CheckIcon,
CheckCircleIcon,
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
ClockIcon, ClockIcon,
@@ -23,10 +24,10 @@ import {
CoinsIcon, CoinsIcon,
CommentIcon, CommentIcon,
CopyIcon, CopyIcon,
CubeIcon, DepositIcon,
DepthChartIcon,
DiscordIcon, DiscordIcon,
EtherscanIcon, EtherscanIcon,
DepthChartIcon,
ExportKeysIcon, ExportKeysIcon,
FeedbackIcon, FeedbackIcon,
FileIcon, FileIcon,
@@ -36,10 +37,12 @@ import {
HelpCircleIcon, HelpCircleIcon,
HideIcon, HideIcon,
HistoryIcon, HistoryIcon,
LeaderboardIcon,
LinkOutIcon, LinkOutIcon,
LockIcon, LockIcon,
LogoShortIcon, LogoShortIcon,
MenuIcon, MenuIcon,
MigrateIcon,
MintscanIcon, MintscanIcon,
OrderbookIcon, OrderbookIcon,
OrderCanceledIcon, OrderCanceledIcon,
@@ -50,10 +53,13 @@ import {
OrderUntriggeredIcon, OrderUntriggeredIcon,
OverviewIcon, OverviewIcon,
PencilIcon, PencilIcon,
PlayIcon,
PlusIcon,
PositionsIcon, PositionsIcon,
PriceChartIcon, PriceChartIcon,
PrivacyIcon, PrivacyIcon,
QrIcon, QrIcon,
RewardStarIcon,
SearchIcon, SearchIcon,
SendIcon, SendIcon,
ShareIcon, ShareIcon,
@@ -68,6 +74,7 @@ import {
WarningIcon, WarningIcon,
WebsiteIcon, WebsiteIcon,
WhitepaperIcon, WhitepaperIcon,
WithdrawIcon,
} from '@/icons'; } from '@/icons';
export enum IconName { export enum IconName {
@@ -83,6 +90,7 @@ export enum IconName {
CautionCircleStroked = 'CautionCircleStroked', CautionCircleStroked = 'CautionCircleStroked',
Chat = 'Chat', Chat = 'Chat',
Check = 'Check', Check = 'Check',
CheckCircle = 'CheckCircle',
ChevronLeft = 'ChevronLeft', ChevronLeft = 'ChevronLeft',
ChevronRight = 'ChevronRight', ChevronRight = 'ChevronRight',
Clock = 'Clock', Clock = 'Clock',
@@ -91,7 +99,7 @@ export enum IconName {
Coins = 'Coins', Coins = 'Coins',
Comment = 'Comment', Comment = 'Comment',
Copy = 'Copy', Copy = 'Copy',
Cube = 'Cube', Deposit = 'Deposit',
DepthChart = 'DepthChart', DepthChart = 'DepthChart',
Discord = 'Discord', Discord = 'Discord',
Etherscan = 'Etherscan', Etherscan = 'Etherscan',
@@ -104,10 +112,12 @@ export enum IconName {
HelpCircle = 'HelpCircle', HelpCircle = 'HelpCircle',
Hide = 'Hide', Hide = 'Hide',
History = 'History', History = 'History',
Leaderboard = 'Leaderboard',
LinkOut = 'LinkOut', LinkOut = 'LinkOut',
Lock = 'Lock', Lock = 'Lock',
LogoShort = 'LogoShort', LogoShort = 'LogoShort',
Menu = 'Menu', Menu = 'Menu',
Migrate = 'Migrate',
Mintscan = 'Mintscan', Mintscan = 'Mintscan',
Onboarding = 'Onboarding', Onboarding = 'Onboarding',
Orderbook = 'OrderbookIcon', Orderbook = 'OrderbookIcon',
@@ -119,10 +129,13 @@ export enum IconName {
OrderUntriggered = 'OrderUntriggered', OrderUntriggered = 'OrderUntriggered',
Overview = 'Overview', Overview = 'Overview',
Pencil = 'Pencil', Pencil = 'Pencil',
Play = 'Play',
Plus = 'Plus',
Positions = 'Positions', Positions = 'Positions',
PriceChart = 'PriceChart', PriceChart = 'PriceChart',
Privacy = 'Privacy', Privacy = 'Privacy',
Qr = 'Qr', Qr = 'Qr',
RewardStar = 'RewardStar',
Search = 'Search', Search = 'Search',
Send = 'Send', Send = 'Send',
Share = 'Share', Share = 'Share',
@@ -137,6 +150,7 @@ export enum IconName {
Warning = 'Warning', Warning = 'Warning',
Website = 'Website', Website = 'Website',
Whitepaper = 'Whitepaper', Whitepaper = 'Whitepaper',
Withdraw = 'Withdraw',
} }
const icons = { const icons = {
@@ -152,6 +166,7 @@ const icons = {
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon, [IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
[IconName.Chat]: ChatIcon, [IconName.Chat]: ChatIcon,
[IconName.Check]: CheckIcon, [IconName.Check]: CheckIcon,
[IconName.CheckCircle]: CheckCircleIcon,
[IconName.ChevronLeft]: ChevronLeftIcon, [IconName.ChevronLeft]: ChevronLeftIcon,
[IconName.ChevronRight]: ChevronRightIcon, [IconName.ChevronRight]: ChevronRightIcon,
[IconName.Clock]: ClockIcon, [IconName.Clock]: ClockIcon,
@@ -160,7 +175,7 @@ const icons = {
[IconName.Coins]: CoinsIcon, [IconName.Coins]: CoinsIcon,
[IconName.Comment]: CommentIcon, [IconName.Comment]: CommentIcon,
[IconName.Copy]: CopyIcon, [IconName.Copy]: CopyIcon,
[IconName.Cube]: CubeIcon, [IconName.Deposit]: DepositIcon,
[IconName.DepthChart]: DepthChartIcon, [IconName.DepthChart]: DepthChartIcon,
[IconName.Discord]: DiscordIcon, [IconName.Discord]: DiscordIcon,
[IconName.Etherscan]: EtherscanIcon, [IconName.Etherscan]: EtherscanIcon,
@@ -173,10 +188,12 @@ const icons = {
[IconName.HelpCircle]: HelpCircleIcon, [IconName.HelpCircle]: HelpCircleIcon,
[IconName.Hide]: HideIcon, [IconName.Hide]: HideIcon,
[IconName.History]: HistoryIcon, [IconName.History]: HistoryIcon,
[IconName.Leaderboard]: LeaderboardIcon,
[IconName.LinkOut]: LinkOutIcon, [IconName.LinkOut]: LinkOutIcon,
[IconName.Lock]: LockIcon, [IconName.Lock]: LockIcon,
[IconName.LogoShort]: LogoShortIcon, [IconName.LogoShort]: LogoShortIcon,
[IconName.Menu]: MenuIcon, [IconName.Menu]: MenuIcon,
[IconName.Migrate]: MigrateIcon,
[IconName.Mintscan]: MintscanIcon, [IconName.Mintscan]: MintscanIcon,
[IconName.Orderbook]: OrderbookIcon, [IconName.Orderbook]: OrderbookIcon,
[IconName.OrderCanceled]: OrderCanceledIcon, [IconName.OrderCanceled]: OrderCanceledIcon,
@@ -187,10 +204,13 @@ const icons = {
[IconName.OrderUntriggered]: OrderUntriggeredIcon, [IconName.OrderUntriggered]: OrderUntriggeredIcon,
[IconName.Overview]: OverviewIcon, [IconName.Overview]: OverviewIcon,
[IconName.Pencil]: PencilIcon, [IconName.Pencil]: PencilIcon,
[IconName.Play]: PlayIcon,
[IconName.Plus]: PlusIcon,
[IconName.Positions]: PositionsIcon, [IconName.Positions]: PositionsIcon,
[IconName.PriceChart]: PriceChartIcon, [IconName.PriceChart]: PriceChartIcon,
[IconName.Privacy]: PrivacyIcon, [IconName.Privacy]: PrivacyIcon,
[IconName.Qr]: QrIcon, [IconName.Qr]: QrIcon,
[IconName.RewardStar]: RewardStarIcon,
[IconName.Search]: SearchIcon, [IconName.Search]: SearchIcon,
[IconName.Send]: SendIcon, [IconName.Send]: SendIcon,
[IconName.Share]: ShareIcon, [IconName.Share]: ShareIcon,
@@ -205,6 +225,7 @@ const icons = {
[IconName.Warning]: WarningIcon, [IconName.Warning]: WarningIcon,
[IconName.Website]: WebsiteIcon, [IconName.Website]: WebsiteIcon,
[IconName.Whitepaper]: WhitepaperIcon, [IconName.Whitepaper]: WhitepaperIcon,
[IconName.Withdraw]: WithdrawIcon,
} as Record<IconName, ElementType>; } as Record<IconName, ElementType>;
type ElementProps = { type ElementProps = {
+1
View File
@@ -67,6 +67,7 @@ const Styled: Record<string, AnyStyledComponent> = {};
const buttonMixin = css` const buttonMixin = css`
// Params // Params
--button-icon-size: 1.125em; --button-icon-size: 1.125em;
--button-padding: 0;
// Rules // Rules
> * { > * {
+30 -21
View File
@@ -10,7 +10,7 @@ import {
USD_DECIMALS, USD_DECIMALS,
} from '@/constants/numbers'; } from '@/constants/numbers';
import { BIG_NUMBERS, MustBigNumber } from '@/lib/numbers'; import { BIG_NUMBERS } from '@/lib/numbers';
import { useLocaleSeparators } from '@/hooks'; import { useLocaleSeparators } from '@/hooks';
export enum InputType { export enum InputType {
@@ -29,31 +29,38 @@ type StyleProps = {
type ElementProps = { type ElementProps = {
type?: InputType; type?: InputType;
value?: string | number | null; value?: string | number | null;
allowNegative?: boolean;
decimals?: number;
disabled?: boolean; disabled?: boolean;
id?: string; id?: string;
max?: number;
onBlur?: () => void; onBlur?: () => void;
onChange?:
| Dispatch<SetStateAction<string>>
| React.ReactEventHandler<HTMLInputElement>
| ((values: NumberFormatValues, e: SourceInfo) => void);
onFocus?: () => void; onFocus?: () => void;
onInput?: ({
value,
floatValue,
formattedValue,
}: {
value: string;
floatValue?: number;
formattedValue: string;
}) => void;
placeholder?: string; placeholder?: string;
}; };
export type InputProps = ElementProps & StyleProps; type ConditionalProps =
| {
allowNegative?: boolean;
decimals?: number;
max?: number;
onChange?: (values: NumberFormatValues, e: SourceInfo) => void;
onInput?: ({
value,
floatValue,
formattedValue,
}: {
value: string;
floatValue?: number;
formattedValue: string;
}) => void;
}
| {
allowNegative?: never;
decimals?: never;
max?: never;
onChange?: Dispatch<SetStateAction<string>> | React.ReactEventHandler<HTMLInputElement>;
onInput?: (e: SyntheticInputEvent) => void;
};
export type InputProps = ElementProps & StyleProps & ConditionalProps;
export const Input = forwardRef<HTMLInputElement, InputProps>( export const Input = forwardRef<HTMLInputElement, InputProps>(
( (
@@ -129,6 +136,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
// Other // Other
data-1p-ignore // prevent 1Password fill
{...otherProps} {...otherProps}
/> />
) : ( ) : (
@@ -137,6 +145,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
ref={ref as React.Ref<typeof NumericFormat<unknown>>} ref={ref as React.Ref<typeof NumericFormat<unknown>>}
id={id} id={id}
// NumericFormat // NumericFormat
valueIsNumericString
allowNegative={allowNegative} allowNegative={allowNegative}
decimalScale={decimals} decimalScale={decimals}
decimalSeparator={LOCALE_DECIMAL_SEPARATOR} decimalSeparator={LOCALE_DECIMAL_SEPARATOR}
@@ -160,7 +169,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
? undefined ? undefined
: Number(formattedValue.replace(',', '.')); : Number(formattedValue.replace(',', '.'));
onInput?.({ value, floatValue, formattedValue }); onInput?.({ value, floatValue, formattedValue, ...e });
}} }}
// Native // Native
disabled={disabled} disabled={disabled}
@@ -168,6 +177,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
value={formattedValue} value={formattedValue}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
data-1p-ignore // prevent 1Password fill
{...otherProps} {...otherProps}
/> />
)} )}
@@ -188,7 +198,6 @@ Styled.InputContainer = styled.div`
border-radius: inherit; border-radius: inherit;
input { input {
user-select: all;
flex: 1; flex: 1;
width: 100%; width: 100%;
} }
+1 -1
View File
@@ -1,11 +1,11 @@
import styled, { type AnyStyledComponent } from 'styled-components'; import styled, { type AnyStyledComponent } from 'styled-components';
import { RiskLevels } from '@/constants/abacus'; import { RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { Ring } from '@/components/Ring'; import { Ring } from '@/components/Ring';
import { abacusHelper } from '@/lib/abacus'; import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = { type ElementProps = {
value: number; value: number;
+15 -65
View File
@@ -1,4 +1,4 @@
import { forwardRef, Ref, useEffect, useRef, useState } from 'react'; import { forwardRef, Ref } from 'react';
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components'; import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
import { NavLink, matchPath, useLocation } from 'react-router-dom'; import { NavLink, matchPath, useLocation } from 'react-router-dom';
@@ -21,8 +21,6 @@ import {
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { isTruthy } from '@/lib/isTruthy';
import { Tag } from './Tag'; import { Tag } from './Tag';
import { Icon, IconName } from './Icon'; import { Icon, IconName } from './Icon';
@@ -61,7 +59,6 @@ const NavItem = forwardRef(
<> <>
{slotBefore} {slotBefore}
<span> <span>
{/* {`${label}${subitems?.length ? ' ' : ''}`} */}
{label} {label}
{tag && ( {tag && (
<> <>
@@ -71,7 +68,7 @@ const NavItem = forwardRef(
)} )}
</span> </span>
{slotAfter} {slotAfter}
{subitems?.length && <Styled.Icon iconName={IconName.Caret} />} {subitems?.length && <Styled.Icon iconName={IconName.Triangle} />}
</> </>
); );
@@ -100,8 +97,6 @@ const NavItem = forwardRef(
} }
); );
type TriggerRef = HTMLAnchorElement | HTMLDivElement | HTMLButtonElement | null;
export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue extends string>({ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue extends string>({
onSelectItem, onSelectItem,
onSelectGroup, onSelectGroup,
@@ -112,33 +107,6 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
dir = 'ltr', dir = 'ltr',
className, className,
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => { }: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => {
// Disable click (close) in the first 500ms after hover (open)
// https://github.com/radix-ui/primitives/issues/1630#issuecomment-1545995075
const [clickIsDisabled, setClickIsDisabled] = useState(false);
const triggerRefs = useRef({} as { [value: string]: TriggerRef });
useEffect(() => {
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (
mutation.type === 'attributes' &&
mutation.attributeName === 'data-state' &&
(mutation.target as unknown as HTMLOrSVGElement).dataset.state === 'open' &&
mutation.target !== document.activeElement
) {
setClickIsDisabled(true);
setTimeout(() => setClickIsDisabled(false), 500);
}
}
});
for (const element of Object.values(triggerRefs.current).filter(isTruthy)) {
observer.observe(element, { attributes: true });
}
return () => observer.disconnect();
}, []);
const renderSubitems = ({ const renderSubitems = ({
item, item,
depth = 0, depth = 0,
@@ -148,18 +116,18 @@ export const NavigationMenu = <MenuItemValue extends string, MenuGroupValue exte
}) => ( }) => (
<> <>
<Styled.SubMenuTrigger <Styled.SubMenuTrigger
asChild={depth > 0} asChild
ref={(ref: TriggerRef) => (triggerRefs.current[item.value] = ref)} onPointerMove={(e: MouseEvent) => e.preventDefault()}
onClick={(e: MouseEvent) => { onPointerLeave={(e: MouseEvent) => e.preventDefault()}
if (clickIsDisabled) {
e.preventDefault();
}
}}
> >
<Styled.NavItem onSelect={onSelectGroup} orientation={itemOrientation} {...item} /> <Styled.NavItem onSelect={onSelectGroup} orientation={itemOrientation} {...item} />
</Styled.SubMenuTrigger> </Styled.SubMenuTrigger>
<Styled.Content data-placement={submenuPlacement}> <Styled.Content
onPointerEnter={(e: MouseEvent) => e.preventDefault()}
onPointerLeave={(e: MouseEvent) => e.preventDefault()}
data-placement={submenuPlacement}
>
<Styled.Sub data-placement={submenuPlacement}> <Styled.Sub data-placement={submenuPlacement}>
<Styled.List <Styled.List
data-orientation={depth > 0 ? 'menu' : orientation === 'vertical' ? 'vertical' : 'menu'} data-orientation={depth > 0 ? 'menu' : orientation === 'vertical' ? 'vertical' : 'menu'}
@@ -410,25 +378,12 @@ Styled.List = styled(List)`
`; `;
Styled.ListItem = styled(Item)` Styled.ListItem = styled(Item)`
/* display: contents; */
display: grid; display: grid;
position: relative; position: relative;
${Styled.List}[data-orientation="horizontal"] > & { ${Styled.List}[data-orientation="horizontal"] > & {
gap: var(--submenu-side-offset); gap: var(--submenu-side-offset);
} }
/* ${Styled.List}[data-orientation="menu"] > & {
grid-template-columns: 1fr 0;
align-items: start;
gap: 2rem;
} */
/* &:has([data-state="open"]) {
position: sticky;
left: 0;
right: 0;
} */
`; `;
Styled.SubMenuTrigger = styled(Trigger)` Styled.SubMenuTrigger = styled(Trigger)`
@@ -436,11 +391,6 @@ Styled.SubMenuTrigger = styled(Trigger)`
outline-offset: -2px; outline-offset: -2px;
&[data-state='open'] { &[data-state='open'] {
div {
background-color: var(--navigationMenu-item-checked-backgroundColor);
color: var(--navigationMenu-item-checked-textColor);
}
svg { svg {
rotate: 0.5turn; rotate: 0.5turn;
} }
@@ -452,11 +402,10 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
subitems?.length subitems?.length
? css` ? css`
${popoverMixins.trigger} ${popoverMixins.trigger}
--trigger-backgroundColor: transparent; --trigger-open-backgroundColor: var(--navigationMenu-item-checked-backgroundColor);
--trigger-open-backgroundColor: var(--color-layer-3);
` `
: css` : css`
&:hover:not(.active) { &:hover:not(:active) {
background-color: var(--navigationMenu-item-highlighted-backgroundColor); background-color: var(--navigationMenu-item-highlighted-backgroundColor);
color: var(--navigationMenu-item-highlighted-textColor); color: var(--navigationMenu-item-highlighted-textColor);
} }
@@ -470,8 +419,6 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
--item-radius: var(--navigationMenu-item-radius); --item-radius: var(--navigationMenu-item-radius);
--item-padding: var(--navigationMenu-item-padding); --item-padding: var(--navigationMenu-item-padding);
/* ${popoverMixins.backdropOverlay} */
${layoutMixins.scrollSnapItem} ${layoutMixins.scrollSnapItem}
min-height: var(--navigationMenu-item-height); min-height: var(--navigationMenu-item-height);
@@ -525,6 +472,9 @@ Styled.NavItem = styled(NavItem)<{ orientation: 'horizontal' | 'vertical' }>`
`; `;
Styled.Icon = styled(Icon)` Styled.Icon = styled(Icon)`
font-size: 0.375em;
transition: rotate 0.3s var(--ease-out-expo);
${Styled.List}[data-orientation="menu"] & { ${Styled.List}[data-orientation="menu"] & {
rotate: -0.25turn; rotate: -0.25turn;
} }
+201
View File
@@ -0,0 +1,201 @@
import React, { type MouseEvent } from 'react';
import styled, { css } from 'styled-components';
import { ButtonShape, ButtonSize } from '@/constants/buttons';
import {
NotificationStatus,
type Notification as NotificationDataType,
} from '@/constants/notifications';
import { useNotifications } from '@/hooks/useNotifications';
import { popoverMixins } from '@/styles/popoverMixins';
import { layoutMixins } from '@/styles/layoutMixins';
import { IconButton } from '@/components/IconButton';
import { IconName } from '@/components/Icon';
import { Output, OutputType } from '@/components/Output';
type ElementProps = {
notification: NotificationDataType;
onClick?: () => void;
slotIcon?: React.ReactNode;
slotTitle?: React.ReactNode;
slotTitleLeft?: React.ReactNode;
slotTitleRight?: React.ReactNode;
slotDescription?: React.ReactNode;
slotCustomContent?: React.ReactNode;
slotAction?: React.ReactNode;
withClose?: boolean;
};
type StyleProps = {
className?: string;
isToast?: boolean;
};
export type NotificationProps = ElementProps & StyleProps;
export const Notification = ({
className,
isToast,
notification,
onClick,
slotIcon,
slotTitle,
slotTitleLeft,
slotTitleRight,
slotDescription,
slotCustomContent,
slotAction,
withClose = !isToast,
}: NotificationProps) => {
const { markCleared, markSeen } = useNotifications();
return (
<$Container className={className} isToast={isToast} onClick={onClick}>
<$Header>
{slotTitleLeft ?? (slotIcon && <$Icon>{slotIcon}</$Icon>)}
<$Title>{slotTitle}</$Title>
{slotTitleRight}
{withClose && !isToast && (
<$ActionItems>
<$Output
type={OutputType.RelativeTime}
value={notification.timestamps[NotificationStatus.Triggered]}
/>
{notification.status < NotificationStatus.Seen ? <$UnreadIndicator /> : null}
{notification.status < NotificationStatus.Cleared ? (
<$IconButton
iconName={IconName.Close}
shape={ButtonShape.Square}
size={ButtonSize.XSmall}
onClick={(e: MouseEvent) => {
e.stopPropagation();
if (notification.status < NotificationStatus.Seen) {
markSeen(notification);
} else if (notification.status < NotificationStatus.Cleared) {
markCleared(notification);
}
}}
/>
) : null}
</$ActionItems>
)}
</$Header>
<$Description>{slotCustomContent ?? slotDescription}</$Description>
{slotAction && <$Action>{slotAction}</$Action>}
</$Container>
);
};
const $Container = styled.div<{ isToast?: boolean }>`
// Params
--toast-icon-size: 1.75em;
// Rules
${popoverMixins.popover}
overflow: visible;
padding: 1rem 1.25rem;
${({ isToast }) =>
isToast
? css`
padding: 1rem;
box-shadow: 0 0 0 var(--border-width) var(--color-border),
// border
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
`
: css`
flex: 1;
background-color: transparent;
border-radius: 0;
backdrop-filter: none;
`}
`;
const $Header = styled.header`
${layoutMixins.row}
position: relative;
`;
const $Icon = styled.div`
${layoutMixins.row}
float: left;
margin-right: 0.5rem;
line-height: 1;
> svg,
img {
width: 1.5rem;
height: 1.5rem;
}
`;
const $Title = styled.div`
flex: 1;
font: var(--font-base-medium);
color: var(--color-text-2);
overflow: hidden;
text-overflow: ellipsis;
`;
const $Description = styled.div`
margin-top: 0.5rem;
color: var(--color-text-0);
font: var(--font-small-book);
`;
const $Action = styled.div`
margin-top: 0.5rem;
font: var(--font-small-book);
`;
const $ActionItems = styled.div`
min-width: fit-content;
padding-left: 0.5rem;
height: 100%;
${layoutMixins.row};
justify-content: end;
gap: 0.5rem;
position: absolute;
right: 0;
top: 0;
background-color: var(--color-layer-2);
font: var(--font-mini-book);
display: none;
z-index: 1;
${$Container}:hover & {
display: flex;
}
`;
const $UnreadIndicator = styled.div`
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background-color: var(--color-accent);
border: 1px solid var(--color-layer-2);
`;
const $Output = styled(Output)`
color: var(--color-text-0);
`;
const $IconButton = styled(IconButton)`
--button-border: none;
--button-textColor: var(--color-text-0);
--button-hover-textColor: var(--color-text-1);
`;
+59 -27
View File
@@ -4,10 +4,12 @@ import { Link } from 'react-router-dom';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
type PanelProps = { type PanelProps = {
slotHeaderContent?: string; slotHeaderContent?: string;
slotHeader?: React.ReactNode; slotHeader?: React.ReactNode;
slotRight?: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
href?: string; href?: string;
onHeaderClick?: () => void; onHeaderClick?: () => void;
@@ -22,6 +24,7 @@ type PanelStyleProps = {
export const Panel = ({ export const Panel = ({
slotHeaderContent, slotHeaderContent,
slotHeader, slotHeader,
slotRight,
children, children,
href, href,
onHeaderClick, onHeaderClick,
@@ -29,56 +32,86 @@ export const Panel = ({
hasSeparator, hasSeparator,
className, className,
}: PanelProps & PanelStyleProps) => ( }: PanelProps & PanelStyleProps) => (
<Styled.Panel onClick={onClick}> <Styled.Panel onClick={onClick} className={className}>
{href ? ( <Styled.Left>
<Link to={href}> {href ? (
{slotHeader ? ( <Link to={href}>
slotHeader {slotHeader ? (
) : ( slotHeader
) : (
<Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}>
{slotHeaderContent}
<Styled.Icon iconName={IconName.ChevronRight} />
</Styled.Header>
)}
</Link>
) : slotHeader ? (
slotHeader
) : (
slotHeaderContent && (
<Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}> <Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}>
{slotHeaderContent} {slotHeaderContent}
<Styled.Icon iconName={IconName.ChevronRight} />
</Styled.Header> </Styled.Header>
)} )
</Link> )}
) : slotHeader ? ( <Styled.Content>{children}</Styled.Content>
slotHeader </Styled.Left>
) : ( {slotRight}
slotHeaderContent && (
<Styled.Header role="button" onClick={onHeaderClick} hasSeparator={hasSeparator}>
{slotHeaderContent}
</Styled.Header>
)
)}
<Styled.Content className={className}>{children}</Styled.Content>
</Styled.Panel> </Styled.Panel>
); );
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled.section` Styled.Panel = styled.section<{ onClick?: () => void }>`
${layoutMixins.flexColumn} --panel-paddingY: 1rem;
--panel-paddingX: 1rem;
--panel-content-paddingY: var(--panel-paddingY);
--panel-content-paddingX: var(--panel-paddingX);
@media ${breakpoints.notTablet} {
--panel-paddingX: 1.5rem;
--panel-paddingY: 1.25rem;
--panel-content-paddingY: 1rem;
}
${layoutMixins.row}
background-color: var(--color-layer-3); background-color: var(--color-layer-3);
border-radius: 0.875rem; border-radius: 0.875rem;
${({ onClick }) =>
onClick &&
css`
cursor: pointer;
&:hover {
button:not(:disabled) {
color: var(--button-hover-textColor);
filter: var(--button-hover-filter);
}
}
`}
`;
Styled.Left = styled.div`
${layoutMixins.flexColumn}
width: 100%;
`; `;
Styled.Header = styled.header<{ hasSeparator?: boolean }>` Styled.Header = styled.header<{ hasSeparator?: boolean }>`
${layoutMixins.spacedRow} ${layoutMixins.spacedRow}
padding: var(--panel-paddingY) var(--panel-paddingX);
padding: 0.875rem 1rem 0.625rem;
font-size: 0.875rem;
${({ hasSeparator }) => ${({ hasSeparator }) =>
hasSeparator && hasSeparator &&
css` css`
padding-bottom: 0.625rem;
box-shadow: 0 var(--border-width) var(--border-color); box-shadow: 0 var(--border-width) var(--border-color);
`} `}
`; `;
Styled.Icon = styled(Icon)` Styled.Icon = styled(Icon)`
color: var(--color-text-0); color: var(--color-text-0);
font-size: 0.625rem; font-size: 0.625rem;
`; `;
@@ -86,6 +119,5 @@ Styled.Content = styled.div`
${layoutMixins.scrollArea} ${layoutMixins.scrollArea}
${layoutMixins.stickyArea0} ${layoutMixins.stickyArea0}
--stickyArea0-background: transparent; --stickyArea0-background: transparent;
padding: var(--panel-content-paddingY) var(--panel-content-paddingX);
padding: 0.5rem 1rem;
`; `;
+7 -1
View File
@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import styled, { type AnyStyledComponent, css } from 'styled-components'; import styled, { type AnyStyledComponent, css } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -22,6 +22,11 @@ export const SearchInput = ({
}: SearchInputProps) => { }: SearchInputProps) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (isOpen) inputRef?.current?.focus();
}, [inputRef, isOpen]);
return ( return (
<Styled.Search> <Styled.Search>
@@ -41,6 +46,7 @@ export const SearchInput = ({
/> />
<Styled.Input <Styled.Input
autoFocus autoFocus
ref={inputRef}
value={value} value={value}
isOpen={isOpen} isOpen={isOpen}
type={type} type={type}
+12 -7
View File
@@ -12,6 +12,8 @@ import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { WithLabel } from '@/components/WithLabel'; import { WithLabel } from '@/components/WithLabel';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
import breakpoints from '@/styles/breakpoints';
type ElementProps = { type ElementProps = {
asChild?: boolean; asChild?: boolean;
@@ -84,27 +86,30 @@ export const SearchSelectMenu = ({
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.SearchSelectMenu = styled.div`
${layoutMixins.column}
`;
Styled.MenuTrigger = styled.div` Styled.MenuTrigger = styled.div`
height: var(--form-input-height); height: var(--form-input-height);
${layoutMixins.spacedRow} ${layoutMixins.spacedRow}
align-items: center; align-items: center;
padding: 0 1rem; padding: var(--form-input-paddingY) var(--form-input-paddingX);
@media ${breakpoints.tablet} {
height: var(--form-input-height-mobile);
}
`; `;
Styled.WithLabel = styled(WithLabel)` Styled.WithLabel = styled(WithLabel)`
--label-textColor: var(--color-text-0); ${formMixins.inputLabel}
gap: 0.25rem;
label { label {
font: var(--font-mini-book); font: var(--font-mini-book);
} }
`; `;
Styled.SearchSelectMenu = styled.div`
${layoutMixins.column}
`;
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)` Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
--withReceipt-backgroundColor: var(--color-layer-2); --withReceipt-backgroundColor: var(--color-layer-2);
+15 -9
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import styled, { type AnyStyledComponent } from 'styled-components'; import styled, { css, type AnyStyledComponent } from 'styled-components';
import { import {
Root, Root,
@@ -13,11 +13,13 @@ import {
Portal, Portal,
Icon as SelectIcon, Icon as SelectIcon,
} from '@radix-ui/react-select'; } from '@radix-ui/react-select';
import { CheckIcon } from '@radix-ui/react-icons';
import { popoverMixins } from '@/styles/popoverMixins'; import { popoverMixins } from '@/styles/popoverMixins';
import { formMixins } from '@/styles/formMixins'; import { formMixins } from '@/styles/formMixins';
import { WithLabel } from '@/components/WithLabel'; import { WithLabel } from '@/components/WithLabel';
import { Icon, IconName } from './Icon';
export const SelectMenu = <T extends string>({ export const SelectMenu = <T extends string>({
children, children,
@@ -44,7 +46,9 @@ export const SelectMenu = <T extends string>({
) : ( ) : (
<Value /> <Value />
)} )}
{React.Children.toArray(children).length > 1 && <Styled.DropdownIcon />} {React.Children.toArray(children).length > 1 && (
<Styled.TriggerIcon iconName={IconName.Triangle} />
)}
</Styled.Trigger> </Styled.Trigger>
<Portal> <Portal>
<Styled.Content className={className}> <Styled.Content className={className}>
@@ -68,7 +72,9 @@ export const SelectItem = <T extends string>({
}) => ( }) => (
<Styled.Item className={className} value={value}> <Styled.Item className={className} value={value}>
<ItemText>{label}</ItemText> <ItemText>{label}</ItemText>
<Styled.ItemIndicator>{/* <CheckIcon /> */}</Styled.ItemIndicator> <Styled.ItemIndicator>
<CheckIcon />
</Styled.ItemIndicator>
</Styled.Item> </Styled.Item>
); );
@@ -88,12 +94,6 @@ Styled.Trigger = styled(Trigger)<{ $withBlur?: boolean }>`
} }
`; `;
Styled.DropdownIcon = styled(SelectIcon)`
font-size: 0.675em;
color: var(--color-text-1);
margin-left: auto;
`;
Styled.Content = styled(Content)` Styled.Content = styled(Content)`
--select-menu-content-maxWidth: ; --select-menu-content-maxWidth: ;
max-width: var(--select-menu-content-maxWidth); max-width: var(--select-menu-content-maxWidth);
@@ -116,3 +116,9 @@ Styled.WithLabel = styled(WithLabel)`
${formMixins.inputLabel} ${formMixins.inputLabel}
border-radius: 0; border-radius: 0;
`; `;
Styled.TriggerIcon = styled(Icon)`
width: 0.625rem;
height: 0.375rem;
color: var(--color-text-0);
`;
+1 -1
View File
@@ -47,7 +47,7 @@ Styled.Root = styled(Root)`
--switch-thumb-backgroundColor: var(--color-layer-6); --switch-thumb-backgroundColor: var(--color-layer-6);
--switch-active-backgroundColor: var(--color-accent); --switch-active-backgroundColor: var(--color-accent);
--switch-active-thumb-backgroundColor: var(--color-white); --switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground};
position: relative; position: relative;
width: var(--switch-width); width: var(--switch-width);
+14 -12
View File
@@ -43,6 +43,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { Icon, IconName } from './Icon'; import { Icon, IconName } from './Icon';
import { Tag } from './Tag'; import { Tag } from './Tag';
import { MustBigNumber } from '@/lib/numbers';
export { TableCell } from './Table/TableCell'; export { TableCell } from './Table/TableCell';
export { TableColumnHeader } from './Table/TableColumnHeader'; export { TableColumnHeader } from './Table/TableColumnHeader';
@@ -161,10 +162,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
? // String ? // String
collator.compare(first as string, second as string) collator.compare(first as string, second as string)
: // Number : // Number
Math.sign( MustBigNumber(first).comparedTo(MustBigNumber(second))) *
(parseInt(first as string) || (first as number)) -
(parseInt(second as string) || (second as number))
)) *
// Flip the direction if descending order is specified. // Flip the direction if descending order is specified.
(sortDirection === 'descending' ? -1 : 1) (sortDirection === 'descending' ? -1 : 1)
); );
@@ -831,6 +829,7 @@ Styled.SortArrow = styled.span<{ sortDirection: 'ascending' | 'descending' }>`
Styled.Thead = styled.thead<StyleProps>` Styled.Thead = styled.thead<StyleProps>`
${layoutMixins.stickyHeader} ${layoutMixins.stickyHeader}
scroll-snap-align: none; scroll-snap-align: none;
font: var(--font-mini-book);
> * { > * {
height: var(--stickyArea-topHeight); height: var(--stickyArea-topHeight);
@@ -839,18 +838,17 @@ Styled.Thead = styled.thead<StyleProps>`
color: var(--tableHeader-textColor); color: var(--tableHeader-textColor);
background-color: var(--tableHeader-backgroundColor); background-color: var(--tableHeader-backgroundColor);
@media ${breakpoints.notTablet} { ${({ withInnerBorders, withGradientCardRows }) =>
${({ withInnerBorders, withGradientCardRows }) => withInnerBorders &&
withInnerBorders && !withGradientCardRows &&
!withGradientCardRows && css`
css` ${layoutMixins.withInnerHorizontalBorders}
${layoutMixins.withInnerHorizontalBorders} `}
`}
}
`; `;
Styled.Tbody = styled.tbody<StyleProps>` Styled.Tbody = styled.tbody<StyleProps>`
${layoutMixins.stickyArea2} ${layoutMixins.stickyArea2}
font: var(--font-small-book);
// If <table> height is fixed with not enough rows to overflow, vertically center the rows // If <table> height is fixed with not enough rows to overflow, vertically center the rows
&:before, &:before,
@@ -869,6 +867,10 @@ Styled.Tbody = styled.tbody<StyleProps>`
--stickyArea2-paddingBottom: var(--border-width); --stickyArea2-paddingBottom: var(--border-width);
--stickyArea2-paddingLeft: var(--border-width); --stickyArea2-paddingLeft: var(--border-width);
--stickyArea2-paddingRight: var(--border-width); --stickyArea2-paddingRight: var(--border-width);
tr:first-of-type {
box-shadow: none;
}
`} `}
${({ withGradientCardRows }) => ${({ withGradientCardRows }) =>
+5 -2
View File
@@ -21,6 +21,7 @@ export type TabItem<TabItemsValue> = {
content?: React.ReactNode; content?: React.ReactNode;
subitems?: TabItem<TabItemsValue>[]; subitems?: TabItem<TabItemsValue>[];
customTrigger?: ReactNode; customTrigger?: ReactNode;
asChild?: boolean;
}; };
type ElementProps<TabItemsValue> = { type ElementProps<TabItemsValue> = {
@@ -41,6 +42,8 @@ type StyleProps = {
className?: string; className?: string;
}; };
export type TabsProps<TabItemsValue> = ElementProps<TabItemsValue> & StyleProps;
export const Tabs = <TabItemsValue extends string>({ export const Tabs = <TabItemsValue extends string>({
defaultValue, defaultValue,
value, value,
@@ -107,9 +110,10 @@ export const Tabs = <TabItemsValue extends string>({
sharedContent sharedContent
) : ( ) : (
<Styled.Stack> <Styled.Stack>
{items.map(({ value, content, forceMount }) => ( {items.map(({ asChild, value, content, forceMount }) => (
<Styled.Content <Styled.Content
key={value} key={value}
asChild={asChild}
value={value} value={value}
forceMount={forceMount} forceMount={forceMount}
$hide={forceMount && currentItem?.value !== value} $hide={forceMount && currentItem?.value !== value}
@@ -242,7 +246,6 @@ Styled.Trigger = styled(Trigger)<{ $withBorders?: boolean }>`
Styled.Stack = styled.div` Styled.Stack = styled.div`
${layoutMixins.stack} ${layoutMixins.stack}
${layoutMixins.perspectiveArea}
box-shadow: none; box-shadow: none;
`; `;
+76 -77
View File
@@ -1,24 +1,19 @@
import React, { useEffect, useRef, useState } from 'react'; import { type MouseEvent, useEffect, useRef, useState } from 'react';
import styled, { keyframes } from 'styled-components';
import { Root, Title, Description, Action, Close } from '@radix-ui/react-toast'; import { Root, Action, Close } from '@radix-ui/react-toast';
import { ButtonShape, ButtonSize } from '@/constants/buttons'; import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { popoverMixins } from '@/styles/popoverMixins';
import { breakpoints } from '@/styles';
import { Notification, type NotificationProps } from '@/components/Notification';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';
import { CloseIcon } from '@/icons'; import { IconName } from './Icon';
import styled, { keyframes } from 'styled-components';
import { popoverMixins } from '@/styles/popoverMixins';
import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = { type ElementProps = {
isOpen?: boolean; isOpen?: boolean;
setIsOpen?: (isOpen: boolean, isClosedFromTimeout?: boolean) => void; setIsOpen?: (isOpen: boolean, isClosedFromTimeout?: boolean) => void;
slotIcon?: React.ReactNode;
slotTitle?: React.ReactNode;
slotDescription?: React.ReactNode;
slotCustomContent?: React.ReactNode;
slotAction?: React.ReactNode;
actionDescription?: string; actionDescription?: string;
actionAltText?: string; actionAltText?: string;
sensitivity?: 'foreground' | 'background'; sensitivity?: 'foreground' | 'background';
@@ -26,13 +21,22 @@ type ElementProps = {
lastUpdated?: number; lastUpdated?: number;
}; };
type StyleProps = {}; type StyleProps = {
className?: string;
};
type ToastProps = NotificationProps & ElementProps & StyleProps;
export const Toast = ({ export const Toast = ({
className,
isOpen = true, isOpen = true,
notification,
onClick,
setIsOpen, setIsOpen,
slotIcon, slotIcon,
slotTitle, slotTitle,
slotTitleLeft,
slotTitleRight,
slotDescription, slotDescription,
slotCustomContent, slotCustomContent,
slotAction, slotAction,
@@ -41,7 +45,7 @@ export const Toast = ({
sensitivity = 'background', sensitivity = 'background',
duration = Infinity, duration = Infinity,
lastUpdated, lastUpdated,
}: ElementProps & StyleProps) => { }: ToastProps) => {
// Timeout // Timeout
const timeout = useRef<number>(); const timeout = useRef<number>();
const [isPaused, setIsPaused] = useState(false); const [isPaused, setIsPaused] = useState(false);
@@ -58,37 +62,45 @@ export const Toast = ({
return ( return (
<$Root <$Root
className={className}
type={sensitivity} type={sensitivity}
duration={Infinity} duration={Infinity}
open={isOpen} open={isOpen}
onOpenChange={setIsOpen} onOpenChange={setIsOpen}
onPause={() => setIsPaused(true)} onPause={() => setIsPaused(true)}
onResume={() => setIsPaused(false)} onResume={() => setIsPaused(false)}
onClick={() => {
setIsOpen?.(false, false);
onClick?.();
}}
> >
<div> <Close asChild>
<$Container> <$CloseButton
<$Header> iconName={IconName.Close}
{slotIcon && <$Icon>{slotIcon}</$Icon>} shape={ButtonShape.Circle}
size={ButtonSize.XSmall}
onClick={(e: MouseEvent) => e.stopPropagation()}
/>
</Close>
<Close asChild> {slotCustomContent ?? (
<$CloseButton <$Notification
iconComponent={CloseIcon} isToast
shape={ButtonShape.Square} notification={notification}
size={ButtonSize.XSmall} slotIcon={slotIcon}
/> slotTitle={slotTitle}
</Close> slotTitleLeft={slotTitleLeft}
slotTitleRight={slotTitleRight}
<$Title>{slotTitle}</$Title> slotDescription={slotDescription}
</$Header> slotAction={
{!slotCustomContent && <$Description>{slotDescription}</$Description>} actionDescription && (
{slotCustomContent} <$Action asChild altText={actionAltText}>
{actionDescription && ( {slotAction}
<$Action asChild altText={actionAltText}> </$Action>
{slotAction} )
</$Action> }
)} />
</$Container> )}
</div>
</$Root> </$Root>
); );
}; };
@@ -114,7 +126,7 @@ const $Root = styled(Root)`
translate: var(--x) var(--y); translate: var(--x) var(--y);
will-change: translate, margin-top; will-change: translate, margin-top;
margin-bottom: var(--toasts-gap); /* margin-bottom: var(--toasts-gap); */
&[data-swipe-direction='right'] { &[data-swipe-direction='right'] {
&[data-state='open'] { &[data-state='open'] {
@@ -163,6 +175,10 @@ const $Root = styled(Root)`
cursor: n-resize; cursor: n-resize;
} }
&:focus:not([data-swipe='end']) & {
outline: var(--color-accent) 1px solid;
}
&[data-swipe='move'] { &[data-swipe='move'] {
transition-property: opacity; transition-property: opacity;
opacity: 0.98; opacity: 0.98;
@@ -171,6 +187,10 @@ const $Root = styled(Root)`
* { * {
cursor: inherit; cursor: inherit;
} }
& > * {
opacity: 0.5;
}
} }
&[data-state='closed'], &[data-state='closed'],
@@ -198,69 +218,48 @@ const $Root = styled(Root)`
} }
`; `;
const $Container = styled.div` const $Notification = styled(Notification)`
// Params // Params
--toast-icon-size: 1.75em; --toast-icon-size: 1.75em;
// Rules // Rules
${popoverMixins.popover} ${popoverMixins.popover}
overflow: visible;
padding: 1rem; padding: 1rem;
box-shadow: 0 0 0 var(--border-width) var(--color-border), box-shadow: 0 0 0 var(--border-width) var(--color-border),
// border // border
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow 0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
${$Root}:focus:not([data-swipe='end']) & {
outline: var(--color-accent) 1px solid;
}
> * { > * {
transition: opacity 0.2s; transition: opacity 0.2s;
} }
${$Root}[data-swipe='move'] & > * {
opacity: 0.5;
}
`;
const $Header = styled.header`
display: block;
`;
const $Icon = styled.div`
${layoutMixins.row}
float: left;
width: 1em;
height: 1em;
margin-right: 0.4em;
line-height: 1;
`; `;
const $CloseButton = styled(IconButton)` const $CloseButton = styled(IconButton)`
--button-textColor: var(--color-text-0); --button-textColor: var(--color-text-0);
--button-border: none; --button-border: none;
--button-icon-size: 0.85em; --button-icon-size: 0.85em;
display: none;
float: right; /* float: right; */
margin: -0.42rem -0.42rem -0.42rem 0.42rem; margin: -0.42rem -0.42rem -0.42rem 0.42rem;
`;
const $Title = styled(Title)` // Absolute
flex: 1; position: absolute;
top: 0;
right: 0;
font: var(--font-base-medium); border: solid var(--border-width) var(--color-border);
color: var(--color-text-2);
overflow: hidden; ${$Root}:hover & {
text-overflow: ellipsis; display: block;
`; z-index: 2;
}
const $Description = styled(Description)` @media ${breakpoints.mobile} {
margin-top: 0.5rem; display: block;
color: var(--color-text-0); z-index: 2;
font: var(--font-small-book); }
`; `;
const $Action = styled(Action)` const $Action = styled(Action)`
+8 -4
View File
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import { Provider, Viewport } from '@radix-ui/react-toast'; import { Provider, Viewport } from '@radix-ui/react-toast';
import styled from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = { type ElementProps = {
@@ -12,7 +13,9 @@ type StyleProps = {
className?: string; className?: string;
}; };
export const ToastArea = ({ swipeDirection, children, className }: ElementProps & StyleProps) => ( type ToastAreaProps = ElementProps & StyleProps;
export const ToastArea = ({ swipeDirection, children, className }: ToastAreaProps) => (
<$ToastArea className={className}> <$ToastArea className={className}>
<Provider swipeDirection={swipeDirection}> <Provider swipeDirection={swipeDirection}>
{children} {children}
@@ -24,9 +27,8 @@ export const ToastArea = ({ swipeDirection, children, className }: ElementProps
const $ToastArea = styled.aside` const $ToastArea = styled.aside`
// Params // Params
--toasts-gap: 0.5rem; --toasts-gap: 0.5rem;
// Rules // Rules
${layoutMixins.scrollArea}
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
@@ -34,6 +36,7 @@ const $ToastArea = styled.aside`
> * { > * {
display: contents; display: contents;
pointer-events: none; pointer-events: none;
position: relative;
> ol { > ol {
/* display: block; */ /* display: block; */
@@ -43,6 +46,7 @@ const $ToastArea = styled.aside`
// Desired display order: latest to earliest (¯\_(ツ)_/¯) // Desired display order: latest to earliest (¯\_(ツ)_/¯)
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
position: relative;
pointer-events: none; pointer-events: none;
+4
View File
@@ -52,15 +52,19 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.BaseButton = styled(BaseButton)` Styled.BaseButton = styled(BaseButton)`
--button-toggle-off-backgroundColor: var(--color-layer-3); --button-toggle-off-backgroundColor: var(--color-layer-3);
--button-toggle-off-textColor: var(--color-text-0); --button-toggle-off-textColor: var(--color-text-0);
--button-toggle-off-border: solid var(--border-width) var(--border-color);
--button-toggle-on-backgroundColor: var(--color-layer-1); --button-toggle-on-backgroundColor: var(--color-layer-1);
--button-toggle-on-textColor: var(--color-text-2); --button-toggle-on-textColor: var(--color-text-2);
--button-toggle-on-border: solid var(--border-width) var(--border-color);
--button-backgroundColor: var(--button-toggle-off-backgroundColor); --button-backgroundColor: var(--button-toggle-off-backgroundColor);
--button-textColor: var(--button-toggle-off-textColor); --button-textColor: var(--button-toggle-off-textColor);
--button-border: var(--button-toggle-off-border);
&[data-state='on'], &[data-state='on'],
&[data-state='active'] { &[data-state='active'] {
--button-backgroundColor: var(--button-toggle-on-backgroundColor); --button-backgroundColor: var(--button-toggle-on-backgroundColor);
--button-textColor: var(--button-toggle-on-textColor); --button-textColor: var(--button-toggle-on-textColor);
--button-border: var(--button-toggle-on-border);
} }
`; `;
+15 -4
View File
@@ -7,13 +7,15 @@ import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { useBreakpoints } from '@/hooks'; import { useBreakpoints } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { type BaseButtonProps } from '@/components/BaseButton'; import { type BaseButtonProps } from '@/components/BaseButton';
import { ToggleButton } from '@/components/ToggleButton'; import { ToggleButton } from '@/components/ToggleButton';
type ElementProps<MenuItemValue extends string> = { type ElementProps<MenuItemValue extends string> = {
items: MenuItem<MenuItemValue>[]; items: MenuItem<MenuItemValue>[];
value: MenuItemValue; value: MenuItemValue;
onValueChange: (value: MenuItemValue) => void; onValueChange: (value: any) => void;
onInteraction?: () => void;
ensureSelected?: boolean;
}; };
type StyleProps = { type StyleProps = {
@@ -25,10 +27,14 @@ export const ToggleGroup = forwardRef(
{ {
items, items,
value, value,
ensureSelected = true,
onValueChange, onValueChange,
onInteraction,
className, className,
size, size,
shape = ButtonShape.Pill, shape = ButtonShape.Pill,
...buttonProps ...buttonProps
}: ElementProps<MenuItemValue> & StyleProps & BaseButtonProps, }: ElementProps<MenuItemValue> & StyleProps & BaseButtonProps,
ref: Ref<HTMLDivElement> ref: Ref<HTMLDivElement>
@@ -40,12 +46,17 @@ export const ToggleGroup = forwardRef(
ref={ref} ref={ref}
type="single" type="single"
value={value} value={value}
onValueChange={onValueChange} onValueChange={(newValue: MenuItemValue) => {
if ((ensureSelected && newValue) || !ensureSelected) {
onValueChange(newValue);
}
onInteraction?.();
}}
className={className} className={className}
loop loop
> >
{items.map((item) => ( {items.map((item) => (
<Item key={item.value} value={item.value} aria-label={item.label} asChild> <Item key={item.value} value={item.value} asChild>
<ToggleButton <ToggleButton
size={size ? size : isTablet ? ButtonSize.Small : ButtonSize.XSmall} size={size ? size : isTablet ? ButtonSize.Small : ButtonSize.XSmall}
shape={shape} shape={shape}
+2 -2
View File
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus'; import { type RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { abacusHelper } from '@/lib/abacus'; import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = { type ElementProps = {
value: number; value: number;
}; };
+20 -8
View File
@@ -2,11 +2,13 @@ import type { ReactNode } from 'react';
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { Content, Portal, Provider, Root, Trigger, Arrow } from '@radix-ui/react-tooltip'; import { Content, Portal, Provider, Root, Trigger, Arrow } from '@radix-ui/react-tooltip';
import { STRING_KEYS } from '@/constants/localization';
import { tooltipStrings } from '@/constants/tooltips'; import { tooltipStrings } from '@/constants/tooltips';
import { useStringGetter } from '@/hooks'; import { useStringGetter, useURLConfigs } from '@/hooks';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
import { Link } from '@/components/Link';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { popoverMixins } from '@/styles/popoverMixins'; import { popoverMixins } from '@/styles/popoverMixins';
@@ -38,20 +40,24 @@ export const WithTooltip = ({
slotTooltip, slotTooltip,
}: ElementProps & StyleProps) => { }: ElementProps & StyleProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const urlConfigs = useURLConfigs();
const getTooltipStrings = tooltip && tooltipStrings[tooltip]; const getTooltipStrings = tooltip && tooltipStrings[tooltip];
if (!getTooltipStrings && !tooltipString && !slotTooltip) return <>{children}</>; if (!getTooltipStrings && !tooltipString && !slotTooltip) return <>{children}</>;
let tooltipTitle; let tooltipTitle;
let tooltipBody; let tooltipBody;
let tooltipLearnMore;
if (getTooltipStrings) { if (getTooltipStrings) {
const { title, body } = getTooltipStrings({ const { title, body, learnMoreLink } = getTooltipStrings({
stringGetter, stringGetter,
stringParams, stringParams,
urlConfigs,
}); });
tooltipTitle = title; tooltipTitle = title;
tooltipBody = body; tooltipBody = body;
tooltipLearnMore = learnMoreLink;
} else { } else {
tooltipBody = tooltipString; tooltipBody = tooltipString;
} }
@@ -72,6 +78,13 @@ export const WithTooltip = ({
<dl> <dl>
{tooltipTitle && <dt>{tooltipTitle}</dt>} {tooltipTitle && <dt>{tooltipTitle}</dt>}
{tooltipBody && <dd>{tooltipBody}</dd>} {tooltipBody && <dd>{tooltipBody}</dd>}
{tooltipLearnMore && (
<dd>
<Styled.LearnMore href={tooltipLearnMore}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Styled.LearnMore>
</dd>
)}
<Styled.Arrow /> <Styled.Arrow />
</dl> </dl>
)} )}
@@ -97,12 +110,7 @@ Styled.Abbr = styled.abbr`
Styled.Content = styled(Content)` Styled.Content = styled(Content)`
--tooltip-backgroundColor: var(--color-layer-4); --tooltip-backgroundColor: var(--color-layer-4);
--tooltip-backgroundColor: hsl( --tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground};
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) + 4%),
0.66
);
${popoverMixins.popover} ${popoverMixins.popover}
--popover-backgroundColor: var(--tooltip-backgroundColor); --popover-backgroundColor: var(--tooltip-backgroundColor);
@@ -142,3 +150,7 @@ Styled.Arrow = styled(Arrow)`
Styled.Icon = styled(Icon)` Styled.Icon = styled(Icon)`
color: var(--color-text-0); color: var(--color-text-0);
`; `;
Styled.LearnMore = styled(Link)`
--link-color: var(--color-accent);
`;
+1 -6
View File
@@ -27,12 +27,7 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
${popoverMixins.popover} ${popoverMixins.popover}
--popover-radius: 0.5rem; --popover-radius: 0.5rem;
--popover-background-color: hsl( --popover-background-color: ${({ theme }) => theme.popoverBackground};
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness)),
0.9
);
--popover-backdrop-filter: saturate(120%) blur(12px); --popover-backdrop-filter: saturate(120%) blur(12px);
display: grid; display: grid;
@@ -1,26 +1,33 @@
import React, { useContext, useState } from 'react'; import React, { PropsWithChildren, useContext, useState } from 'react';
import { Point } from '@visx/point'; import { Point } from '@visx/point';
import { localPoint } from '@visx/event'; import { localPoint } from '@visx/event';
import { XYChart, DataContext } from '@visx/xychart'; import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth'; import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
export const XYChartWithPointerEvents = ({ export const XYChartWithPointerEvents = ({
onPointerMove, onPointerUp, onPointerPressedChange, ...props onPointerMove,
onPointerUp,
onPointerPressedChange,
...props
}: { }: {
onPointerMove?: (point: Point) => void; onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
onPointerUp?: (point: Point) => void; onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
onPointerPressedChange?: (isPointerPressed: boolean) => void; onPointerPressedChange?: (isPointerPressed: boolean) => void;
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => { } & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
const { xScale, yScale } = useContext(DataContext); const { xScale, yScale } = useContext(DataContext);
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>(); const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null; const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
const pointerChartPosition = xScale && yScale && pointerContainerPosition && const pointerChartPosition =
xScale &&
yScale &&
pointerContainerPosition &&
new Point({ new Point({
// @ts-expect-error invert supposedly doesn't exist on AxisScale
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2), x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
// @ts-expect-error invert supposedly doesn't exist on AxisScale
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2), y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
}); });
@@ -29,15 +36,13 @@ export const XYChartWithPointerEvents = ({
{...props} {...props}
onPointerMove={({ event }) => { onPointerMove={({ event }) => {
setLastPointerMoveEvent(event as React.PointerEvent); setLastPointerMoveEvent(event as React.PointerEvent);
if (pointerChartPosition) if (pointerChartPosition) onPointerMove?.(pointerChartPosition);
onPointerMove?.(pointerChartPosition);
}} }}
onPointerOut={() => setLastPointerMoveEvent(undefined)} onPointerOut={() => setLastPointerMoveEvent(undefined)}
onPointerDown={() => onPointerPressedChange?.(true)} onPointerDown={() => onPointerPressedChange?.(true)}
onPointerUp={() => { onPointerUp={() => {
onPointerPressedChange?.(false); onPointerPressedChange?.(false);
if (pointerChartPosition) if (pointerChartPosition) onPointerUp?.(pointerChartPosition);
onPointerUp?.(pointerChartPosition);
}} }}
> >
{props.children} {props.children}
+37 -4
View File
@@ -124,6 +124,8 @@ export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPo
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder; export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus; export type OrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus; export const AbacusOrderStatus = Abacus.exchange.dydx.abacus.output.input.OrderStatus;
const abacusOrderStatuses = [...AbacusOrderStatus.values()] as const;
export type AbacusOrderStatuses = (typeof abacusOrderStatuses)[number];
export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[]; export type SubaccountFills = Abacus.exchange.dydx.abacus.output.SubaccountFill[];
export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill; export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment; export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
@@ -140,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number]; export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
// ------ Transfer Items ------ // // ------ Transfer Items ------ //
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField; export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
const transferInputFields = [...TransferInputField.values()] as const; const transferInputFields = [...TransferInputField.values()] as const;
export type TransferInputFields = (typeof transferInputFields)[number]; export type TransferInputFields = (typeof transferInputFields)[number];
@@ -149,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
export type TransferTypes = (typeof transferTypes)[number]; export type TransferTypes = (typeof transferTypes)[number];
// ------ Trade Items ------ // // ------ Trade Items ------ //
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField; export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
const tradeInputFields = [...TradeInputField.values()] as const; const tradeInputFields = [...TradeInputField.values()] as const;
export type TradeInputFields = (typeof tradeInputFields)[number]; export type TradeInputFields = (typeof tradeInputFields)[number];
@@ -160,7 +162,7 @@ export type TradeState<T> = {
}; };
export const ClosePositionInputField = export const ClosePositionInputField =
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField; Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
const closePositionInputFields = [...ClosePositionInputField.values()] as const; const closePositionInputFields = [...ClosePositionInputField.values()] as const;
export type ClosePositionInputFields = (typeof closePositionInputFields)[number]; export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
@@ -209,11 +211,13 @@ type ReadonlyKeysOf<T> = {
[K in keyof T]: IfEquals<{ [_ in K]: T[K] }, { readonly [_ in K]: T[K] }, K, never>; [K in keyof T]: IfEquals<{ [_ in K]: T[K] }, { readonly [_ in K]: T[K] }, K, never>;
}[keyof T]; }[keyof T];
type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>; export type KotlinIrEnumValues<T> = Exclude<ReadonlyKeysOf<T>, 'Companion' | '$serializer'>;
export const ORDER_SIDES: Record<KotlinIrEnumValues<typeof AbacusOrderSide>, OrderSide> = { export const ORDER_SIDES: Record<KotlinIrEnumValues<typeof AbacusOrderSide>, OrderSide> = {
[AbacusOrderSide.buy.name]: OrderSide.BUY, [AbacusOrderSide.buy.name]: OrderSide.BUY,
[AbacusOrderSide.sell.name]: OrderSide.SELL, [AbacusOrderSide.sell.name]: OrderSide.SELL,
[AbacusOrderSide.buy.rawValue]: OrderSide.BUY,
[AbacusOrderSide.sell.rawValue]: OrderSide.SELL,
}; };
export const POSITION_SIDES: Record<KotlinIrEnumValues<typeof AbacusPositionSide>, PositionSide> = { export const POSITION_SIDES: Record<KotlinIrEnumValues<typeof AbacusPositionSide>, PositionSide> = {
@@ -234,12 +238,25 @@ export const HISTORICAL_PNL_PERIODS: Record<
export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = { export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderStatus>, string> = {
[AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS, [AbacusOrderStatus.open.name]: STRING_KEYS.OPEN_STATUS,
[AbacusOrderStatus.open.rawValue]: STRING_KEYS.OPEN_STATUS,
[AbacusOrderStatus.partiallyFilled.name]: STRING_KEYS.PARTIALLY_FILLED, [AbacusOrderStatus.partiallyFilled.name]: STRING_KEYS.PARTIALLY_FILLED,
[AbacusOrderStatus.partiallyFilled.rawValue]: STRING_KEYS.PARTIALLY_FILLED,
[AbacusOrderStatus.filled.name]: STRING_KEYS.ORDER_FILLED, [AbacusOrderStatus.filled.name]: STRING_KEYS.ORDER_FILLED,
[AbacusOrderStatus.filled.rawValue]: STRING_KEYS.ORDER_FILLED,
[AbacusOrderStatus.cancelled.name]: STRING_KEYS.CANCELED, [AbacusOrderStatus.cancelled.name]: STRING_KEYS.CANCELED,
[AbacusOrderStatus.cancelled.rawValue]: STRING_KEYS.CANCELED,
[AbacusOrderStatus.canceling.name]: STRING_KEYS.CANCELING, [AbacusOrderStatus.canceling.name]: STRING_KEYS.CANCELING,
[AbacusOrderStatus.canceling.rawValue]: STRING_KEYS.CANCELING,
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING, [AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
[AbacusOrderStatus.pending.rawValue]: STRING_KEYS.PENDING,
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED, [AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
[AbacusOrderStatus.untriggered.rawValue]: STRING_KEYS.UNTRIGGERED,
}; };
export const TRADE_TYPES: Record< export const TRADE_TYPES: Record<
@@ -247,15 +264,31 @@ export const TRADE_TYPES: Record<
Nullable<TradeTypes> Nullable<TradeTypes>
> = { > = {
[AbacusOrderType.limit.name]: TradeTypes.LIMIT, [AbacusOrderType.limit.name]: TradeTypes.LIMIT,
[AbacusOrderType.limit.rawValue]: TradeTypes.LIMIT,
[AbacusOrderType.market.name]: TradeTypes.MARKET, [AbacusOrderType.market.name]: TradeTypes.MARKET,
[AbacusOrderType.market.rawValue]: TradeTypes.MARKET,
[AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT, [AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT,
[AbacusOrderType.stopLimit.rawValue]: TradeTypes.STOP_LIMIT,
[AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET, [AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET,
[AbacusOrderType.stopMarket.rawValue]: TradeTypes.STOP_MARKET,
[AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT, [AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT,
[AbacusOrderType.takeProfitLimit.rawValue]: TradeTypes.TAKE_PROFIT,
[AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET, [AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET,
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
[AbacusOrderType.liquidated.name]: null, [AbacusOrderType.liquidated.name]: null,
[AbacusOrderType.liquidation.name]: null, [AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidation.rawValue]: null,
[AbacusOrderType.trailingStop.name]: null, [AbacusOrderType.trailingStop.name]: null,
[AbacusOrderType.trailingStop.rawValue]: null,
}; };
// Custom types involving Abacus // Custom types involving Abacus
+1
View File
@@ -96,6 +96,7 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
elapsedTime?: number; elapsedTime?: number;
blockHeight?: number; blockHeight?: number;
indexerBlockHeight?: number; indexerBlockHeight?: number;
trailingBlocks?: number;
} }
: // Navigation : // Navigation
T extends AnalyticsEvent.NavigatePage T extends AnalyticsEvent.NavigatePage
+40
View File
@@ -0,0 +1,40 @@
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { FundingDirection } from './markets';
// ------ Depth Chart ------ //
export enum DepthChartSeries {
Asks = 'Asks',
Bids = 'Bids',
MidMarket = 'MidMarket',
}
export type DepthChartDatum = {
size: number;
price: number;
depth: number;
seriesKey: DepthChartSeries;
};
export type DepthChartPoint = {
side: OrderSide;
price: number;
size: number;
};
export const SERIES_KEY_FOR_ORDER_SIDE = {
[OrderSide.BUY]: DepthChartSeries.Bids,
[OrderSide.SELL]: DepthChartSeries.Asks,
};
// ------ Funding Chart ------ //
export enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
export type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
+3 -1
View File
@@ -3,6 +3,7 @@ export enum DialogTypes {
Deposit = 'Deposit', Deposit = 'Deposit',
DisconnectWallet = 'DisconnectWallet', DisconnectWallet = 'DisconnectWallet',
ExchangeOffline = 'ExchangeOffline', ExchangeOffline = 'ExchangeOffline',
ExternalLink = 'ExternalLink',
FillDetails = 'FillDetails', FillDetails = 'FillDetails',
Help = 'Help', Help = 'Help',
ExternalNavKeplr = 'ExternalNavKeplr', ExternalNavKeplr = 'ExternalNavKeplr',
@@ -10,13 +11,14 @@ export enum DialogTypes {
MobileSignIn = 'MobileSignIn', MobileSignIn = 'MobileSignIn',
Onboarding = 'Onboarding', Onboarding = 'Onboarding',
OrderDetails = 'OrderDetails', OrderDetails = 'OrderDetails',
Preferences = 'Preferences',
RateLimit = 'RateLimit', RateLimit = 'RateLimit',
Receive = 'Receive',
RestrictedGeo = 'RestrictedGeo', RestrictedGeo = 'RestrictedGeo',
RestrictedWallet = 'RestrictedWallet', RestrictedWallet = 'RestrictedWallet',
Trade = 'Trade', Trade = 'Trade',
Transfer = 'Transfer', Transfer = 'Transfer',
Withdraw = 'Withdraw', Withdraw = 'Withdraw',
ManageFunds = 'ManageFunds',
} }
export enum TradeBoxDialogTypes { export enum TradeBoxDialogTypes {
+5
View File
@@ -13,6 +13,7 @@ export enum LocalStorageKey {
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled', PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated', PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
TransferNotifications = 'dydx.TransferNotifications', TransferNotifications = 'dydx.TransferNotifications',
NotificationPreferences = 'dydx.NotificationPreferences',
// UI State // UI State
LastViewedMarket = 'dydx.LastViewedMarket', LastViewedMarket = 'dydx.LastViewedMarket',
@@ -21,9 +22,13 @@ export enum LocalStorageKey {
SelectedTheme = 'dydx.SelectedTheme', SelectedTheme = 'dydx.SelectedTheme',
SelectedTradeLayout = 'dydx.SelectedTradeLayout', SelectedTradeLayout = 'dydx.SelectedTradeLayout',
TradingViewChartConfig = 'dydx.TradingViewChartConfig', TradingViewChartConfig = 'dydx.TradingViewChartConfig',
HasSeenLaunchIncentives = 'dydx.HasSeenLaunchIncentives',
} }
export const LOCAL_STORAGE_VERSIONS = { export const LOCAL_STORAGE_VERSIONS = {
[LocalStorageKey.EvmDerivedAddresses]: 'v2', [LocalStorageKey.EvmDerivedAddresses]: 'v2',
[LocalStorageKey.NotificationPreferences]: 'v1',
[LocalStorageKey.TransferNotifications]: 'v1',
[LocalStorageKey.Notifications]: 'v1',
// TODO: version all localStorage keys // TODO: version all localStorage keys
}; };
+12
View File
@@ -6,8 +6,12 @@ import {
LOCALE_DATA, LOCALE_DATA,
TOOLTIPS, TOOLTIPS,
WARNINGS_STRING_KEYS, WARNINGS_STRING_KEYS,
NOTIFICATIONS_STRING_KEYS,
NOTIFICATIONS,
} from '@dydxprotocol/v4-localization'; } from '@dydxprotocol/v4-localization';
import { type LinksConfigs } from '@/hooks/useURLConfigs';
export { TOOLTIP_STRING_KEYS } from '@dydxprotocol/v4-localization'; export { TOOLTIP_STRING_KEYS } from '@dydxprotocol/v4-localization';
export enum SupportedLocales { export enum SupportedLocales {
@@ -25,6 +29,7 @@ export enum SupportedLocales {
export const EN_LOCALE_DATA = { export const EN_LOCALE_DATA = {
...LOCALE_DATA[SupportedLocales.EN], ...LOCALE_DATA[SupportedLocales.EN],
...NOTIFICATIONS[SupportedLocales.EN],
TOOLTIPS: TOOLTIPS[SupportedLocales.EN], TOOLTIPS: TOOLTIPS[SupportedLocales.EN],
}; };
@@ -32,8 +37,13 @@ export const STRING_KEYS = {
...APP_STRING_KEYS, ...APP_STRING_KEYS,
...ERRORS_STRING_KEYS, ...ERRORS_STRING_KEYS,
...WARNINGS_STRING_KEYS, ...WARNINGS_STRING_KEYS,
...NOTIFICATIONS_STRING_KEYS,
}; };
export const STRING_KEY_VALUES = Object.fromEntries(
Object.values(STRING_KEYS).map((key) => [key, key])
);
export type StringKey = keyof typeof STRING_KEYS; export type StringKey = keyof typeof STRING_KEYS;
export type LocaleData = typeof EN_LOCALE_DATA; export type LocaleData = typeof EN_LOCALE_DATA;
@@ -79,9 +89,11 @@ export type TooltipStrings = {
[key: string]: ({ [key: string]: ({
stringGetter, stringGetter,
stringParams, stringParams,
urlConfigs,
}: { }: {
stringGetter: StringGetterFunction; stringGetter: StringGetterFunction;
stringParams?: any; stringParams?: any;
urlConfigs?: LinksConfigs;
}) => { }) => {
title: string; title: string;
body: string; body: string;
+1
View File
@@ -25,4 +25,5 @@ export const DEFAULT_MARKETID = 'ETH-USD';
export enum FundingDirection { export enum FundingDirection {
ToShort = 'ToShort', ToShort = 'ToShort',
ToLong = 'ToLong', ToLong = 'ToLong',
None = 'None',
} }
+1
View File
@@ -26,6 +26,7 @@ export type MenuItem<MenuItemValue, MenuItemTypes = string> = {
tag?: React.ReactNode; tag?: React.ReactNode;
slotAfter?: React.ReactNode; slotAfter?: React.ReactNode;
description?: string; description?: string;
slotCustomContent?: React.ReactNode;
href?: string; href?: string;
onSelect?: (key: MenuItemValue) => void; onSelect?: (key: MenuItemValue) => void;
+12 -6
View File
@@ -1,12 +1,18 @@
import environments from '../../public/configs/env.json'; import environments from '../../public/configs/env.json';
// TODO: Update for Mainnet deployment const CURRENT_MODE = ({
export const AVAILABLE_ENVIRONMENTS = production: 'MAINNET',
import.meta.env.MODE === 'production' testnet: 'TESTNET',
? environments.deployments.TESTNET staging: 'DEV',
: environments.deployments.DEV; development: 'DEV',
}[import.meta.env.MODE] ?? 'MAINNET') as 'MAINNET' | 'TESTNET' | 'DEV';
export const CURRENT_ABACUS_DEPLOYMENT = import.meta.env.MODE === 'production' ? 'TESTNET' : 'DEV'; export const isMainnet = CURRENT_MODE === 'MAINNET';
export const isTestnet = CURRENT_MODE === 'TESTNET';
export const isDev = CURRENT_MODE === 'DEV';
export const AVAILABLE_ENVIRONMENTS = environments.deployments[CURRENT_MODE];
export const CURRENT_ABACUS_DEPLOYMENT = CURRENT_MODE;
export const ENVIRONMENT_CONFIG_MAP = environments.environments; export const ENVIRONMENT_CONFIG_MAP = environments.environments;
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP; export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork; export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
+33 -7
View File
@@ -2,8 +2,9 @@ import { StatusResponse } from '@0xsquid/sdk';
/** implemented in useNotificationTypes */ /** implemented in useNotificationTypes */
export enum NotificationType { export enum NotificationType {
OrderStatusChanged = 'OrderStatusChanged', AbacusGenerated = 'AbacusGenerated',
SquidTransfer = 'SquidTransfer', SquidTransfer = 'SquidTransfer',
ReleaseUpdates = 'ReleaseUpdates',
} }
export enum NotificationComponentType {} export enum NotificationComponentType {}
@@ -59,7 +60,7 @@ export enum NotificationStatus {
/** Toast or NotificationsMenu item interacted with or dismissed. "Seen" in NotificationsMenu. */ /** Toast or NotificationsMenu item interacted with or dismissed. "Seen" in NotificationsMenu. */
Seen, Seen,
/** Notification marked for deletion. "Archived" in NotificationsMenu. */ /** Notification marked for deletion. Hidden in NotificationsMenu. */
Cleared, Cleared,
} }
@@ -80,14 +81,22 @@ export type Notifications = Record<NotificationId, Notification<any>>;
/** Notification display data derived from app state at runtime. */ /** Notification display data derived from app state at runtime. */
export type NotificationDisplayData = { export type NotificationDisplayData = {
icon?: React.ReactNode; icon?: React.ReactNode;
title: string; // Title for Toast, Notification, and Push Notification
body?: string; // Description body for Toast, Notification, and Push Notification
title?: string; slotTitleLeft?: React.ReactNode;
slotTitleRight?: React.ReactNode;
description?: React.ReactNode; groupKey: string; // Grouping key toast notification stacking
customContent?: React.ReactNode; // Overrides title/body for Notification in NotificationMenu
renderCustomBody?: ({
customMenuContent?: React.ReactNode; isToast,
notification,
}: {
isToast?: boolean;
notification: Notification;
}) => React.ReactNode; // Custom Notification
actionDescription?: string; actionDescription?: string;
@@ -115,12 +124,29 @@ export type NotificationDisplayData = {
toastDuration?: number; toastDuration?: number;
}; };
export enum TransferNotificationTypes {
Withdrawal = 'withdrawal',
Deposit = 'deposit',
}
// Notification types // Notification types
export type TransferNotifcation = { export type TransferNotifcation = {
txHash: string; txHash: string;
type?: TransferNotificationTypes;
toChainId?: string; toChainId?: string;
fromChainId?: string; fromChainId?: string;
toAmount?: number; toAmount?: number;
triggeredAt?: number; triggeredAt?: number;
isCctp?: boolean;
errorCount?: number;
status?: StatusResponse; status?: StatusResponse;
}; };
/**
* @description Struct to store whether a NotificationType should be triggered
*/
export type NotificationPreferences = {
[key in NotificationType]: boolean;
} & { version: string };
export const DEFAULT_TOAST_AUTO_CLOSE_MS = 5000;
+5
View File
@@ -10,6 +10,7 @@ export const LEVERAGE_DECIMALS = 2;
export const TOKEN_DECIMALS = 4; export const TOKEN_DECIMALS = 4;
export const LARGE_TOKEN_DECIMALS = 2; export const LARGE_TOKEN_DECIMALS = 2;
export const FEE_DECIMALS = 3; export const FEE_DECIMALS = 3;
export const FUNDING_DECIMALS = 6;
export const QUANTUM_MULTIPLIER = 1_000_000; export const QUANTUM_MULTIPLIER = 1_000_000;
@@ -18,3 +19,7 @@ export enum NumberSign {
Negative = 'Negative', Negative = 'Negative',
Neutral = 'Neutral', Neutral = 'Neutral',
} }
// Deposit/Withdraw
export const MAX_CCTP_TRANSFER_AMOUNT = 1_000_000;
export const MAX_PRICE_IMPACT = 0.02; // 2%
-4
View File
@@ -37,7 +37,3 @@ export const HISTORY_ROUTE = `${AppRoute.Portfolio}/${PortfolioRoute.History}/:s
export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/${DEFAULT_MARKETID}`; export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/${DEFAULT_MARKETID}`;
export const SETTINGS_ROUTE = `${AppRoute.Settings}/*`; export const SETTINGS_ROUTE = `${AppRoute.Settings}/*`;
export const DEFAULT_DOCUMENT_TITLE = 'dYdX'; export const DEFAULT_DOCUMENT_TITLE = 'dYdX';
export enum ExternalLink {
Foundation = 'https://dydx.foundation',
}
+70
View File
@@ -0,0 +1,70 @@
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',
}
+77
View File
@@ -0,0 +1,77 @@
export type ThemeColors = LayerColors &
BorderColors &
TextColors &
GradientColors &
AccentColors &
StatusColors &
DirectionalColors &
RiskColors &
IconColors &
ComponentColors;
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;
};
type GradientColors = {
gradientBase0: string;
gradientBase1: string;
};
type AccentColors = {
accent: string;
accentFaded: string;
favorite: string;
};
type StatusColors = {
success: string;
warning: string;
error: string;
};
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;
switchThumbActiveBackground: string;
toggleBackground: string;
tooltipBackground: string;
};
+4
View File
@@ -1,6 +1,10 @@
import { type TooltipStrings, TOOLTIP_STRING_KEYS } from '@/constants/localization'; import { type TooltipStrings, TOOLTIP_STRING_KEYS } from '@/constants/localization';
export const depositTooltips: TooltipStrings = { export const depositTooltips: TooltipStrings = {
'minimum-deposit-amount': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_DEPOSIT_AMOUNT_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_DEPOSIT_AMOUNT_BODY }),
}),
swap: ({ stringGetter }) => ({ swap: ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_BODY }), body: stringGetter({ key: TOOLTIP_STRING_KEYS.SWAP_BODY }),
+4 -4
View File
@@ -57,10 +57,10 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_BODY }), body: stringGetter({ key: TOOLTIP_STRING_KEYS.INDEX_PRICE_BODY }),
}), }),
'initial-margin-fraction': ({ stringGetter }) => ({ 'initial-margin-fraction': ({ stringGetter, urlConfigs }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_BODY }), body: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_MARGIN_FRACTION_BODY }),
learnMoreLink: 'https://help.dydx.exchange/articles/5232637-maximum-position-sizes', learnMoreLink: urlConfigs?.initialMarginFractionLearnMore,
}), }),
'initial-stop': ({ stringGetter }) => ({ 'initial-stop': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_STOP_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.INITIAL_STOP_TITLE }),
@@ -146,10 +146,10 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_BODY }), body: stringGetter({ key: TOOLTIP_STRING_KEYS.REALIZED_PNL_BODY }),
}), }),
'reduce-only': ({ stringGetter }) => ({ 'reduce-only': ({ stringGetter, urlConfigs }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_BODY }), body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_BODY }),
learnMoreLink: 'https://help.dydx.exchange/articles/6345793-reduce-only-orders', learnMoreLink: urlConfigs?.reduceOnlyLearnMore,
}), }),
spread: () => ({ spread: () => ({
title: 'Spread', title: 'Spread',
+4
View File
@@ -5,6 +5,10 @@ export const withdrawTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_BODY }), body: stringGetter({ key: TOOLTIP_STRING_KEYS.FAST_WITHDRAW_FEE_BODY }),
}), }),
'minimum-amount-received': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_AMOUNT_RECEIVED_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MINIMUM_AMOUNT_RECEIVED_BODY }),
}),
'withdraw-types': ({ stringGetter }) => ({ 'withdraw-types': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_TITLE }), title: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_BODY }), body: stringGetter({ key: TOOLTIP_STRING_KEYS.WITHDRAW_TYPES_BODY }),
+12
View File
@@ -116,3 +116,15 @@ export enum MobilePlaceOrderSteps {
PlacingOrder = 'PlacingOrder', PlacingOrder = 'PlacingOrder',
Confirmation = 'Confirmation', Confirmation = 'Confirmation',
} }
export const CLEARED_TRADE_INPUTS = {
limitPriceInput: '',
triggerPriceInput: '',
trailingPercentInput: '',
};
export const CLEARED_SIZE_INPUTS = {
amountInput: '',
usdAmountInput: '',
leverageInput: '',
};
+4
View File
@@ -22,6 +22,8 @@ import { useShouldShowFooter } from './useShouldShowFooter';
import { useSelectedNetwork } from './useSelectedNetwork'; import { useSelectedNetwork } from './useSelectedNetwork';
import { useStringGetter } from './useStringGetter'; import { useStringGetter } from './useStringGetter';
import { useSubaccount } from './useSubaccount'; import { useSubaccount } from './useSubaccount';
import { useTradeFormInputs } from './useTradeFormInputs';
import { useURLConfigs } from './useURLConfigs';
export { export {
useApiState, useApiState,
@@ -48,4 +50,6 @@ export {
useSelectedNetwork, useSelectedNetwork,
useStringGetter, useStringGetter,
useSubaccount, useSubaccount,
useTradeFormInputs,
useURLConfigs,
}; };
+2 -2
View File
@@ -44,7 +44,7 @@ export const useTradingView = ({
const hasMarkets = marketIds.length > 0; const hasMarkets = marketIds.length > 0;
useEffect(() => { useEffect(() => {
if (hasMarkets && isClientConnected) { if (hasMarkets && isClientConnected && marketId) {
const widgetOptions = getWidgetOptions(); const widgetOptions = getWidgetOptions();
const widgetOverrides = getWidgetOverrides(appTheme); const widgetOverrides = getWidgetOverrides(appTheme);
const options = { const options = {
@@ -75,7 +75,7 @@ export const useTradingView = ({
tvWidgetRef.current = null; tvWidgetRef.current = null;
setIsChartReady(false); setIsChartReady(false);
}; };
}, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork]); }, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork, !!marketId]);
return { savedResolution }; return { savedResolution };
}; };
+1 -2
View File
@@ -4,7 +4,6 @@ import { useSelector } from 'react-redux';
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library'; import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
import { AppTheme } from '@/state/configs'; import { AppTheme } from '@/state/configs';
import { getAppTheme } from '@/state/configsSelectors'; import { getAppTheme } from '@/state/configsSelectors';
import { getWidgetOverrides } from '@/lib/tradingView/utils'; import { getWidgetOverrides } from '@/lib/tradingView/utils';
@@ -29,7 +28,7 @@ export const useTradingViewTheme = ({
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null; tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
isWidgetReady?: boolean; isWidgetReady?: boolean;
}) => { }) => {
const appTheme = useSelector(getAppTheme); const appTheme: AppTheme = useSelector(getAppTheme);
useEffect(() => { useEffect(() => {
if (tvWidget && isWidgetReady) { if (tvWidget && isWidgetReady) {
+1 -4
View File
@@ -6,7 +6,6 @@ import { useQuery } from 'react-query';
import { formatUnits } from 'viem'; import { formatUnits } from 'viem';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { EvmAddress } from '@/constants/wallets'; import { EvmAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils'; import { convertBech32Address } from '@/lib/addressUtils';
@@ -21,7 +20,6 @@ import { useTokenConfigs } from './useTokenConfigs';
type UseAccountBalanceProps = { type UseAccountBalanceProps = {
// Token Items // Token Items
addressOrDenom?: string; addressOrDenom?: string;
assetSymbol?: string;
decimals?: number; decimals?: number;
// Chain Items // Chain Items
@@ -40,7 +38,6 @@ export const CHAIN_DEFAULT_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeee
export const useAccountBalance = ({ export const useAccountBalance = ({
addressOrDenom, addressOrDenom,
assetSymbol,
bech32AddrPrefix, bech32AddrPrefix,
chainId, chainId,
decimals = 0, decimals = 0,
@@ -91,7 +88,7 @@ export const useAccountBalance = ({
}); });
const { formatted: evmBalance } = evmQuery.data || {}; const { formatted: evmBalance } = evmQuery.data || {};
const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance; const balance = isCosmosChain ? cosmosQuery.data : evmBalance;
const nativeTokenCoinBalance = balances?.[chainTokenDenom]; const nativeTokenCoinBalance = balances?.[chainTokenDenom];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount); const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
+15 -3
View File
@@ -2,7 +2,7 @@ import { useCallback, useContext, createContext, useEffect, useState, useMemo }
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { AES, enc } from 'crypto-js'; import { AES, enc } from 'crypto-js';
import { LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js'; import { NOBLE_BECH32_PREFIX, LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account'; import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
@@ -42,6 +42,7 @@ const useAccountsContext = () => {
selectedWalletError, selectedWalletError,
evmAddress, evmAddress,
signerWagmi, signerWagmi,
publicClientWagmi,
dydxAddress: connectedDydxAddress, dydxAddress: connectedDydxAddress,
signerGraz, signerGraz,
} = useWalletConnection(); } = useWalletConnection();
@@ -135,8 +136,8 @@ const useAccountsContext = () => {
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => { getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
try { try {
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress); const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
setDydxSubaccounts(response.subaccounts); setDydxSubaccounts(response?.subaccounts);
return response.subaccounts; return response?.subaccounts ?? [];
} catch (error) { } catch (error) {
// 404 is expected if the user has no subaccounts // 404 is expected if the user has no subaccounts
if (error.status === 404) { if (error.status === 404) {
@@ -218,6 +219,16 @@ const useAccountsContext = () => {
else abacusStateManager.attemptDisconnectAccount(); else abacusStateManager.attemptDisconnectAccount();
}, [localDydxWallet]); }, [localDydxWallet]);
useEffect(() => {
const setNobleWallet = async () => {
if (hdKey?.mnemonic) {
const nobleWallet = await LocalWallet.fromMnemonic(hdKey.mnemonic, NOBLE_BECH32_PREFIX);
abacusStateManager.setNobleWallet(nobleWallet);
}
};
setNobleWallet();
}, [hdKey?.mnemonic]);
// clear subaccounts when no dydxAddress is set // clear subaccounts when no dydxAddress is set
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -296,6 +307,7 @@ const useAccountsContext = () => {
// Wallet connection (EVM) // Wallet connection (EVM)
evmAddress, evmAddress,
signerWagmi, signerWagmi,
publicClientWagmi,
// Wallet connection (Cosmos) // Wallet connection (Cosmos)
signerGraz, signerGraz,
+2 -1
View File
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
}, []); }, []);
// AnalyticsEvent.NetworkStatus // AnalyticsEvent.NetworkStatus
const { height, indexerHeight, status } = useApiState(); const { height, indexerHeight, status, trailingBlocks} = useApiState();
useEffect(() => { useEffect(() => {
if (status) { if (status) {
@@ -114,6 +114,7 @@ export const useAnalytics = () => {
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery, elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
blockHeight: height ?? undefined, blockHeight: height ?? undefined,
indexerBlockHeight: indexerHeight ?? undefined, indexerBlockHeight: indexerHeight ?? undefined,
trailingBlocks: trailingBlocks ?? undefined
}); });
} }
}, [status]); }, [status]);
+1 -1
View File
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
export const useApiState = () => { export const useApiState = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const apiState = useSelector(getApiState, shallowEqual); const apiState = useSelector(getApiState, shallowEqual);
const { haltedBlock, height, status, trailingBlocks } = apiState ?? {}; const { haltedBlock, height, status, trailingBlocks} = apiState ?? {};
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter }); const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
const indexerHeight = getIndexerHeight(apiState); const indexerHeight = getIndexerHeight(apiState);
+16
View File
@@ -0,0 +1,16 @@
import { useSelector } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { AppTheme } from '@/state/configs';
import { getAppTheme } from '@/state/configsSelectors';
import { Themes } from '@/styles/themes';
export const AppThemeProvider = ({ ...props }) => {
return <ThemeProvider theme={useAppThemeContext()} {...props} />
};
export const useAppThemeContext = () => {
const theme: AppTheme = useSelector(getAppTheme);
return Themes[theme];
}
+31 -12
View File
@@ -5,16 +5,15 @@ import { useMatch, useNavigate } from 'react-router-dom';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_MARKETID } from '@/constants/markets'; import { DEFAULT_MARKETID } from '@/constants/markets';
import { AppRoute } from '@/constants/routes'; import { AppRoute } from '@/constants/routes';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { getSelectedNetwork } from '@/state/appSelectors'; import { getSelectedNetwork } from '@/state/appSelectors';
import { closeDialogInTradeBox } from '@/state/dialogs'; import { closeDialogInTradeBox } from '@/state/dialogs';
import { setCurrentMarketId } from '@/state/perpetuals'; import { setCurrentMarketId } from '@/state/perpetuals';
import { getMarketIds } from '@/state/perpetualsSelectors';
import abacusStateManager from '@/lib/abacus'; import abacusStateManager from '@/lib/abacus';
import { useLocalStorage } from './useLocalStorage';
import { getMarketIds } from '@/state/perpetualsSelectors';
export const useCurrentMarketId = () => { export const useCurrentMarketId = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const match = useMatch(`/${AppRoute.Trade}/:marketId`); const match = useMatch(`/${AppRoute.Trade}/:marketId`);
@@ -22,6 +21,8 @@ export const useCurrentMarketId = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const marketIds = useSelector(getMarketIds, shallowEqual); const marketIds = useSelector(getMarketIds, shallowEqual);
const hasMarketIds = marketIds.length > 0;
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({ const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
key: LocalStorageKey.LastViewedMarket, key: LocalStorageKey.LastViewedMarket,
defaultValue: DEFAULT_MARKETID, defaultValue: DEFAULT_MARKETID,
@@ -31,22 +32,40 @@ export const useCurrentMarketId = () => {
if (marketIds.length === 0) return marketId ?? lastViewedMarket; if (marketIds.length === 0) return marketId ?? lastViewedMarket;
if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID; if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID;
return marketId ?? lastViewedMarket; return marketId ?? lastViewedMarket;
}, [marketIds, marketId]); }, [hasMarketIds, marketId]);
useEffect(() => { useEffect(() => {
setLastViewedMarket(validId); // If v4_markets has not been subscribed to yet or marketId is not specified, default to validId
dispatch(setCurrentMarketId(validId)); if (!hasMarketIds || !marketId) {
dispatch(closeDialogInTradeBox()); setLastViewedMarket(validId);
dispatch(setCurrentMarketId(validId));
dispatch(closeDialogInTradeBox());
navigate(`${AppRoute.Trade}/${validId}`, { if (validId !== marketId) {
replace: true, navigate(`${AppRoute.Trade}/${validId}`, {
}); replace: true,
}, [validId]); });
}
} else {
// If v4_markets has been subscribed to, check if marketId is valid
if (!marketIds.includes(marketId)) {
// If marketId is not valid (i.e. final settlement), navigate to markets page
navigate(AppRoute.Markets, {
replace: true,
});
} else {
// If marketId is valid, set currentMarketId
setLastViewedMarket(marketId);
dispatch(setCurrentMarketId(marketId));
dispatch(closeDialogInTradeBox());
}
}
}, [hasMarketIds, marketId]);
useEffect(() => { useEffect(() => {
// Check for marketIds otherwise Abacus will silently fail its isMarketValid check // Check for marketIds otherwise Abacus will silently fail its isMarketValid check
if (marketIds) { if (marketIds) {
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID); abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
} }
}, [selectedNetwork, marketIds, marketId]); }, [selectedNetwork, hasMarketIds, marketId]);
}; };
+2 -1
View File
@@ -1,3 +1,4 @@
import { isDev } from '@/constants/networks';
import { WalletType } from '@/constants/wallets'; import { WalletType } from '@/constants/wallets';
import { isTruthy } from '@/lib/isTruthy'; import { isTruthy } from '@/lib/isTruthy';
@@ -6,7 +7,7 @@ export const useDisplayedWallets = () => {
return [ return [
WalletType.MetaMask, WalletType.MetaMask,
import.meta.env.MODE !== 'production' && WalletType.Keplr, isDev && WalletType.Keplr,
WalletType.WalletConnect2, WalletType.WalletConnect2,
+74 -42
View File
@@ -1,12 +1,12 @@
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react'; import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import type { StatusResponse } from '@0xsquid/sdk';
import { LocalStorageKey } from '@/constants/localStorage'; import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import { type TransferNotifcation } from '@/constants/notifications'; import { type TransferNotifcation } from '@/constants/notifications';
import { useAccounts } from '@/hooks/useAccounts'; import { useAccounts } from '@/hooks/useAccounts';
import { STATUS_ERROR_GRACE_PERIOD, useSquid } from '@/hooks/useSquid';
import { fetchSquidStatus, STATUS_ERROR_GRACE_PERIOD } from '@/lib/squid';
import { useLocalStorage } from './useLocalStorage'; import { useLocalStorage } from './useLocalStorage';
const LocalNotificationsContext = createContext< const LocalNotificationsContext = createContext<
@@ -22,16 +22,32 @@ export const LocalNotificationsProvider = ({ ...props }) => (
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!; export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000; const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
const ERROR_COUNT_THRESHOLD = 3;
const useLocalNotificationsContext = () => { const useLocalNotificationsContext = () => {
// transfer notifications // transfer notifications
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{ const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
[key: `dydx${string}`]: TransferNotifcation[]; [key: `dydx${string}`]: TransferNotifcation[];
version: string;
}>({ }>({
key: LocalStorageKey.TransferNotifications, key: LocalStorageKey.TransferNotifications,
defaultValue: {}, defaultValue: {
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
},
}); });
// Ensure version matches, otherwise wipe
useEffect(() => {
if (
allTransferNotifications?.version !==
LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications]
) {
setAllTransferNotifications({
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.TransferNotifications],
});
}
}, [allTransferNotifications]);
const { dydxAddress } = useAccounts(); const { dydxAddress } = useAccounts();
const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : []; const transferNotifications = dydxAddress ? allTransferNotifications[dydxAddress] || [] : [];
@@ -52,49 +68,65 @@ const useLocalNotificationsContext = () => {
[transferNotifications] [transferNotifications]
); );
const squid = useSquid(); useQuery({
queryKey: 'getTransactionStatus',
const { data: transferStatuses } = useQuery({
queryKey: ['getTransactionStatus', transferNotifications],
queryFn: async () => { queryFn: async () => {
const statuses: { [key: string]: StatusResponse } = {}; const processTransferNotifications = async (transferNotifications: TransferNotifcation[]) => {
for (const { const newTransferNotifications = await Promise.all(
txHash, transferNotifications.map(async (transferNotification) => {
toChainId, const {
fromChainId, txHash,
triggeredAt, toChainId,
status: currentStatus, fromChainId,
} of transferNotifications) { triggeredAt,
try { isCctp,
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue; errorCount,
status: currentStatus,
} = transferNotification;
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId }); // @ts-ignore status.errors is not in the type definition but can be returned
if (status) statuses[txHash] = status; // also error can some time come back as an empty object so we need to ignore for that
} catch (error) { const hasErrors = !!currentStatus?.errors ||
// ignore errors for the first 120s since the status might not be available yet (currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
statuses[txHash] = error; if (
} !hasErrors &&
} (!currentStatus?.squidTransactionStatus ||
} currentStatus?.squidTransactionStatus === 'ongoing')
return statuses; ) {
try {
const status = await fetchSquidStatus({
transactionId: txHash,
toChainId,
fromChainId,
}, isCctp);
if (status) {
transferNotification.status = status;
}
} catch (error) {
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
if (errorCount && errorCount > ERROR_COUNT_THRESHOLD) {
transferNotification.status = error;
} else {
transferNotification.errorCount = errorCount ? errorCount + 1 : 1;
}
}
}
}
return transferNotification;
})
);
return newTransferNotifications;
};
const newTransferNotifications = await processTransferNotifications(transferNotifications);
setTransferNotifications(newTransferNotifications);
}, },
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL, refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
}); });
useEffect(() => {
if (!transferStatuses) return;
const newTransferNotifications = transferNotifications.map((notification) => {
const status = transferStatuses[notification.txHash];
if (!status) return notification;
return {
...notification,
status,
};
});
setTransferNotifications(newTransferNotifications);
}, [transferStatuses]);
return { return {
transferNotifications, transferNotifications,
addTransferNotification, addTransferNotification,
+12 -8
View File
@@ -6,8 +6,10 @@ import { MarketFilters, MARKET_FILTER_LABELS, type MarketData } from '@/constant
import { getAssets } from '@/state/assetsSelectors'; import { getAssets } from '@/state/assetsSelectors';
import { getPerpetualMarkets } from '@/state/perpetualsSelectors'; import { getPerpetualMarkets } from '@/state/perpetualsSelectors';
import { isTruthy } from '@/lib/isTruthy';
const filterFunctions = { const filterFunctions = {
[MarketFilters.ALL]: (market: MarketData) => true, [MarketFilters.ALL]: () => true,
[MarketFilters.LAYER_1]: (market: MarketData) => { [MarketFilters.LAYER_1]: (market: MarketData) => {
return market.asset.tags?.toArray().includes('Layer 1'); return market.asset.tags?.toArray().includes('Layer 1');
}, },
@@ -28,13 +30,15 @@ export const useMarketsData = (
const allAssets = useSelector(getAssets, shallowEqual) || {}; const allAssets = useSelector(getAssets, shallowEqual) || {};
const markets = useMemo(() => { const markets = useMemo(() => {
return Object.values(allPerpetualMarkets).map((marketData) => ({ return Object.values(allPerpetualMarkets)
asset: allAssets[marketData.assetId], .filter(isTruthy)
tickSizeDecimals: marketData.configs?.tickSizeDecimals, .map((marketData) => ({
...marketData, asset: allAssets[marketData.assetId],
...marketData.perpetual, tickSizeDecimals: marketData.configs?.tickSizeDecimals,
...marketData.configs, ...marketData,
})) as MarketData[]; ...marketData.perpetual,
...marketData.configs,
})) as MarketData[];
}, [allPerpetualMarkets, allAssets]); }, [allPerpetualMarkets, allAssets]);
const filteredMarkets = useMemo(() => { const filteredMarkets = useMemo(() => {
+241 -155
View File
@@ -1,205 +1,291 @@
import { useCallback, useEffect, useMemo } from 'react'; import { type ReactNode, useEffect } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components'; import styled from 'styled-components';
import { useSelector, shallowEqual, useDispatch } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { groupBy } from 'lodash'; import { isEqual, groupBy } from 'lodash';
import { useNavigate } from 'react-router-dom';
import { AlertType } from '@/constants/alerts';
import { AbacusOrderStatus, ORDER_SIDES } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS, StringKey } from '@/constants/localization'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications'; import { AppRoute } from '@/constants/routes';
import { ORDER_SIDE_STRINGS } from '@/constants/trade'; import { DydxChainAsset } from '@/constants/wallets';
import {
STRING_KEYS,
STRING_KEY_VALUES,
type StringGetterFunction,
type StringKey,
} from '@/constants/localization';
import {
type NotificationTypeConfig,
NotificationType,
DEFAULT_TOAST_AUTO_CLOSE_MS,
TransferNotificationTypes,
} from '@/constants/notifications';
import { useSelectedNetwork, useStringGetter } from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications'; import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType } from '@/components/Output'; import { BlockRewardNotification } from '@/views/notifications/BlockRewardNotification';
import { TransferStatusToast } from '@/views/TransferStatus'; import { TradeNotification } from '@/views/notifications/TradeNotification';
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors'; import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs'; import { openDialog } from '@/state/dialogs';
import { getAbacusNotifications } from '@/state/notificationsSelectors';
import { getMarketIds } from '@/state/perpetualsSelectors';
import { OrderStatusIcon } from '@/views/OrderStatusIcon'; import { formatSeconds } from '@/lib/timeUtils';
import { useStringGetter } from './useStringGetter'; const parseStringParamsForNotification = ({
import { TransferStatusSteps } from '@/views/TransferStatusSteps'; stringGetter,
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js'; value,
}: {
stringGetter: StringGetterFunction;
value: unknown;
}): ReactNode => {
if (STRING_KEY_VALUES[value as StringKey]) {
return stringGetter({ key: value as StringKey });
}
export const notificationTypes = [ return value as ReactNode;
};
export const notificationTypes: NotificationTypeConfig[] = [
{ {
type: NotificationType.OrderStatusChanged, type: NotificationType.AbacusGenerated,
useTrigger: ({ trigger }) => {
useTrigger: ({ trigger, lastUpdated }) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const abacusNotifications = useSelector(getAbacusNotifications, isEqual);
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
const orderIds = useMemo(
() => [...Object.keys(ordersByOrderId), ...Object.keys(fillsByOrderId)],
[orders, fills]
);
useEffect(() => { useEffect(() => {
for (const orderId of orderIds) { for (const abacusNotif of abacusNotifications) {
const fills = fillsByOrderId[orderId]; const [abacusNotificationType = '', id = ''] = abacusNotif.id.split(':');
const parsedData = abacusNotif.data ? JSON.parse(abacusNotif.data) : {};
const order = const params = Object.fromEntries(
ordersByOrderId[orderId] ?? Object.entries(parsedData).map(([key, value]) => {
(fills?.length return [key, parseStringParamsForNotification({ stringGetter, value })];
? { })
...fills[fills.length - 1], );
id: orderId,
createdAtMilliseconds: Math.max( switch (abacusNotificationType) {
...fills.map((fill) => fill.createdAtMilliseconds) case 'order': {
trigger(
abacusNotif.id,
{
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
title: stringGetter({ key: abacusNotif.title }),
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
renderCustomBody: ({ isToast, notification }) => (
<TradeNotification
isToast={isToast}
data={parsedData}
notification={notification}
/>
), ),
status: AbacusOrderStatus.filled, },
} [abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
: undefined); true
);
if (order) break;
trigger( }
order.id, case 'blockReward': {
{ trigger(
icon: ( abacusNotif.id,
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} /> {
), icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
title: `${stringGetter({ title: stringGetter({ key: abacusNotif.title }),
key: order.resources.typeStringKey as StringKey, body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
})} ${ toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0 toastSensitivity: 'foreground',
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED }) groupKey: abacusNotificationType,
: stringGetter({ key: order.resources.statusStringKey as StringKey }) renderCustomBody: ({ isToast, notification }) => (
}`, <BlockRewardNotification
description: `${stringGetter({ isToast={isToast}
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]], data={parsedData}
})} ${order.size} ${order.marketId} @ $${order.price}`, notification={notification}
actionDescription: 'View Order', />
actionAltText: 'View this order in the Orders tab or the Notifications menu.', ),
toastSensitivity: },
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background', [abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
toastDuration: 5000, true
}, );
[order.status.name, order.size], break;
!order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated }
); default:
trigger(
abacusNotif.id,
{
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
title: stringGetter({ key: abacusNotif.title }),
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
},
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data]
);
break;
}
} }
}, [orderIds]); }, [abacusNotifications, stringGetter]);
}, },
useNotificationAction: () => {
const dispatch = useDispatch();
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
const ordersById = groupBy(orders, 'id');
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
const fillsById = groupBy(fills, 'id');
const marketIds = useSelector(getMarketIds, shallowEqual);
const navigate = useNavigate();
// useNotificationAction: () => { return (notificationId: string) => {
// const dispatch = useDispatch(); const [abacusNotificationType = '', id = ''] = notificationId.split(':');
// const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
// const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
// const fills = useSelector(getSubaccountFills, shallowEqual) || []; if (ordersById[id]) {
// const fillsByOrderId = groupBy(fills, (fill) => fill.orderId); dispatch(
openDialog({
// return (id) => { type: DialogTypes.OrderDetails,
// if (ordersByOrderId[id]) { dialogProps: { orderId: id },
// dispatch( })
// openDialog({ );
// type: DialogTypes.OrderDetails, } else if (fillsById[id]) {
// dialogProps: { orderId: id }, dispatch(
// }) openDialog({
// ); type: DialogTypes.FillDetails,
// } else if (fillsByOrderId[id]) { dialogProps: { fillId: id },
// const fillId = fillsByOrderId[id][0].id; })
);
// dispatch( } else if (marketIds.includes(id)) {
// openDialog({ navigate(`${AppRoute.Trade}/${id}`, {
// type: DialogTypes.FillDetails, replace: true,
// dialogProps: { fillId }, });
// }) }
// ); };
// } },
// }; },
// },
} as NotificationTypeConfig<string, [string, number]>,
{ {
type: NotificationType.SquidTransfer, type: NotificationType.SquidTransfer,
useTrigger: ({ trigger }) => { useTrigger: ({ trigger }) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { transferNotifications } = useLocalNotifications(); const { transferNotifications } = useLocalNotifications();
const { selectedNetwork } = useSelectedNetwork();
const getTitleStringKey = useCallback((type: 'deposit' | 'withdrawal', finished: boolean) => {
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
if (type === 'withdrawal' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
return STRING_KEYS.WITHDRAW;
}, []);
useEffect(() => { useEffect(() => {
for (const transfer of transferNotifications) { for (const transfer of transferNotifications) {
const { fromChainId, status, txHash, toAmount } = transfer; const { fromChainId, status, txHash, toAmount, type } = transfer;
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing'; const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
const type = fromChainId === TESTNET_CHAIN_ID ? 'withdrawal' : 'deposit'; const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;
// @ts-ignore status.errors is not in the type definition but can be returned
const error = status?.errors?.length ? status?.errors[0] : status?.error;
// TODO: confirm with design what the description should be const transferType =
const description = ( type ?? fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
<div> ? TransferNotificationTypes.Withdrawal
<Styled.TransferText> : TransferNotificationTypes.Deposit;
{type === 'deposit' ? 'Deposit of ' : 'Withdraw of '}
<Output type={OutputType.Fiat} value={toAmount} />
</Styled.TransferText>
{error && ( const title = stringGetter({
<Styled.ErrorMessage type={AlertType.Error}> key: {
{stringGetter({ deposit: isFinished ? STRING_KEYS.DEPOSIT : STRING_KEYS.DEPOSIT_IN_PROGRESS,
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE, withdrawal: isFinished ? STRING_KEYS.WITHDRAW : STRING_KEYS.WITHDRAW_IN_PROGRESS,
params: { }[transferType],
ERROR_MESSAGE: });
error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
}, const toChainEta = status?.toChain?.chainData?.estimatedRouteDuration || 0;
})} const estimatedDuration = formatSeconds(Math.max(toChainEta, 0));
</Styled.ErrorMessage> const body = stringGetter({
)} key: STRING_KEYS.DEPOSIT_STATUS,
</div> params: {
); AMOUNT_USD: `${toAmount} ${DydxChainAsset.USDC.toUpperCase()}`,
ESTIMATED_DURATION: estimatedDuration,
},
});
trigger( trigger(
txHash, txHash,
{ {
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />, icon,
title: stringGetter({ key: getTitleStringKey(type, finished) }), title,
description: description, body,
customContent: ( renderCustomBody: ({ isToast, notification }) => (
<TransferStatusToast <TransferStatusNotification
type={type} isToast={isToast}
toAmount={transfer.toAmount} slotIcon={icon}
slotTitle={title}
transfer={transfer}
type={transferType}
triggeredAt={transfer.triggeredAt} triggeredAt={transfer.triggeredAt}
status={transfer.status} notification={notification}
/> />
), ),
customMenuContent: !finished && (
<div>
{description}
<TransferStatusSteps status={transfer.status} type={type} />
</div>
),
toastSensitivity: 'foreground', toastSensitivity: 'foreground',
groupKey: NotificationType.SquidTransfer,
}, },
[] [isFinished]
); );
} }
}, [transferNotifications]); }, [transferNotifications, stringGetter]);
},
useNotificationAction: () => {
return () => {};
}, },
}, },
] satisfies NotificationTypeConfig[]; {
type: NotificationType.ReleaseUpdates,
useTrigger: ({ trigger }) => {
const stringGetter = useStringGetter();
const Styled: Record<string, AnyStyledComponent> = {}; useEffect(() => {
trigger(
'rewards-and-full-trading-live',
{
icon: <AssetIcon symbol="DYDX" />,
title: stringGetter({ key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.TITLE' }),
body: stringGetter({
key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.BODY',
params: {
DOS_BLOGPOST: (
<$Link
href="https://www.dydxopsdao.com/blog/deep-dive-full-trading"
target="_blank"
rel="noopener noreferrer"
>
{stringGetter({ key: STRING_KEYS.HERE })}
</$Link>
),
TRADING_BLOGPOST: (
<$Link
href="https://dydx.exchange/blog/v4-full-trading"
target="_blank"
rel="noopener noreferrer"
>
{stringGetter({ key: STRING_KEYS.HERE })}
</$Link>
),
},
}),
toastSensitivity: 'foreground',
groupKey: NotificationType.ReleaseUpdates,
},
[]
);
}, [stringGetter]);
},
useNotificationAction: () => {
return () => {};
},
},
];
Styled.TransferText = styled.span` const $Icon = styled.img`
display: inline-flex; height: 1.5rem;
align-items: center; width: 1.5rem;
gap: 0.5ch;
`; `;
Styled.ErrorMessage = styled.div` const $Link = styled.a`
max-width: 13rem; --link-color: var(--color-text-2);
`; `;
+82 -36
View File
@@ -1,11 +1,13 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { LocalStorageKey } from '@/constants/localStorage'; import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import { import {
type Notification, type Notification,
type NotificationDisplayData, type NotificationDisplayData,
type NotificationPreferences,
type Notifications, type Notifications,
NotificationStatus, NotificationStatus,
NotificationType,
} from '@/constants/notifications'; } from '@/constants/notifications';
import { useLocalStorage } from './useLocalStorage'; import { useLocalStorage } from './useLocalStorage';
@@ -13,8 +15,10 @@ import { notificationTypes } from './useNotificationTypes';
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl'; import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
const NotificationsContext = createContext<ReturnType<typeof useNotificationsContext> | undefined>( type NotificationsContextType = ReturnType<typeof useNotificationsContext>;
undefined
const NotificationsContext = createContext<NotificationsContextType>(
{} as NotificationsContextType
); );
NotificationsContext.displayName = 'Notifications'; NotificationsContext.displayName = 'Notifications';
@@ -27,6 +31,7 @@ export const useNotifications = () => useContext(NotificationsContext)!;
const useNotificationsContext = () => { const useNotificationsContext = () => {
// Local storage // Local storage
// const [notifications, setNotifications] = useState<Notifications>({});
const [notifications, setNotifications] = useLocalStorage<Notifications>({ const [notifications, setNotifications] = useLocalStorage<Notifications>({
key: LocalStorageKey.Notifications, key: LocalStorageKey.Notifications,
defaultValue: {}, defaultValue: {},
@@ -37,12 +42,25 @@ const useNotificationsContext = () => {
defaultValue: Date.now(), defaultValue: Date.now(),
}); });
const [notificationPreferences, setNotificationPreferences] =
useLocalStorage<NotificationPreferences>({
key: LocalStorageKey.NotificationPreferences,
defaultValue: {
[NotificationType.AbacusGenerated]: true,
[NotificationType.SquidTransfer]: true,
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
},
});
useEffect(() => { useEffect(() => {
setNotificationsLastUpdated(Date.now()); setNotificationsLastUpdated(Date.now());
}, [notifications]); }, [notifications]);
const getKey = <T extends string | number>(notification: Pick<Notification<T>, 'type' | 'id'>) => const getKey = useCallback(
`${notification.type}/${notification.id}`; <T extends string | number>(notification: Pick<Notification<T>, 'type' | 'id'>) =>
`${notification.type}/${notification.id}`,
[]
);
// Display data // Display data
const [notificationsDisplayData, setNotificationsDisplayData] = useState( const [notificationsDisplayData, setNotificationsDisplayData] = useState(
@@ -54,29 +72,47 @@ const useNotificationsContext = () => {
[notificationsDisplayData] [notificationsDisplayData]
); );
// Check for version changes
useEffect(() => {
if (
notificationPreferences.version !==
LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences]
) {
setNotificationPreferences({
[NotificationType.AbacusGenerated]: true,
[NotificationType.SquidTransfer]: true,
[NotificationType.ReleaseUpdates]: true,
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
});
}
}, []);
// Status changes // Status changes
const updateStatus = useCallback( const updateStatus = useCallback(
(notification: Notification, status: NotificationStatus) => { (notification: Notification, status: NotificationStatus) => {
notification.status = status; notification.status = status;
notification.timestamps[notification.status] = Date.now(); notification.timestamps[notification.status] = Date.now();
setNotifications({ ...notifications }); setNotifications({ ...notifications, [getKey(notification)]: notification });
}, },
[notifications] [notifications, getKey]
); );
const { markUnseen, markSeen, markCleared } = useMemo( const { markUnseen, markSeen, markCleared } = useMemo(
() => ({ () => ({
markUnseen: (notification: Notification) => { markUnseen: (notification: Notification) => {
if (notification.status < NotificationStatus.Unseen) if (notification.status < NotificationStatus.Unseen) {
updateStatus(notification, NotificationStatus.Unseen); updateStatus(notification, NotificationStatus.Unseen);
}
}, },
markSeen: (notification: Notification) => { markSeen: (notification: Notification) => {
if (notification.status < NotificationStatus.Seen) if (notification.status < NotificationStatus.Seen) {
updateStatus(notification, NotificationStatus.Seen); updateStatus(notification, NotificationStatus.Seen);
}
}, },
markCleared: (notification: Notification) => { markCleared: (notification: Notification) => {
if (notification.status < NotificationStatus.Cleared) if (notification.status < NotificationStatus.Cleared) {
updateStatus(notification, NotificationStatus.Cleared); updateStatus(notification, NotificationStatus.Cleared);
}
}, },
}), }),
[updateStatus] [updateStatus]
@@ -97,33 +133,37 @@ const useNotificationsContext = () => {
const notification = notifications[key]; const notification = notifications[key];
// New unique key - create new notification // Filter out notifications that are not enabled
if (!notification) { if (notificationPreferences[type] !== false) {
const notification = (notifications[key] = { // New unique key - create new notification
id, if (!notification) {
type, const notification = (notifications[key] = {
timestamps: {}, id,
updateKey, type,
} as Notification); timestamps: {},
updateKey,
} as Notification);
updateStatus(
notification,
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
);
} else if (JSON.stringify(updateKey) !== JSON.stringify(notification.updateKey)) {
// updateKey changed - update existing notification
updateStatus( const notification = notifications[key];
notification,
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
);
}
// updateKey changed - update existing notification notification.updateKey = updateKey;
else if (JSON.stringify(updateKey) !== JSON.stringify(notification.updateKey)) { updateStatus(notification, NotificationStatus.Updated);
const notification = notifications[key]; }
} else {
notification.updateKey = updateKey; // Notification is disabled - remove it
updateStatus(notification, NotificationStatus.Updated); delete notifications[key];
} }
notificationsDisplayData[key] = displayData; notificationsDisplayData[key] = displayData;
setNotificationsDisplayData({ ...notificationsDisplayData }); setNotificationsDisplayData({ ...notificationsDisplayData });
}, },
[notifications, updateStatus] [notifications, updateStatus, notificationPreferences[type]]
), ),
lastUpdated: notificationsLastUpdated, lastUpdated: notificationsLastUpdated,
@@ -136,10 +176,8 @@ const useNotificationsContext = () => {
) )
); );
const onNotificationAction = useCallback( const onNotificationAction = async (notification: Notification) =>
async (notification: Notification) => await actions[notification.type]?.(notification.id), await actions[notification.type]?.(notification.id);
[actions]
);
// Push notifications // Push notifications
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({ const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
@@ -186,7 +224,7 @@ const useNotificationsContext = () => {
renotify: true, renotify: true,
tag: getKey(notification), tag: getKey(notification),
data: notification, data: notification,
body: displayData.description, description: displayData.body,
icon: iconUrl ?? '/favicon.svg', icon: iconUrl ?? '/favicon.svg',
badge: iconUrl ?? '/favicon.svg', badge: iconUrl ?? '/favicon.svg',
image: iconUrl ?? '/favicon.svg', image: iconUrl ?? '/favicon.svg',
@@ -208,7 +246,7 @@ const useNotificationsContext = () => {
setPushNotificationsLastUpdated(Date.now()); setPushNotificationsLastUpdated(Date.now());
})(); })();
}, [hasEnabledPush, notifications, onNotificationAction, markSeen]); }, [hasEnabledPush, notifications, markSeen]);
// Menu state // Menu state
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -239,5 +277,13 @@ const useNotificationsContext = () => {
// Menu state // Menu state
isMenuOpen, isMenuOpen,
setIsMenuOpen, setIsMenuOpen,
// Notification Preferences
notificationPreferences,
setNotificationPreferences,
getNotificationPreferenceForType: useCallback(
(type: NotificationType) => notificationPreferences[type],
[notificationPreferences]
),
}; };
}; };
+44
View File
@@ -0,0 +1,44 @@
import { useMemo } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { MustBigNumber } from '@/lib/numbers';
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]);
};
-46
View File
@@ -1,46 +0,0 @@
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { Squid } from '@0xsquid/sdk';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { getSelectedNetwork } from '@/state/appSelectors';
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
export const STATUS_ERROR_GRACE_PERIOD = 120_000;
const useSquidContext = () => {
const selectedNetwork = useSelector(getSelectedNetwork);
const [_, setInitialized] = useState(false);
const initializeClient = async () => {
setInitialized(false);
if (!squid) return;
await squid.init();
setInitialized(true);
};
const squid = useMemo(
() => new Squid({ baseUrl: ENVIRONMENT_CONFIG_MAP[selectedNetwork]?.endpoints['0xsquid'] }),
[selectedNetwork]
);
useEffect(() => {
if (squid) {
initializeClient();
}
}, [squid]);
return squid;
};
type SquidContextType = ReturnType<typeof useSquidContext>;
const SquidContext = createContext<SquidContextType | undefined>(undefined);
SquidContext.displayName = '0xSquid';
export const SquidProvider = ({ ...props }) => (
<SquidContext.Provider value={useSquidContext()} {...props} />
);
export const useSquid = () => useContext(SquidContext);
+112 -63
View File
@@ -31,6 +31,7 @@ import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts'; import { useAccounts } from './useAccounts';
import { useTokenConfigs } from './useTokenConfigs'; import { useTokenConfigs } from './useTokenConfigs';
import { useDydxClient } from './useDydxClient'; import { useDydxClient } from './useDydxClient';
import { hashFromTx } from '@/lib/hashfromTx';
type SubaccountContextType = ReturnType<typeof useSubaccountContext>; type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType); const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
@@ -48,7 +49,7 @@ export const useSubaccount = () => useContext(SubaccountContext);
export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWallet }) => { export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWallet }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { usdcDenom } = useTokenConfigs(); const { usdcDenom, usdcDecimals } = useTokenConfigs();
const { compositeClient, faucetClient } = useDydxClient(); const { compositeClient, faucetClient } = useDydxClient();
const { getFaucetFunds } = useMemo( const { getFaucetFunds } = useMemo(
@@ -79,16 +80,34 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
subaccountClient: SubaccountClient; subaccountClient: SubaccountClient;
assetId?: number; assetId?: number;
amount: number; amount: number;
}) => await compositeClient?.depositToSubaccount(subaccountClient, amount.toString()), }) => {
try {
return await compositeClient?.depositToSubaccount(
subaccountClient,
amount.toFixed(usdcDecimals)
);
} catch (error) {
log('useSubaccount/depositToSubaccount', error);
throw error;
}
},
withdrawFromSubaccount: async ({ withdrawFromSubaccount: async ({
subaccountClient, subaccountClient,
amount, amount,
}: { }: {
subaccountClient: SubaccountClient; subaccountClient: SubaccountClient;
amount: number; amount: number;
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount.toString()), }) => {
try {
return await compositeClient?.withdrawFromSubaccount(
subaccountClient,
amount.toFixed(usdcDecimals)
);
} catch (error) {
log('useSubaccount/withdrawFromSubaccount', error);
throw error;
}
},
transferFromSubaccountToAddress: async ({ transferFromSubaccountToAddress: async ({
subaccountClient, subaccountClient,
assetId = 0, assetId = 0,
@@ -99,27 +118,33 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
assetId?: number; assetId?: number;
amount: number; amount: number;
recipient: string; recipient: string;
}) => }) => {
await compositeClient?.validatorClient.post.send( try {
subaccountClient?.wallet, return await compositeClient?.validatorClient.post.send(
() => subaccountClient?.wallet,
new Promise((resolve) => { () =>
const msg = new Promise((resolve) => {
compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount( const msg =
subaccountClient.address, compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(
subaccountClient.subaccountNumber, subaccountClient.address,
assetId, subaccountClient.subaccountNumber,
Long.fromNumber(amount * QUANTUM_MULTIPLIER), assetId,
recipient Long.fromNumber(amount * QUANTUM_MULTIPLIER),
); recipient
);
resolve([msg]); resolve([msg]);
}), }),
false, false,
undefined, undefined,
undefined, undefined,
Method.BroadcastTxCommit Method.BroadcastTxCommit
), );
} catch (error) {
log('useSubaccount/transferFromSubaccountToAddress', error);
throw error;
}
},
transferNativeToken: async ({ transferNativeToken: async ({
subaccountClient, subaccountClient,
@@ -129,24 +154,30 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
subaccountClient: SubaccountClient; subaccountClient: SubaccountClient;
amount: number; amount: number;
recipient: string; recipient: string;
}) => }) => {
await compositeClient?.validatorClient.post.send( try {
subaccountClient.wallet, return await compositeClient?.validatorClient.post.send(
() => subaccountClient.wallet,
new Promise((resolve) => { () =>
const msg = compositeClient?.sendTokenMessage( new Promise((resolve) => {
subaccountClient.wallet, const msg = compositeClient?.sendTokenMessage(
amount.toString(), subaccountClient.wallet,
recipient, amount.toString(),
); recipient
);
resolve([msg]); resolve([msg]);
}), }),
false, false,
compositeClient?.validatorClient?.post.defaultDydxGasPrice, compositeClient?.validatorClient?.post.defaultDydxGasPrice,
undefined, undefined,
Method.BroadcastTxCommit Method.BroadcastTxCommit
), );
} catch (error) {
log('useSubaccount/transferNativeToken', error);
throw error;
}
},
sendSquidWithdrawFromSubaccount: async ({ sendSquidWithdrawFromSubaccount: async ({
subaccountClient, subaccountClient,
@@ -158,20 +189,27 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
payload: string; payload: string;
}) => { }) => {
if (!compositeClient) throw new Error('client not initialized'); if (!compositeClient) throw new Error('client not initialized');
try {
const transaction = JSON.parse(payload);
const transaction = JSON.parse(payload); const msg = compositeClient.withdrawFromSubaccountMessage(
subaccountClient,
amount.toFixed(usdcDecimals)
);
const ibcMsg: EncodeObject = {
typeUrl: transaction.msgTypeUrl,
value: transaction.msg,
};
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount.toString()); return await compositeClient.send(
const ibcMsg: EncodeObject = { subaccountClient.wallet,
typeUrl: transaction.msgTypeUrl, () => Promise.resolve([msg, ibcMsg]),
value: transaction.msg, false
}; );
} catch (error) {
return await compositeClient.send( log('useSubaccount/sendSquidWithdrawFromSubaccount', error);
subaccountClient.wallet, throw error;
() => Promise.resolve([msg, ibcMsg]), }
false
);
}, },
}), }),
[compositeClient] [compositeClient]
@@ -256,12 +294,29 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
); );
const sendSquidWithdraw = useCallback( const sendSquidWithdraw = useCallback(
async (amount: number, payload: string) => { async (amount: number, payload: string, isCctp?: boolean) => {
const cctpWithdraw = () => {
return new Promise<string>((resolve, reject) =>
abacusStateManager.cctpWithdraw((success, error, data) => {
const parsedData = JSON.parse(data);
if (success && parsedData?.code == 0) {
resolve(parsedData?.transactionHash);
} else {
reject(error);
}
})
)
}
if (isCctp) {
return await cctpWithdraw();
}
if (!subaccountClient) { if (!subaccountClient) {
return; return;
} }
const tx = await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
return await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload }); return hashFromTx(tx?.hash);
}, },
[subaccountClient, sendSquidWithdrawFromSubaccount] [subaccountClient, sendSquidWithdrawFromSubaccount]
); );
@@ -313,11 +368,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
placeOrderParams = abacusStateManager.placeOrder(callback); placeOrderParams = abacusStateManager.placeOrder(callback);
} }
track(AnalyticsEvent.TradePlaceOrder, {
...placeOrderParams,
isClosePosition,
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
return placeOrderParams; return placeOrderParams;
}, },
[subaccountClient] [subaccountClient]
@@ -346,7 +396,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
}) => { }) => {
const callback = (success: boolean, parsingError?: Nullable<ParsingError>) => { const callback = (success: boolean, parsingError?: Nullable<ParsingError>) => {
if (success) { if (success) {
track(AnalyticsEvent.TradeCancelOrder);
onSuccess?.(); onSuccess?.();
} else { } else {
onError?.({ errorStringKey: parsingError?.stringKey }); onError?.({ errorStringKey: parsingError?.stringKey });
+4
View File
@@ -19,8 +19,10 @@ export const useTokenConfigs = (): {
}, },
}; };
usdcDenom: string; usdcDenom: string;
usdcDecimals: number;
usdcLabel: string; usdcLabel: string;
chainTokenDenom: string; chainTokenDenom: string;
chainTokenDecimals: number;
chainTokenLabel: string; chainTokenLabel: string;
} => { } => {
const { selectedNetwork } = useSelectedNetwork(); const { selectedNetwork } = useSelectedNetwork();
@@ -29,8 +31,10 @@ export const useTokenConfigs = (): {
return { return {
tokensConfigs, tokensConfigs,
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom, usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name, usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom, chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name, chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
}; };
}; };
+30
View File
@@ -0,0 +1,30 @@
import { getTradeFormInputs } from '@/state/inputsSelectors';
import { useEffect } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { TradeInputField } from '@/constants/abacus';
import abacusStateManager from '@/lib/abacus';
export const useTradeFormInputs = () => {
const tradeFormInputValues = useSelector(getTradeFormInputs, shallowEqual);
const { limitPriceInput, triggerPriceInput, trailingPercentInput } = tradeFormInputValues;
useEffect(() => {
abacusStateManager.setTradeValue({
value: triggerPriceInput,
field: TradeInputField.triggerPrice,
});
}, [triggerPriceInput]);
useEffect(() => {
abacusStateManager.setTradeValue({ value: limitPriceInput, field: TradeInputField.limitPrice });
}, [limitPriceInput]);
useEffect(() => {
abacusStateManager.setTradeValue({
value: trailingPercentInput,
field: TradeInputField.trailingPercent,
});
}, [trailingPercentInput]);
};
+50
View File
@@ -0,0 +1,50 @@
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useSelectedNetwork } from '@/hooks';
const FALLBACK_URL = 'https://help.dydx.exchange/';
export interface LinksConfigs {
tos: string,
privacy: string,
mintscan: string,
mintscanBase: string,
feedback?: string,
help?: string,
blogs?: string,
foundation?: string,
initialMarginFractionLearnMore?: string,
reduceOnlyLearnMore?: string,
documentation?: string,
community?: string,
governanceLearnMore?: string,
stakingLearnMore?: string,
keplrDashboard?: string,
accountExportLearnMore?: string,
walletLearnMore?: string
}
export const useURLConfigs = (): LinksConfigs => {
const { selectedNetwork } = useSelectedNetwork();
const linksConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].links as LinksConfigs;
return {
tos: linksConfigs.tos,
privacy: linksConfigs.privacy,
mintscan: linksConfigs.mintscan,
mintscanBase: linksConfigs.mintscanBase,
feedback: linksConfigs.feedback || FALLBACK_URL,
help: linksConfigs.help || FALLBACK_URL,
blogs: linksConfigs.blogs || FALLBACK_URL,
foundation: linksConfigs.foundation || FALLBACK_URL,
initialMarginFractionLearnMore: linksConfigs.initialMarginFractionLearnMore || FALLBACK_URL,
reduceOnlyLearnMore: linksConfigs.reduceOnlyLearnMore || FALLBACK_URL,
documentation: linksConfigs.documentation || FALLBACK_URL,
community: linksConfigs.community || FALLBACK_URL,
governanceLearnMore: linksConfigs.governanceLearnMore || FALLBACK_URL,
stakingLearnMore: linksConfigs.stakingLearnMore || FALLBACK_URL,
keplrDashboard: linksConfigs.keplrDashboard || FALLBACK_URL,
accountExportLearnMore: linksConfigs.accountExportLearnMore || FALLBACK_URL,
walletLearnMore: linksConfigs.walletLearnMore || FALLBACK_URL,
};
};
+11 -9
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
@@ -90,19 +90,21 @@ export const useWalletConnection = () => {
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect; const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
const wagmiConnector = useMemo(
const { connectAsync: connectWagmi } = () =>
walletType && walletConnectionType walletType && walletConnectionType
? useConnectWagmi({ ? resolveWagmiConnector({
connector: resolveWagmiConnector({
walletType, walletType,
walletConnection: { walletConnection: {
type: walletConnectionType, type: walletConnectionType,
}, },
walletConnectConfig, walletConnectConfig,
}), })
}) : undefined,
: useConnectWagmi(); [walletConnectConfig, walletType, walletConnectionType]
);
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
const { suggestAndConnect: connectGraz } = useConnectGraz(); const { suggestAndConnect: connectGraz } = useConnectGraz();
const connectWallet = useCallback( const connectWallet = useCallback(
+1 -3
View File
@@ -1,3 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg fill="none" height="10" viewBox="0 0 15 10" width="15" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m.321829 1.09389c.200562-.192959.469529-.298401.747791-.29315.27827.00525.54307.120763.73621.32115l5.194 5.5132 5.19397-5.5132c.0944-.10408.2088-.188152.3363-.247234.1275-.059081.2655-.091968.406-.096713.1404-.004746.2804.018748.4116.069088s.2509.126504.3522.223979c.1012.09747.1818.21427.2371.34347.0552.12921.084.26819.0845.40871.0006.14052-.0271.27972-.0813.40936s-.1339.24707-.2344.34534l-5.94997 6.3c-.09795.10163-.21538.18245-.34527.23766-.1299.05521-.26959.08367-.41073.08367s-.28083-.02846-.41073-.08367c-.12989-.05521-.24732-.13603-.34527-.23766l-5.950001-6.3c-.192962-.20056-.29840394-.46953-.29315364-.74779.00525031-.27827.12076264-.54307.32115364-.73621z" fill="currentColor" fill-rule="evenodd"/></svg>
<path d="M14 6L7.83335 10.5L2 6" stroke="currentColor" stroke-width="2.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 205 B

After

Width:  |  Height:  |  Size: 840 B

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