Compare commits

..
Author SHA1 Message Date
Bill He 1db26c1433 Add nobleValidator to config 2023-11-30 16:08:08 -08: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
53 changed files with 1128 additions and 375 deletions
+2 -2
View File
@@ -39,9 +39,9 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.0.24",
"@dydxprotocol/v4-abacus": "^1.0.30",
"@dydxprotocol/v4-client-js": "^1.0.0",
"@dydxprotocol/v4-localization": "^1.0.5",
"@dydxprotocol/v4-localization": "^1.0.8",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-checkbox": "^1.0.4",
+8 -8
View File
@@ -23,14 +23,14 @@ dependencies:
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/v4-abacus':
specifier: ^1.0.24
version: 1.0.24
specifier: ^1.0.30
version: 1.0.30
'@dydxprotocol/v4-client-js':
specifier: ^1.0.0
version: 1.0.0
'@dydxprotocol/v4-localization':
specifier: ^1.0.5
version: 1.0.5
specifier: ^1.0.8
version: 1.0.8
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@@ -984,8 +984,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
/@dydxprotocol/v4-abacus@1.0.24:
resolution: {integrity: sha512-wDGSjkrc3Se6Ev7UTjPgJV7PiyzZSz2mJwOTZikLwH7W3k1iPYGQmaCd7TudFk8h2aSdlEwNsQBCf0sLoyvHaQ==}
/@dydxprotocol/v4-abacus@1.0.30:
resolution: {integrity: sha512-zzJzNzMR3Stwv1Mu0XGUNlyo1V/ywo3yi3QSzE4mzUtXB+p5Shj5hRQCcwhHcNjLgp6a+T0BcRjBPa4QMETmwg==}
dev: false
/@dydxprotocol/v4-client-js@1.0.0:
@@ -1016,8 +1016,8 @@ packages:
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@1.0.5:
resolution: {integrity: sha512-zZm3GFp/ORz9YjigQFVBlcWmaxYY+XEdIw8AWN5nEbsuiA2Jay+/OA7lO9+nV1J1p1T+TgLo9iczIHJsAo3IsA==}
/@dydxprotocol/v4-localization@1.0.8:
resolution: {integrity: sha512-h5CSkWHPct1BWmiwJBeX9vOauXSwlx8RK9ryOsaCipJWS4AnBni07p3/jj3L/HgmU1BZXwfcr2hBouXVxxZS8A==}
dev: false
/@dydxprotocol/v4-proto@0.4.1:
+61
View File
@@ -0,0 +1,61 @@
[
{
"chainId": "42161",
"tokenAddress": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"name": "Arbitrum"
},
{
"chainId": "43114",
"tokenAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"name": "Avalanche"
},
{
"chainId": "8453",
"tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"name": "Base"
},
{
"chainId": "1",
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"name": "Ethereum"
},
{
"chainId": "10",
"tokenAddress": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"name": "OP Mainnet"
},
{
"chainId": "421613",
"tokenAddress": "0xfd064a18f3bf249cf1f87fc203e90d8f650f2d63",
"name": "Arbitrum Goerli"
},
{
"chainId": "43113",
"tokenAddress": "0x5425890298aed601595a70AB815c96711a31Bc65",
"name": "Avalanche Fuji"
},
{
"chainId": "84531",
"tokenAddress": "0xf175520c52418dfe19c8098071a252da48cd1c19",
"name": "Base Goerli"
},
{
"chainId": "5",
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
"name": "Ethereum Goerli"
},
{
"chainId": "grand-1",
"tokenAddress": "uusdc",
"name": "Noble Testnet"
},
{
"chainId": "420",
"tokenAddress": "0xe05606174bac4a6364b31bd0eca4bf4dd368f8c6",
"name": "OP Goerli"
},
{
"chainId": "103",
"name": "Solana Devnet"
}
]
+23 -7
View File
@@ -71,6 +71,7 @@
"https://validator.v4dev.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4dev.dydx.exchange"
},
"links": {
@@ -143,7 +144,8 @@
"validators": [
"http://54.92.118.111"
],
"0xsquid": "https://testnet.api.0xsquid.com"
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -215,7 +217,8 @@
"validators": [
"http://validator.v4dev4.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com"
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -287,7 +290,8 @@
"validators": [
"http://18.223.78.50"
],
"0xsquid": "https://testnet.api.0xsquid.com"
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -360,7 +364,8 @@
"validators": [
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com"
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -406,6 +411,7 @@
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"squidIntegratorId": "dYdX-api",
"isMainNet": false,
"tokens": {
"chain": {
@@ -433,7 +439,8 @@
"validators": [
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com"
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -505,7 +512,8 @@
"validators": [
"https://validator-uswest1.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com"
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -583,6 +591,7 @@
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -657,6 +666,7 @@
"https://validator.v4testnet.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -732,6 +742,7 @@
"https://dydx-testnet.nodefleet.org"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -807,6 +818,7 @@
"https://test-dydx.kingnodes.com"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -882,6 +894,7 @@
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -957,6 +970,7 @@
"https://dydx-testnet-rpc.polkachu.com/"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -1023,6 +1037,7 @@
"https://dydx-testnet-full-rpc.public.blastapi.io/"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"nobleValidator": "https://rpc.testnet.noble.strange.love",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
@@ -1099,7 +1114,8 @@
"[Validator endpoint 1",
"[Validator endpoint n]"
],
"0xsquid": "[0xSquid endpoint for mainnet]"
"0xsquid": "[0xSquid endpoint for mainnet]",
"nobleValidator": "[noble validator endpoint for mainnet]"
},
"links": {
"tos": "[HTTP link to TOS]",
+1 -1
View File
@@ -199,7 +199,7 @@
},
"mobile": {
"native": "trust:",
"universal": "https://link.trustwallet.com"
"universal": ""
},
"desktop": {
"native": "",
+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" }
]
+1
View File
@@ -122,6 +122,7 @@ Styled.Dialog = styled(Dialog)`
/* Net 0 sticky top inset (let stickyArea1 header stick to top) */
--stickyArea0-topGap: calc(-1 * var(--stickyArea0-topHeight));
overflow-x: clip;
}
`;
+6 -9
View File
@@ -44,13 +44,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
}: ComboboxMenuProps<MenuItemValue, MenuGroupValue>) => {
const [highlightedCommand, setHighlightedCommand] = useState<MenuItemValue>();
const [searchValue, setSearchValue] = useState('');
// const inputRef = useRef<HTMLInputElement | null>(null);
// console.log({ commandValue: highlightedCommand });
// useEffect(() => {
// inputRef?.current?.focus();
// }, []);
return (
<Styled.Command
@@ -67,11 +60,14 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
{withSearch && (
<Styled.Header $withStickyLayout={withStickyLayout}>
<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"
value={searchValue}
onValueChange={setSearchValue}
autoFocus
placeholder={inputPlaceholder}
/>
</Styled.Header>
@@ -258,6 +254,7 @@ Styled.Group = styled(Command.Group)<{ $withItemBorders?: boolean; $withStickyLa
> [cmdk-group-heading] {
${layoutMixins.stickyHeader}
z-index: 2;
}
> [cmdk-group-items] {
+12 -7
View File
@@ -10,6 +10,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { Button, ButtonProps } from './Button';
import { Icon, IconName } from './Icon';
import { IconButton } from './IconButton';
import { WithTooltip } from './WithTooltip';
export type CopyButtonProps = {
value?: string;
@@ -40,13 +41,17 @@ export const CopyButton = ({
<Styled.Icon copied={copied} iconName={copied ? IconName.Check : IconName.Copy} />
</Styled.InlineRow>
) : buttonType === 'icon' ? (
<Styled.IconButton
{...buttonProps}
copied={copied}
action={ButtonAction.Base}
iconName={copied ? IconName.Check : IconName.Copy}
onClick={onCopy}
/>
<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
{...buttonProps}
+3
View File
@@ -53,6 +53,7 @@ import {
OrderUntriggeredIcon,
OverviewIcon,
PencilIcon,
PlayIcon,
PositionsIcon,
PriceChartIcon,
PrivacyIcon,
@@ -126,6 +127,7 @@ export enum IconName {
OrderUntriggered = 'OrderUntriggered',
Overview = 'Overview',
Pencil = 'Pencil',
Play = 'Play',
Positions = 'Positions',
PriceChart = 'PriceChart',
Privacy = 'Privacy',
@@ -198,6 +200,7 @@ const icons = {
[IconName.OrderUntriggered]: OrderUntriggeredIcon,
[IconName.Overview]: OverviewIcon,
[IconName.Pencil]: PencilIcon,
[IconName.Play]: PlayIcon,
[IconName.Positions]: PositionsIcon,
[IconName.PriceChart]: PriceChartIcon,
[IconName.Privacy]: PrivacyIcon,
+7 -2
View File
@@ -4,6 +4,7 @@ import { Root, Action, Close } from '@radix-ui/react-toast';
import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { popoverMixins } from '@/styles/popoverMixins';
import { breakpoints } from '@/styles';
import { Notification, type NotificationProps } from '@/components/Notification';
@@ -76,7 +77,7 @@ export const Toast = ({
<Close asChild>
<$CloseButton
iconName={IconName.Close}
shape={ButtonShape.Square}
shape={ButtonShape.Circle}
size={ButtonSize.XSmall}
onClick={(e: MouseEvent) => e.stopPropagation()}
/>
@@ -249,12 +250,16 @@ const $CloseButton = styled(IconButton)`
right: 0;
border: solid var(--border-width) var(--color-border);
border-radius: 50%;
${$Root}:hover & {
display: block;
z-index: 2;
}
@media ${breakpoints.mobile} {
display: block;
z-index: 2;
}
`;
const $Action = styled(Action)`
+5 -2
View File
@@ -1,6 +1,7 @@
import styled from 'styled-components';
import { Provider, Viewport } from '@radix-ui/react-toast';
import styled from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = {
@@ -12,7 +13,9 @@ type StyleProps = {
className?: string;
};
export const ToastArea = ({ swipeDirection, children, className }: ElementProps & StyleProps) => (
type ToastAreaProps = ElementProps & StyleProps;
export const ToastArea = ({ swipeDirection, children, className }: ToastAreaProps) => (
<$ToastArea className={className}>
<Provider swipeDirection={swipeDirection}>
{children}
-1
View File
@@ -12,7 +12,6 @@ export enum DialogTypes {
OrderDetails = 'OrderDetails',
Preferences = 'Preferences',
RateLimit = 'RateLimit',
Receive = 'Receive',
RestrictedGeo = 'RestrictedGeo',
RestrictedWallet = 'RestrictedWallet',
Trade = 'Trade',
+11
View File
@@ -26,3 +26,14 @@ export enum FundingDirection {
ToShort = 'ToShort',
ToLong = 'ToLong',
}
export const MARKETS_TO_DISPLAY = [
'BTC-USD',
'ETH-USD',
'LINK-USD',
'SOL-USD',
'MATIC-USD',
'ATOM-USD',
'AVAX-USD',
'APE-USD',
];
+1
View File
@@ -4,6 +4,7 @@ import { StatusResponse } from '@0xsquid/sdk';
export enum NotificationType {
AbacusGenerated = 'AbacusGenerated',
SquidTransfer = 'SquidTransfer',
ReleaseUpdates = 'ReleaseUpdates',
}
export enum NotificationComponentType {}
+12
View File
@@ -116,3 +116,15 @@ export enum MobilePlaceOrderSteps {
PlacingOrder = 'PlacingOrder',
Confirmation = 'Confirmation',
}
export const CLEARED_TRADE_INPUTS = {
limitPriceInput: '',
triggerPriceInput: '',
trailingPercentInput: '',
};
export const CLEARED_SIZE_INPUTS = {
amountInput: '',
usdAmountInput: '',
leverageInput: '',
};
+2
View File
@@ -22,6 +22,7 @@ import { useShouldShowFooter } from './useShouldShowFooter';
import { useSelectedNetwork } from './useSelectedNetwork';
import { useStringGetter } from './useStringGetter';
import { useSubaccount } from './useSubaccount';
import { useTradeFormInputs } from './useTradeFormInputs';
import { useURLConfigs } from './useURLConfigs';
export {
@@ -49,5 +50,6 @@ export {
useSelectedNetwork,
useStringGetter,
useSubaccount,
useTradeFormInputs,
useURLConfigs,
};
+1
View File
@@ -23,6 +23,7 @@ export const useCurrentMarketId = () => {
const selectedNetwork = useSelector(getSelectedNetwork);
const marketIds = useSelector(getMarketIds, shallowEqual);
const hasMarketIds = marketIds.length > 0;
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
key: LocalStorageKey.LastViewedMarket,
defaultValue: DEFAULT_MARKETID,
+11 -2
View File
@@ -1,7 +1,14 @@
import { useMemo } from 'react';
import { useSelector, shallowEqual } from 'react-redux';
import { MarketFilters, MARKET_FILTER_LABELS, type MarketData } from '@/constants/markets';
import {
MarketFilters,
MARKET_FILTER_LABELS,
type MarketData,
MARKETS_TO_DISPLAY,
} from '@/constants/markets';
import { testFlags } from '@/hooks/useTestFlags';
import { getAssets } from '@/state/assetsSelectors';
import { getPerpetualMarkets } from '@/state/perpetualsSelectors';
@@ -42,7 +49,9 @@ export const useMarketsData = (
}, [allPerpetualMarkets, allAssets]);
const filteredMarkets = useMemo(() => {
const filtered = markets.filter(filterFunctions[filter]);
const filtered = markets
.filter(filterFunctions[filter])
.filter(({ id }) => (testFlags.displayAllMarkets ? true : MARKETS_TO_DISPLAY.includes(id)));
if (searchFilter) {
return filtered.filter(
+50
View File
@@ -25,6 +25,7 @@ import {
import { useSelectedNetwork, useStringGetter } from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
import { Icon, IconName } from '@/components/Icon';
import { TradeNotification } from '@/views/notifications/TradeNotification';
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
@@ -205,9 +206,58 @@ export const notificationTypes: NotificationTypeConfig[] = [
return () => {};
},
},
{
type: NotificationType.ReleaseUpdates,
useTrigger: ({ trigger }) => {
const stringGetter = useStringGetter();
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: {
BLOGPOST: (
<$Link
href="https://www.dydxopsdao.com/blog/deep-dive-full-trading"
target="_blank"
rel="noopener noreferrer"
>
{stringGetter({ key: STRING_KEYS.HERE })}
</$Link>
),
// todo: update localization to flip the two
DOS_BLOGPOST: (
<$Link
href="https://dydx.exchange/blog/v4-full-trading"
target="_blank"
rel="noopener noreferrer"
>
{stringGetter({ key: STRING_KEYS.HERE })}
</$Link>
),
},
}),
toastSensitivity: 'foreground',
},
[]
);
}, [stringGetter]);
},
useNotificationAction: () => {
return () => {};
},
},
];
const $Icon = styled.img`
height: 1.5rem;
width: 1.5rem;
`;
const $Link = styled.a`
--link-color: var(--color-text-2);
`;
+1
View File
@@ -81,6 +81,7 @@ const useNotificationsContext = () => {
setNotificationPreferences({
[NotificationType.AbacusGenerated]: true,
[NotificationType.SquidTransfer]: true,
[NotificationType.ReleaseUpdates]: true,
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
});
}
+7
View File
@@ -16,6 +16,13 @@ class TestFlags {
get displayInitializingMarkets() {
return !!this.queryParams.displayInitializingMarkets;
}
/**
* @description We are temporarily displaying a subset of ACTIVE markets, this flag will allow you to see all ACTIVE markets
*/
get displayAllMarkets() {
return !!this.queryParams.displayAllMarkets;
}
}
export const testFlags = new TestFlags();
+33
View File
@@ -0,0 +1,33 @@
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(() => {
const floatValue = parseFloat(triggerPriceInput);
abacusStateManager.setTradeValue({
value: floatValue,
field: TradeInputField.triggerPrice,
});
}, [triggerPriceInput]);
useEffect(() => {
const floatValue = parseFloat(limitPriceInput);
abacusStateManager.setTradeValue({ value: floatValue, field: TradeInputField.limitPrice });
}, [limitPriceInput]);
useEffect(() => {
const floatValue = parseFloat(trailingPercentInput);
abacusStateManager.setTradeValue({
value: floatValue,
field: TradeInputField.trailingPercent,
});
}, [trailingPercentInput]);
};
-2
View File
@@ -17,7 +17,6 @@ import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog';
import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
import { PreferencesDialog } from '@/views/dialogs/PreferencesDialog';
import { RateLimitDialog } from '@/views/dialogs/RateLimitDialog';
import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog';
import { RestrictedGeoDialog } from '@/views/dialogs/RestrictedGeoDialog';
import { TradeDialog } from '@/views/dialogs/TradeDialog';
import { TransferDialog } from '@/views/dialogs/TransferDialog';
@@ -60,7 +59,6 @@ export const DialogManager = () => {
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
[DialogTypes.RateLimit]: <RateLimitDialog {...modalProps} />,
[DialogTypes.Receive]: <ReceiveDialog {...modalProps} />,
[DialogTypes.RestrictedGeo]: <RestrictedGeoDialog {...modalProps} />,
[DialogTypes.RestrictedWallet]: <RestrictedWalletDialog {...modalProps} />,
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
+4 -2
View File
@@ -1,7 +1,7 @@
import styled, { type AnyStyledComponent, css } from 'styled-components';
import { AbacusApiStatus } from '@/constants/abacus';
import { ButtonSize } from '@/constants/buttons';
import { ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
@@ -55,10 +55,12 @@ export const FooterDesktop = () => {
}
>
<Styled.FooterButton
type={statusPage ? ButtonType.Link : ButtonType.Button}
slotLeft={<Styled.StatusDot exchangeStatus={exchangeStatus} />}
slotRight={statusPage && <LinkOutIcon />}
size={ButtonSize.XSmall}
state={{ isDisabled: !statusPage }}
href={statusPage}
>
{label}
</Styled.FooterButton>
@@ -132,7 +134,7 @@ Styled.StatusDot = styled.div<{ exchangeStatus: ExchangeStatus }>`
Styled.FooterButton = styled(Button)`
--button-height: 1.5rem;
--button-radius: 0.25rem;
--button-backgroundColor: var(--color-layer-2);
--button-backgroundColor: transparent;
--button-border: none;
--button-textColor: var(--color-text-0);
+29 -13
View File
@@ -1,7 +1,6 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { useDispatch, useSelector } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { DEFAULT_MARKETID } from '@/constants/markets';
@@ -11,8 +10,8 @@ import { useShouldShowFooter, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { NavigationMenu } from '@/components/NavigationMenu';
import { Icon } from '@/components/Icon';
import { BellIcon, MarketsIcon, PlayIcon, PortfolioIcon, ProfileIcon, TradeIcon } from '@/icons';
import { Icon, IconName } from '@/components/Icon';
import { BellIcon, MarketsIcon, PortfolioIcon, ProfileIcon } from '@/icons';
import { IconButton } from '@/components/IconButton';
import { calculateCanAccountTrade } from '@/state/accountCalculators';
@@ -42,11 +41,9 @@ export const FooterMobile = () => {
value: 'trade',
label: stringGetter({ key: STRING_KEYS.TRADE }),
slotBefore: (
<Styled.IconButton
iconComponent={TradeIcon}
size={ButtonSize.Large}
action={ButtonAction.Primary}
/>
<Styled.StartIcon>
<Icon iconName={IconName.Trade} />
</Styled.StartIcon>
),
href: `${AppRoute.Trade}/${marketId ?? DEFAULT_MARKETID}`,
}
@@ -54,11 +51,9 @@ export const FooterMobile = () => {
value: 'onboarding',
label: stringGetter({ key: STRING_KEYS.ONBOARDING }),
slotBefore: (
<Styled.IconButton
iconComponent={PlayIcon}
size={ButtonSize.Large}
action={ButtonAction.Primary}
/>
<Styled.StartIcon>
<Icon iconName={IconName.Play} />
</Styled.StartIcon>
),
onClick: () => dispatch(openDialog({ type: DialogTypes.Onboarding })),
},
@@ -168,3 +163,24 @@ Styled.IconButton = styled(IconButton)`
Styled.Icon = styled(Icon)`
font-size: 1.5rem;
`;
Styled.StartIcon = styled.div`
display: inline-flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-top: -0.25rem;
width: 3.5rem;
height: 3.5rem;
color: var(--color-text-2);
background-color: var(--color-accent);
border: solid var(--border-width) var(--color-border-white);
border-radius: 50%;
svg {
width: 1.5rem;
height: 1.5rem;
}
`;
+77 -14
View File
@@ -1,15 +1,17 @@
import { useMemo } from 'react';
import { useMemo, useState } from 'react';
import styled, { css } from 'styled-components';
import { NotificationStatus } from '@/constants/notifications';
import { ButtonSize } from '@/constants/buttons';
import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { useNotifications } from '@/hooks/useNotifications';
import { useBreakpoints } from '@/hooks/useBreakpoints';
import { ChevronLeftIcon } from '@/icons';
import { breakpoints } from '@/styles';
import { Button } from '@/components/Button';
import { Toast } from '@/components/Toast';
import { ToastArea } from '@/components/ToastArea';
import { ToggleButton } from '@/components/ToggleButton';
type StyleProps = {
className?: string;
@@ -18,6 +20,8 @@ type StyleProps = {
const MAX_TOASTS = 10;
export const NotificationsToastArea = ({ className }: StyleProps) => {
const [shouldStackNotifications, setshouldStackNotifications] = useState(true);
const {
notifications,
getKey,
@@ -43,20 +47,37 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
key: getKey(notification),
displayData: getDisplayData(notification),
}))
.filter(({ displayData }) => displayData)
.filter(
({ displayData, notification }) =>
displayData && notification.status < NotificationStatus.Unseen
)
.slice(-MAX_TOASTS)
);
}, [notifications, getKey, getDisplayData]);
if (isMenuOpen) return null;
const hasMultipleToasts = notificationMap.length > 1;
return (
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
{hasMultipleToasts && (
<StyledToggleButton
shape={ButtonShape.Pill}
size={ButtonSize.XSmall}
isPressed={shouldStackNotifications}
onPressedChange={() => setshouldStackNotifications(!shouldStackNotifications)}
>
<ChevronLeftIcon />
</StyledToggleButton>
)}
{notificationMap.map(({ notification, key, displayData }, idx) => (
<StyledToast
key={key}
layer={notificationMap.length - 1 - idx}
isStacked={shouldStackNotifications}
isOpen={notification.status < NotificationStatus.Unseen}
layer={notificationMap.length - 1 - idx}
notification={notification}
slotIcon={displayData.icon}
slotTitle={displayData.title}
@@ -74,7 +95,7 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
actionAltText={displayData.actionAltText}
duration={displayData.toastDuration ?? Infinity}
sensitivity={displayData.toastSensitivity}
setIsOpen={(isOpen, isClosedFromTimeout) => {
setIsOpen={(isOpen: boolean, isClosedFromTimeout?: boolean) => {
if (!isOpen)
if (isClosedFromTimeout)
// Toast timer expired without user interaction
@@ -97,16 +118,58 @@ const StyledToastArea = styled(ToastArea)`
padding: 0.75rem 0.75rem 0.75rem 0;
mask-image: linear-gradient(to left, transparent, white 0.5rem);
@media ${breakpoints.mobile} {
width: 100%;
position: fixed;
}
`;
const StyledToast = styled(Toast)<{ layer: number }>`
const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
// Stacked toast
${({ isStacked, layer }) =>
isStacked
? css`
position: absolute;
left: 0;
top: calc(${layer} * 2px);
right: calc(${layer} * -2px);
`
: css`
margin-bottom: 0.75rem;
`}
`;
const StyledToggleButton = styled(ToggleButton)<{ isPressed: boolean }>`
z-index: 2;
pointer-events: auto;
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
${({ layer }) => css`
right: calc(${layer} * -2px);
top: calc(${layer} * 2px);
`}
top: 4px;
left: calc(50% - 0.75rem);
--button-width: 2rem;
--button-height: 1rem;
${StyledToastArea}:hover & {
display: flex;
}
> svg {
width: 4px;
margin-top: 1px;
transform: rotate(-90deg);
${({ isPressed }) =>
!isPressed &&
css`
transform: rotate(90deg);
`}
}
@media ${breakpoints.mobile} {
display: flex;
left: calc(50% - 2rem);
--button-width: 4rem;
--button-height: 2rem;
}
`;
+8 -3
View File
@@ -27,11 +27,11 @@ import {
import { DEFAULT_MARKETID } from '@/constants/markets';
import { CURRENT_ABACUS_DEPLOYMENT, type DydxNetwork } from '@/constants/networks';
import { CLEARED_SIZE_INPUTS, CLEARED_TRADE_INPUTS } from '@/constants/trade';
import type { RootStore } from '@/state/_store';
import { getInputTradeOptions } from '@/state/inputsSelectors';
import { getTransferInputs } from '@/state/inputsSelectors';
import { setTradeFormInputs } from '@/state/inputs';
import { getInputTradeOptions, getTransferInputs } from '@/state/inputsSelectors';
import AbacusRest from './rest';
import AbacusAnalytics from './analytics';
@@ -43,6 +43,7 @@ import AbacusFormatter from './formatter';
import AbacusThreading from './threading';
import AbacusFileSystem from './filesystem';
import { LocaleSeparators } from '../numbers';
class AbacusStateManager {
private store: RootStore | undefined;
private currentMarket: string | undefined;
@@ -132,6 +133,8 @@ class AbacusStateManager {
this.setTradeValue({ value: null, field: TradeInputField.limitPrice });
}
this.store?.dispatch(setTradeFormInputs(CLEARED_TRADE_INPUTS));
if (shouldResetSize) {
this.setTradeValue({ value: null, field: TradeInputField.size });
this.setTradeValue({ value: null, field: TradeInputField.usdcSize });
@@ -139,6 +142,8 @@ class AbacusStateManager {
if (needsLeverage) {
this.setTradeValue({ value: null, field: TradeInputField.leverage });
}
this.store?.dispatch(setTradeFormInputs(CLEARED_SIZE_INPUTS));
}
};
+98 -35
View File
@@ -2,12 +2,16 @@ import styled, { AnyStyledComponent, css } from 'styled-components';
import { Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { useEnsName } from 'wagmi';
import { useNavigate } from 'react-router-dom';
import { ButtonSize } from '@/constants/buttons';
import { TransferInputField, TransferType } from '@/constants/abacus';
import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details';
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
import { IconName } from '@/components/Icon';
import { Icon, IconName } from '@/components/Icon';
import { IconButton, type IconButtonProps } from '@/components/IconButton';
import { Panel } from '@/components/Panel';
import { Toolbar } from '@/components/Toolbar';
@@ -16,22 +20,22 @@ import { OnboardingState } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute, PortfolioRoute, HistoryRoute } from '@/constants/routes';
import { wallets, WalletType } from '@/constants/wallets';
import { wallets } from '@/constants/wallets';
import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import { getOnboardingState } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import abacusStateManager from '@/lib/abacus';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { layoutMixins } from '@/styles/layoutMixins';
const ENS_CHAIN_ID = 1; // Ethereum
const Profile = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const navigate = useNavigate();
const onboardingState = useSelector(getOnboardingState);
const isConnected = onboardingState !== OnboardingState.Disconnected;
@@ -46,17 +50,48 @@ const Profile = () => {
const actions = [
{
key: 'settings',
label: stringGetter({ key: STRING_KEYS.SETTINGS }),
icon: { iconName: IconName.Gear },
href: AppRoute.Settings,
},
isConnected && {
key: 'transfers',
label: stringGetter({ key: STRING_KEYS.MANAGE_FUNDS }),
icon: { iconName: IconName.Transfer },
key: 'deposit',
label: stringGetter({ key: STRING_KEYS.DEPOSIT }),
icon: { iconName: IconName.Deposit },
onClick: () => {
dispatch(openDialog({ type: DialogTypes.ManageFunds }));
dispatch(
openDialog({
type: DialogTypes.ManageFunds,
dialogProps: {
selectedTransferType: TransferType.deposit.rawValue,
},
})
);
},
},
{
key: 'withdraw',
label: stringGetter({ key: STRING_KEYS.WITHDRAW }),
icon: { iconName: IconName.Withdraw },
onClick: () => {
dispatch(
openDialog({
type: DialogTypes.ManageFunds,
dialogProps: {
selectedTransferType: TransferType.withdrawal.rawValue,
},
})
);
},
},
{
key: 'transfer',
label: stringGetter({ key: STRING_KEYS.TRANSFER }),
icon: { iconName: IconName.Send },
onClick: () => {
dispatch(
openDialog({
type: DialogTypes.ManageFunds,
dialogProps: {
selectedTransferType: TransferType.transferOut.rawValue,
},
})
);
},
},
isConnected
@@ -69,9 +104,9 @@ const Profile = () => {
},
}
: {
key: 'wallet',
label: stringGetter({ key: STRING_KEYS.WALLET }),
icon: { iconComponent: wallets[walletType || WalletType.OtherWallet].icon },
key: 'connect',
label: stringGetter({ key: STRING_KEYS.CONNECT }),
icon: { iconName: IconName.Transfer },
onClick: () => {
dispatch(openDialog({ type: DialogTypes.Onboarding }));
},
@@ -107,12 +142,7 @@ const Profile = () => {
{actions.map(({ key, label, href, icon, onClick }) => {
const action = (
<>
<Styled.ActionButton
{...icon}
size={ButtonSize.Large}
onClick={onClick}
isCloseIcon={icon.iconName === IconName.Close}
/>
<Styled.ActionButton {...icon} size={ButtonSize.Large} onClick={onClick} />
<span>{label}</span>
</>
);
@@ -125,6 +155,26 @@ const Profile = () => {
);
})}
</Styled.Actions>
<Styled.EqualGrid>
<Styled.PanelButton
slotHeader={
<Styled.InlineRow>
<Icon iconName={IconName.Gear} />
{stringGetter({ key: STRING_KEYS.SETTINGS })}
</Styled.InlineRow>
}
onClick={() => navigate(AppRoute.Settings)}
/>
<Styled.PanelButton
slotHeader={
<Styled.InlineRow>
<Icon iconName={IconName.HelpCircle} />
{stringGetter({ key: STRING_KEYS.HELP })}
</Styled.InlineRow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
/>
</Styled.EqualGrid>
<Styled.EqualGrid>
<Panel
@@ -183,7 +233,7 @@ Styled.MobileProfileLayout = styled.div`
${layoutMixins.contentContainerPage}
gap: 1rem;
padding: 1.25rem;
padding: 1.25rem 0.9rem;
`;
Styled.Header = styled.header`
@@ -248,19 +298,21 @@ Styled.Actions = styled(Toolbar)`
}
`;
Styled.ActionButton = styled(IconButton)<{ isCloseIcon?: boolean }>`
Styled.ActionButton = styled(IconButton)<{ iconName?: IconName }>`
margin-bottom: 0.5rem;
${({ isCloseIcon }) =>
isCloseIcon &&
css`
--button-textColor: var(--color-negative);
svg {
width: 0.875em;
height: 0.875em;
}
`}
${({ iconName }) =>
iconName === IconName.Close
? css`
--button-textColor: var(--color-negative);
--button-icon-size: 0.75em;
`
: iconName === IconName.Transfer &&
css`
--button-icon-size: 1.25em;
--button-textColor: var(--color-text-2);
--button-backgroundColor: var(--color-accent);
`}
`;
Styled.EqualGrid = styled.div`
@@ -309,3 +361,14 @@ Styled.TablePanel = styled(Panel)`
}
}
`;
Styled.InlineRow = styled.div`
${layoutMixins.inlineRow}
padding: 1rem;
gap: 0.5rem;
`;
Styled.PanelButton = styled(Panel)`
--panel-paddingY: 0
--panel-paddingX:0;
`;
+3 -3
View File
@@ -39,7 +39,7 @@ export const DYDXBalancePanel = () => {
<AssetIcon symbol={chainTokenLabel} />
{chainTokenLabel}
</Styled.Title>
<Styled.ReceiveAndTransferButtons>
<Styled.ActionButtons>
{!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Small} />
) : (
@@ -52,7 +52,7 @@ export const DYDXBalancePanel = () => {
{stringGetter({ key: STRING_KEYS.TRANSFER })}
</Button>
)}
</Styled.ReceiveAndTransferButtons>
</Styled.ActionButtons>
</Styled.Header>
}
>
@@ -134,7 +134,7 @@ Styled.Title = styled.h3`
}
`;
Styled.ReceiveAndTransferButtons = styled(Toolbar)`
Styled.ActionButtons = styled(Toolbar)`
${layoutMixins.inlineRow}
--stickyArea-topHeight: max-content;
gap: 0.5rem;
+7 -1
View File
@@ -7,7 +7,12 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { TradeLayouts } from '@/constants/layout';
import { useBreakpoints, useCurrentMarketId, usePageTitlePriceUpdates } from '@/hooks';
import {
useBreakpoints,
useCurrentMarketId,
usePageTitlePriceUpdates,
useTradeFormInputs,
} from '@/hooks';
import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { getSelectedTradeLayout } from '@/state/layoutSelectors';
@@ -37,6 +42,7 @@ const TradePage = () => {
const [isHorizontalPanelOpen, setIsHorizontalPanelOpen] = useState(true);
usePageTitlePriceUpdates();
useTradeFormInputs();
return isTablet ? (
<Styled.TradeLayoutMobile>
+15 -1
View File
@@ -1,4 +1,5 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import assign from 'lodash/assign';
import type {
InputError,
@@ -9,9 +10,14 @@ import type {
TransferInputs,
} from '@/constants/abacus';
import { CLEARED_SIZE_INPUTS, CLEARED_TRADE_INPUTS } from '@/constants/trade';
type TradeFormInputs = typeof CLEARED_TRADE_INPUTS & typeof CLEARED_SIZE_INPUTS;
export interface InputsState {
current?: Nullable<string>;
inputErrors?: Nullable<InputError[]>;
tradeFormInputs: TradeFormInputs;
tradeInputs?: Nullable<TradeInputs>;
closePositionInputs?: Nullable<ClosePositionInputs>;
transferInputs?: Nullable<TransferInputs>;
@@ -20,6 +26,10 @@ export interface InputsState {
const initialState: InputsState = {
current: undefined,
inputErrors: undefined,
tradeFormInputs: {
...CLEARED_TRADE_INPUTS,
...CLEARED_SIZE_INPUTS,
},
tradeInputs: undefined,
transferInputs: undefined,
};
@@ -40,7 +50,11 @@ export const inputsSlice = createSlice({
transferInputs: transfer,
};
},
setTradeFormInputs: (state, action: PayloadAction<Partial<TradeFormInputs>>) => {
state.tradeFormInputs = assign({}, state.tradeFormInputs, action.payload);
},
},
});
export const { setInputs } = inputsSlice.actions;
export const { setInputs, setTradeFormInputs } = inputsSlice.actions;
+5
View File
@@ -120,3 +120,8 @@ export const useTradeFormData = () => {
shallowEqual
);
};
/**
* @returns Tradeform Input states for display. Abacus inputs should track these values.
*/
export const getTradeFormInputs = (state: RootState) => state.inputs.tradeFormInputs;
+2 -3
View File
@@ -57,9 +57,8 @@ export default (store: any) => (next: any) => async (action: PayloadAction<any>)
} else if (globalThis.navigator?.language) {
const browserLanguageBaseTag = globalThis.navigator.language.split('-')[0].toLowerCase();
const locale = SUPPORTED_BASE_TAGS_LOCALE_MAPPING[
browserLanguageBaseTag
] as SupportedLocales;
const locale = (SUPPORTED_BASE_TAGS_LOCALE_MAPPING[browserLanguageBaseTag] ??
SUPPORTED_BASE_TAGS_LOCALE_MAPPING[SupportedLocales.EN]) as SupportedLocales;
if (locale) {
store.dispatch(setSelectedLocale({ locale, isAutoDetect: true }));
+2
View File
@@ -151,6 +151,8 @@ export const formMixins: Record<
--form-input-paddingY: 0.5rem;
--form-input-paddingX: 1rem;
height: 100%;
label {
--label-textColor: var(--color-text-0);
}
+4 -1
View File
@@ -2,7 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { STRING_KEYS } from '@/constants/localization';
import { MARKETS_TO_DISPLAY } from '@/constants/markets';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { testFlags } from '@/hooks/useTestFlags';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -32,6 +34,7 @@ export const ExchangeBillboards: React.FC<ExchangeBillboardsProps> = ({
Object.values(perpetualMarkets)
.filter(Boolean)
.filter(({ id }) => (testFlags.displayAllMarkets ? true : MARKETS_TO_DISPLAY.includes(id)))
.forEach(({ oraclePrice, perpetual }) => {
const { volume24H, trades24H, openInterest = 0 } = perpetual || {};
volume24HUSDC += volume24H ?? 0;
@@ -60,7 +63,7 @@ export const ExchangeBillboards: React.FC<ExchangeBillboardsProps> = ({
},
{
key: 'trades',
labelKey: STRING_KEYS.TRADES,
labelKey: isTablet ? STRING_KEYS.TRADES_24H : STRING_KEYS.TRADES,
value: totalTrades24H || undefined,
type: isTablet ? OutputType.CompactNumber : OutputType.Number,
subLabelKey: !isTablet && STRING_KEYS.TRADES_LABEL,
+3 -2
View File
@@ -5,7 +5,7 @@ import { createSelector } from 'reselect';
import { TradeInputField } from '@/constants/abacus';
import { TradeTypes } from '@/constants/trade';
import { STRING_KEYS } from '@/constants/localization';
import { STRING_KEYS, StringKey } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -30,7 +30,8 @@ const useTradeTypeOptions = () => {
const allTradeTypeItems = typeOptions?.toArray()?.map(({ type, stringKey }) => ({
value: type,
label: stringGetter({
key: type === TradeTypes.TAKE_PROFIT ? STRING_KEYS.TAKE_PROFIT_LIMIT : stringKey,
key:
type === TradeTypes.TAKE_PROFIT ? STRING_KEYS.TAKE_PROFIT_LIMIT : (stringKey as StringKey),
}),
}));
@@ -1,12 +1,15 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { TransferInputField, TransferType } from '@/constants/abacus';
import { isMainnet } from '@/constants/networks';
import { layoutMixins } from '@/styles/layoutMixins';
import { DepositForm } from '@/views/forms/AccountManagementForms/DepositForm';
import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm';
import abacusStateManager from '@/lib/abacus';
type ElementProps = {
onDeposit?: () => void;
};
@@ -14,6 +17,20 @@ type ElementProps = {
export const DepositDialogContent = ({ onDeposit }: ElementProps) => {
const [showFaucet, setShowFaucet] = useState(!isMainnet);
useEffect(() => {
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: TransferType.deposit.rawValue,
});
return () => {
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
return (
<Styled.Content>
{isMainnet || !showFaucet ? (
+3 -2
View File
@@ -18,13 +18,14 @@ import abacusStateManager from '@/lib/abacus';
import { DepositDialogContent } from './DepositDialog/DepositDialogContent';
type ElementProps = {
selectedTransferType?: string;
setIsOpen?: (open: boolean) => void;
};
export const ManageFundsDialog = ({ setIsOpen }: ElementProps) => {
export const ManageFundsDialog = ({ setIsOpen, selectedTransferType }: ElementProps) => {
const stringGetter = useStringGetter();
const { type } = useSelector(getTransferInputs, shallowEqual) || {};
const currentType = type?.rawValue ?? TransferType.deposit.rawValue;
const currentType = type?.rawValue ?? selectedTransferType ?? TransferType.deposit.rawValue;
const closeDialog = () => setIsOpen?.(false);
@@ -7,6 +7,7 @@ import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons';
import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
import { Button } from '@/components/Button';
import { Link } from '@/components/Link';
@@ -54,14 +55,14 @@ export const AcknowledgeTerms = ({ onClose, onContinue }: ElementProps) => {
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE5 })}</li>
</ul>
</Styled.TOS>
<Styled.Row>
<Styled.Footer>
<Styled.Button onClick={onClose} action={ButtonAction.Base}>
{stringGetter({ key: STRING_KEYS.CLOSE })}
</Styled.Button>
<Styled.Button onClick={onAcknowledgement} action={ButtonAction.Primary}>
{stringGetter({ key: STRING_KEYS.I_AGREE })}
</Styled.Button>
</Styled.Row>
</Styled.Footer>
</>
);
};
@@ -87,8 +88,9 @@ Styled.TOS = styled.section`
}
`;
Styled.Row = styled.div`
${layoutMixins.row};
Styled.Footer = styled.div`
${formMixins.footer};
${layoutMixins.row}
gap: 1rem;
`;
@@ -8,7 +8,7 @@ import { STRING_KEYS } from '@/constants/localization';
import { Button } from '@/components/Button';
import { openDialog } from '@/state/dialogs';
import { forceOpenDialog } from '@/state/dialogs';
import { getOnboardingState } from '@/state/accountSelectors';
import { OnboardingState } from '@/constants/account';
@@ -26,7 +26,7 @@ export const OnboardingTriggerButton = ({ size = ButtonSize.Small }: StyleProps)
<Button
action={ButtonAction.Primary}
size={size}
onClick={() => dispatch(openDialog({ type: DialogTypes.Onboarding }))}
onClick={() => dispatch(forceOpenDialog({ type: DialogTypes.Onboarding }))}
>
{
{
+12
View File
@@ -53,6 +53,18 @@ export const usePreferenceMenu = () => {
),
onSelect: () => toggleNotifPreference(NotificationType.SquidTransfer),
},
{
value: NotificationType.ReleaseUpdates,
label: "Release Updates",
slotAfter: (
<Switch
name={NotificationType.ReleaseUpdates}
checked={enabledNotifs[NotificationType.ReleaseUpdates]}
onCheckedChange={(enabled: boolean) => null}
/>
),
onSelect: () => toggleNotifPreference(NotificationType.ReleaseUpdates),
}
],
}),
[stringGetter, enabledNotifs]
-124
View File
@@ -1,124 +0,0 @@
import { useState } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { DydxChainAsset } from '@/constants/wallets';
import { formMixins } from '@/styles/formMixins';
import { layoutMixins } from '@/styles/layoutMixins';
import { useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { CopyButton } from '@/components/CopyButton';
import { Dialog } from '@/components/Dialog';
import { QrCode } from '@/components/QrCode';
import { SelectItem, SelectMenu } from '@/components/SelectMenu';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { truncateAddress } from '@/lib/wallet';
import { OnboardingTriggerButton } from './OnboardingTriggerButton';
type ElementProps = {
selectedAsset?: DydxChainAsset;
setIsOpen: (open: boolean) => void;
};
export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.CHAINTOKEN, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const { dydxAddress } = useAccounts();
const { chainTokenLabel, usdcLabel } = useTokenConfigs();
const [asset, setAsset] = useState(selectedAsset);
const assetOptions = [
{
value: DydxChainAsset.USDC,
label: (
<Styled.InlineRow>
<AssetIcon symbol="USDC" /> {usdcLabel}
</Styled.InlineRow>
),
},
{
value: DydxChainAsset.CHAINTOKEN,
label: (
<Styled.InlineRow>
{/* TODO: conditionally display the native token assetIcon */}
{/* <AssetIcon symbol="DYDX" /> */}
{chainTokenLabel}
</Styled.InlineRow>
),
},
];
return (
<Styled.Dialog isOpen setIsOpen={setIsOpen} title={stringGetter({ key: STRING_KEYS.RECEIVE })}>
<Styled.Content>
<Styled.SelectMenu
label={stringGetter({ key: STRING_KEYS.ASSET })}
value={asset}
onValueChange={setAsset}
>
{assetOptions.map(({ value, label }) => (
<Styled.SelectItem key={value} value={value} label={label} />
))}
</Styled.SelectMenu>
{!dydxAddress ? (
<OnboardingTriggerButton />
) : (
<>
<Styled.WithDetailsReceipt
side="bottom"
detailItems={[
{
key: 'address',
label: stringGetter({ key: STRING_KEYS.DYDX_CHAIN_ADDRESS }),
value: truncateAddress(dydxAddress),
},
]}
>
<QrCode hasLogo value={dydxAddress!} />
</Styled.WithDetailsReceipt>
<CopyButton value={dydxAddress} />
</>
)}
</Styled.Content>
</Styled.Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Dialog = styled(Dialog)`
--dialog-content-paddingTop: var(--default-border-width);
--dialog-width: 20rem;
`;
Styled.Content = styled.div`
${formMixins.inputsColumn}
gap: 1.25rem;
`;
Styled.SelectMenu = styled(SelectMenu)`
${formMixins.inputSelectMenu}
--form-input-height: 3.5rem;
`;
Styled.SelectItem = styled(SelectItem)`
${formMixins.inputSelectMenuItem}
`;
Styled.InlineRow = styled.span`
${layoutMixins.inlineRow}
height: 100%;
img {
font-size: 1.1em;
}
`;
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
border-radius: 0.75em;
`;
@@ -2,9 +2,10 @@ import { type FormEvent, useCallback, useEffect, useMemo, useState } from 'react
import styled, { type AnyStyledComponent } from 'styled-components';
import { type NumberFormatValues } from 'react-number-format';
import { shallowEqual, useSelector } from 'react-redux';
import { parseUnits } from 'viem';
import { Abi, parseUnits } from 'viem';
import erc20 from '@/abi/erc20.json';
import erc20_usdt from '@/abi/erc20_usdt.json';
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
@@ -191,15 +192,23 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
const sourceAmountBN = parseUnits(debouncedAmount, sourceToken.decimals);
if (sourceAmountBN > (allowance as bigint)) {
const { request } = await publicClientWagmi.simulateContract({
account: evmAddress,
address: sourceToken.address as EvmAddress,
abi: erc20,
functionName: 'approve',
args: [requestPayload.targetAddress as EvmAddress, sourceAmountBN],
});
const simulateApprove = async (abi: Abi) =>
publicClientWagmi.simulateContract({
account: evmAddress,
address: sourceToken.address as EvmAddress,
abi,
functionName: 'approve',
args: [requestPayload.targetAddress as EvmAddress, sourceAmountBN],
});
const approveTx = await signerWagmi.writeContract(request);
let result;
try {
result = await simulateApprove(erc20 as Abi);
} catch (e) {
result = await simulateApprove(erc20_usdt as Abi);
}
const approveTx = await signerWagmi.writeContract(result.request);
await publicClientWagmi.waitForTransactionReceipt({
hash: approveTx,
});
@@ -2,14 +2,16 @@ import { useState, type FormEvent, useEffect } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent } from 'styled-components';
import { ButtonAction, ButtonType } from '@/constants/buttons';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useAccounts, useStringGetter, useSubaccount } from '@/hooks';
import { formMixins } from '@/styles/formMixins';
import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton';
import { Button } from '@/components/Button';
import { calculateCanAccountTrade } from '@/state/accountCalculators';
import { getSubaccount } from '@/state/accountSelectors';
import { getSelectedNetwork } from '@/state/appSelectors';
@@ -26,6 +28,7 @@ export const TestnetDepositForm = ({ onDeposit, onError }: DepositFormProps) =>
const { requestFaucetFunds } = useSubaccount();
const subAccount = useSelector(getSubaccount, shallowEqual);
const selectedNetwork = useSelector(getSelectedNetwork);
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const [isLoading, setIsLoading] = useState(false);
@@ -72,9 +75,13 @@ export const TestnetDepositForm = ({ onDeposit, onError }: DepositFormProps) =>
})}
</p>
<Styled.Footer>
<Button action={ButtonAction.Primary} type={ButtonType.Submit} state={{ isLoading }}>
{stringGetter({ key: STRING_KEYS.DEPOSIT_FUNDS })}
</Button>
{!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Base} />
) : (
<Button action={ButtonAction.Primary} type={ButtonType.Submit} state={{ isLoading }}>
{stringGetter({ key: STRING_KEYS.DEPOSIT_FUNDS })}
</Button>
)}
</Styled.Footer>
</Styled.Form>
);
+19 -20
View File
@@ -1,6 +1,6 @@
import { type FormEvent, useState, Ref, useCallback } from 'react';
import { type FormEvent, useState, Ref, useCallback, useEffect } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import type { NumberFormatValues, SourceInfo } from 'react-number-format';
import { AlertType } from '@/constants/alerts';
@@ -37,7 +37,8 @@ import { WithTooltip } from '@/components/WithTooltip';
import { Orderbook } from '@/views/tables/Orderbook';
import { getCurrentInput, useTradeFormData } from '@/state/inputsSelectors';
import { setTradeFormInputs } from '@/state/inputs';
import { getCurrentInput, getTradeFormInputs, useTradeFormData } from '@/state/inputsSelectors';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
import abacusStateManager from '@/lib/abacus';
@@ -80,6 +81,7 @@ export const TradeForm = ({
const [placeOrderError, setPlaceOrderError] = useState<string>();
const [showOrderbook, setShowOrderbook] = useState(false);
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { placeOrder } = useSubaccount();
@@ -98,18 +100,21 @@ export const TradeForm = ({
tradeErrors,
} = useTradeFormData();
const { limitPrice, triggerPrice, trailingPercent } = price || {};
const currentInput = useSelector(getCurrentInput);
const { tickSizeDecimals, stepSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) || {};
const tradeFormInputValues = useSelector(getTradeFormInputs, shallowEqual);
const { limitPriceInput, triggerPriceInput, trailingPercentInput } = tradeFormInputValues;
const needsAdvancedOptions =
needsGoodUntil || timeInForceOptions || executionOptions || needsPostOnly || needsReduceOnly;
const tradeFormInputs: TradeBoxInputConfig[] = [];
const isInputFilled =
Object.values(price || {}).some((val) => val != null) ||
Object.values(tradeFormInputValues).some((val) => val !== '') ||
Object.values(price || {}).some((val) => !!val) ||
[size?.size, size?.usdcSize, size?.leverage].some((val) => val != null);
const hasInputErrors =
@@ -197,13 +202,10 @@ export const TradeForm = ({
<Tag>USD</Tag>
</>
),
onChange: ({ floatValue }: NumberFormatValues) => {
abacusStateManager.setTradeValue({
value: floatValue,
field: TradeInputField.triggerPrice,
});
onChange: ({ value }: NumberFormatValues) => {
dispatch(setTradeFormInputs({ triggerPriceInput: value }));
},
value: triggerPrice ?? '',
value: triggerPriceInput ?? '',
decimals: tickSizeDecimals || USD_DECIMALS,
});
}
@@ -220,10 +222,10 @@ export const TradeForm = ({
<Tag>USD</Tag>
</>
),
onChange: ({ floatValue }: NumberFormatValues) => {
abacusStateManager.setTradeValue({ value: floatValue, field: TradeInputField.limitPrice });
onChange: ({ value }: NumberFormatValues) => {
dispatch(setTradeFormInputs({ limitPriceInput: value }));
},
value: limitPrice ?? '',
value: limitPriceInput,
decimals: tickSizeDecimals || USD_DECIMALS,
});
}
@@ -237,13 +239,10 @@ export const TradeForm = ({
{stringGetter({ key: STRING_KEYS.TRAILING_PERCENT })}
</WithTooltip>
),
onChange: ({ floatValue }: NumberFormatValues) => {
abacusStateManager.setTradeValue({
value: floatValue,
field: TradeInputField.trailingPercent,
});
onChange: ({ value }: NumberFormatValues) => {
dispatch(setTradeFormInputs({ trailingPercentInput: value }));
},
value: trailingPercent ?? '',
value: trailingPercentInput ?? '',
});
}
@@ -30,8 +30,8 @@ import { getSelectedOrderSide, hasPositionSideChanged } from '@/lib/tradeData';
import { LeverageSlider } from './LeverageSlider';
type ElementProps = {
leverageInputValue: Nullable<number>;
setLeverageInputValue: Dispatch<SetStateAction<Nullable<number>>>;
leverageInputValue: string;
setLeverageInputValue: (value: string) => void;
};
export const MarketLeverageInput = ({
@@ -66,7 +66,7 @@ export const MarketLeverageInput = ({
const newLeverage = MustBigNumber(floatValue).toFixed();
if (value === '' || newLeverage === 'NaN' || !floatValue) {
setLeverageInputValue(null);
setLeverageInputValue('');
abacusStateManager.setTradeValue({
value: null,
field: TradeInputField.leverage,
@@ -84,7 +84,7 @@ export const MarketLeverageInput = ({
? newLeverageBN.abs().negated()
: newLeverageBN.abs();
setLeverageInputValue(newLeverageSignedBN.toNumber());
setLeverageInputValue(newLeverageSignedBN.toString());
abacusStateManager.setTradeValue({
value: newLeverageSignedBN.toFixed(LEVERAGE_DECIMALS),
@@ -98,11 +98,11 @@ export const MarketLeverageInput = ({
if (leveragePosition === PositionSide.None) return;
const inputValue = leverageInputValue || currentLeverage;
const newInputValue = MustBigNumber(inputValue).negated().toNumber();
const newInputValue = MustBigNumber(inputValue).negated().toFixed(LEVERAGE_DECIMALS);
setLeverageInputValue(newInputValue);
abacusStateManager.setTradeValue({
value: newInputValue.toFixed(LEVERAGE_DECIMALS),
value: newInputValue,
field: TradeInputField.leverage,
});
};
@@ -9,6 +9,7 @@ import { TRADE_TYPE_STRINGS, MobilePlaceOrderSteps } from '@/constants/trade';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { Button } from '@/components/Button';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
@@ -77,7 +78,11 @@ export const PlaceOrderButtonAndReceipt = ({
},
{
key: 'fee',
label: stringGetter({ key: STRING_KEYS.FEE }),
label: (
<WithTooltip tooltip="fee" side="right">
{stringGetter({ key: STRING_KEYS.FEE })}
</WithTooltip>
),
value: <Output type={OutputType.Fiat} value={fee} useGrouping />,
},
{
@@ -85,7 +90,7 @@ export const PlaceOrderButtonAndReceipt = ({
label: (
<>
{stringGetter({ key: STRING_KEYS.MAXIMUM_REWARDS })}
{/* <AssetIcon symbol="DYDX" /> */}
<AssetIcon symbol={chainTokenLabel} />
</>
),
value: (
+24 -18
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import styled, { AnyStyledComponent } from 'styled-components';
import { TradeInputField } from '@/constants/abacus';
@@ -20,8 +20,13 @@ import { Icon, IconName } from '@/components/Icon';
import { ToggleButton } from '@/components/ToggleButton';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { setTradeFormInputs } from '@/state/inputs';
import { getInputTradeSizeData, getInputTradeOptions } from '@/state/inputsSelectors';
import {
getInputTradeSizeData,
getInputTradeOptions,
getTradeFormInputs,
} from '@/state/inputsSelectors';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
@@ -31,12 +36,8 @@ import { MustBigNumber } from '@/lib/numbers';
import { MarketLeverageInput } from './MarketLeverageInput';
export const TradeSizeInputs = () => {
const [sizeInputValue, setSizeInputValue] = useState<number | null>();
const [usdcInputValue, setUsdcInputValue] = useState<number | null>();
const [leverageInputValue, setLeverageInputValue] = useState<number | null>();
const [showUSDCInputOnTablet, setShowUSDCInputOnTablet] = useState(false);
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { isTablet } = useBreakpoints();
@@ -50,24 +51,27 @@ export const TradeSizeInputs = () => {
const { needsLeverage } = currentTradeInputOptions || {};
const decimals = stepSizeDecimals || TOKEN_DECIMALS;
const { amountInput, usdAmountInput, leverageInput } = useSelector(
getTradeFormInputs,
shallowEqual
);
// Update State variables if their inputs are not being source of calculations
// Or if they have been reset to null
useEffect(() => {
if (lastEditedInput !== TradeSizeInput.Size || size == null) {
// Abacus size already respects step size
// using .toFixed to prevent trailing zeros and exponential notation
setSizeInputValue(size);
dispatch(setTradeFormInputs({ amountInput: size ? size.toString() : '' }));
}
if (lastEditedInput !== TradeSizeInput.Usdc || usdcSize == null) {
setUsdcInputValue(usdcSize);
dispatch(setTradeFormInputs({ usdAmountInput: usdcSize ? usdcSize.toString() : '' }));
}
if (lastEditedInput !== TradeSizeInput.Leverage || leverage == null) {
setLeverageInputValue(leverage);
dispatch(setTradeFormInputs({ leverageInput: leverage ? leverage.toString() : '' }));
}
}, [size, usdcSize, leverage, lastEditedInput]);
const onSizeInput = ({ value, floatValue }: { value: string; floatValue?: number }) => {
setSizeInputValue(floatValue);
dispatch(setTradeFormInputs({ amountInput: value }));
const newAmount = MustBigNumber(floatValue).toFixed(decimals);
abacusStateManager.setTradeValue({
@@ -77,7 +81,7 @@ export const TradeSizeInputs = () => {
};
const onUSDCInput = ({ value, floatValue }: { value: string; floatValue?: number }) => {
setUsdcInputValue(floatValue);
dispatch(setTradeFormInputs({ usdAmountInput: value }));
const newUsdcAmount = MustBigNumber(floatValue).toFixed();
abacusStateManager.setTradeValue({
@@ -113,7 +117,7 @@ export const TradeSizeInputs = () => {
}
slotRight={isTablet && inputToggleButton}
type={InputType.Number}
value={sizeInputValue || ''}
value={amountInput || ''}
/>
);
@@ -122,7 +126,7 @@ export const TradeSizeInputs = () => {
id="trade-usdc"
onInput={onUSDCInput}
type={InputType.Currency}
value={usdcInputValue || ''}
value={usdAmountInput || ''}
decimals={tickSizeDecimals || USD_DECIMALS}
label={
<>
@@ -153,8 +157,10 @@ export const TradeSizeInputs = () => {
{needsLeverage && (
<MarketLeverageInput
leverageInputValue={leverageInputValue}
setLeverageInputValue={setLeverageInputValue}
leverageInputValue={leverageInput}
setLeverageInputValue={(value: string) =>
dispatch(setTradeFormInputs({ leverageInput: value }))
}
/>
)}
</Styled.Column>
+4
View File
@@ -119,6 +119,10 @@ export const TransferForm = ({
return () => {
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
+52 -29
View File
@@ -1,12 +1,12 @@
import { memo } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import type { Dispatch } from '@reduxjs/toolkit';
import { OnboardingState } from '@/constants/account';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS, TOOLTIP_STRING_KEYS } from '@/constants/localization';
import { STRING_KEYS, StringGetterFunction, TOOLTIP_STRING_KEYS } from '@/constants/localization';
import { isMainnet } from '@/constants/networks';
import { DydxChainAsset, wallets } from '@/constants/wallets';
@@ -40,7 +40,6 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { headerMixins } from '@/styles/headerMixins';
import { MustBigNumber } from '@/lib/numbers';
export const AccountMenu = () => {
const stringGetter = useStringGetter();
const { mintscanBase } = useURLConfigs();
@@ -91,13 +90,15 @@ export const AccountMenu = () => {
<Styled.Address>{truncateAddress(dydxAddress)}</Styled.Address>
</Styled.Column>
<Styled.CopyButton buttonType="icon" value={dydxAddress} shape={ButtonShape.Square} />
<Styled.IconButton
action={ButtonAction.Base}
href={`${mintscanBase}/account/${dydxAddress}`}
iconName={IconName.LinkOut}
shape={ButtonShape.Square}
type={ButtonType.Link}
/>
<WithTooltip tooltipString={stringGetter({ key: STRING_KEYS.MINTSCAN })}>
<Styled.IconButton
action={ButtonAction.Base}
href={`${mintscanBase}/account/${dydxAddress}`}
iconName={IconName.LinkOut}
shape={ButtonShape.Square}
type={ButtonType.Link}
/>
</WithTooltip>
</Styled.AddressRow>
<Styled.AddressRow>
{walletType && (
@@ -127,6 +128,7 @@ export const AccountMenu = () => {
asset={DydxChainAsset.CHAINTOKEN}
dispatch={dispatch}
hasBalance={nativeTokenBalance.gt(0)}
stringGetter={stringGetter}
/>
</div>
<div>
@@ -148,6 +150,7 @@ export const AccountMenu = () => {
asset={DydxChainAsset.USDC}
dispatch={dispatch}
hasBalance={MustBigNumber(usdcBalance).gt(0)}
stringGetter={stringGetter}
withOnboarding
/>
</div>
@@ -167,6 +170,12 @@ export const AccountMenu = () => {
onSelect: onRecoverKeys,
separator: true,
},
{
value: 'Preferences',
icon: <Icon iconName={IconName.Gear} />,
label: stringGetter({ key: STRING_KEYS.PREFERENCES }),
onSelect: () => dispatch(openDialog({ type: DialogTypes.Preferences })),
},
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
!isMainnet && {
@@ -184,12 +193,6 @@ export const AccountMenu = () => {
},
].filter(isTruthy)
: []),
{
value: 'Preferences',
icon: <Icon iconName={IconName.Gear} />,
label: stringGetter({ key: STRING_KEYS.PREFERENCES }),
onSelect: () => dispatch(openDialog({ type: DialogTypes.Preferences })),
},
{
value: 'Disconnect',
icon: <Icon iconName={IconName.BoxClose} />,
@@ -217,38 +220,48 @@ const AssetActions = memo(
dispatch,
withOnboarding,
hasBalance,
stringGetter,
}: {
asset: DydxChainAsset;
dispatch: Dispatch;
withOnboarding?: boolean;
hasBalance?: boolean;
stringGetter: StringGetterFunction;
}) => (
<Styled.InlineRow>
{[
withOnboarding && {
dialogType: DialogTypes.Deposit,
iconName: IconName.Deposit,
tooltipStringKey: STRING_KEYS.DEPOSIT,
},
withOnboarding &&
hasBalance && {
dialogType: DialogTypes.Withdraw,
iconName: IconName.Withdraw,
tooltipStringKey: STRING_KEYS.WITHDRAW,
},
withOnboarding && {
dialogType: DialogTypes.Deposit,
iconName: IconName.Deposit,
},
hasBalance && {
dialogType: DialogTypes.Transfer,
dialogProps: { selectedAsset: asset },
iconName: IconName.Send,
tooltipStringKey: STRING_KEYS.TRANSFER,
},
]
.filter(isTruthy)
.map(({ iconName, dialogType, dialogProps }) => (
<Styled.IconButton
key={dialogType}
action={ButtonAction.Base}
shape={ButtonShape.Square}
iconName={iconName}
onClick={() => dispatch(openDialog({ type: dialogType, dialogProps }))}
/>
.map(({ iconName, tooltipStringKey, dialogType, dialogProps }) => (
<Styled.WithTooltip
key={tooltipStringKey}
tooltipString={stringGetter({ key: tooltipStringKey })}
>
<Styled.IconButton
key={dialogType}
action={ButtonAction.Base}
shape={ButtonShape.Square}
iconName={iconName}
onClick={() => dispatch(openDialog({ type: dialogType, dialogProps }))}
/>
</Styled.WithTooltip>
))}
</Styled.InlineRow>
)
@@ -370,12 +383,22 @@ Styled.ConnectToChain = styled(Styled.Column)`
}
`;
Styled.IconButton = styled(IconButton)`
Styled.IconButton = styled(IconButton)<{ iconName: IconName }>`
--button-padding: 0 0.25rem;
--button-border: solid var(--border-width) var(--color-layer-6);
${({ iconName }) =>
[IconName.Withdraw, IconName.Deposit].includes(iconName) &&
css`
--button-icon-size: 1.375em;
`}
`;
Styled.CopyButton = styled(CopyButton)`
--button-padding: 0 0.25rem;
--button-border: solid var(--border-width) var(--color-layer-6);
`;
Styled.WithTooltip = styled(WithTooltip)`
--tooltip-backgroundColor: var(--color-layer-5);
`;
+19 -13
View File
@@ -67,7 +67,8 @@ export const MarketsTable = ({ className }: { className?: string }) => {
<Styled.Output
type={OutputType.Percent}
value={MustBigNumber(priceChange24HPercent).abs()}
isNegative={MustBigNumber(priceChange24H).isNegative()}
isPositive={MustBigNumber(priceChange24HPercent).gt(0)}
isNegative={MustBigNumber(priceChange24HPercent).isNegative()}
/>
</>
)}
@@ -96,19 +97,20 @@ export const MarketsTable = ({ className }: { className?: string }) => {
),
},
{
columnKey: 'priceChange24H',
getCellValue: (row) => row.priceChange24H,
columnKey: 'priceChange24HPercent',
getCellValue: (row) => row.priceChange24HPercent,
label: stringGetter({ key: STRING_KEYS.CHANGE_24H }),
renderCell: ({ priceChange24H, priceChange24HPercent, tickSizeDecimals }) => (
<TableCell stacked>
<Styled.InlineRow>
{!priceChange24H ? (
{!priceChange24HPercent ? (
<Output type={OutputType.Text} value={null} />
) : (
<Styled.Output
type={OutputType.Percent}
value={MustBigNumber(priceChange24HPercent).abs()}
isNegative={MustBigNumber(priceChange24H).isNegative()}
isPositive={MustBigNumber(priceChange24HPercent).gt(0)}
isNegative={MustBigNumber(priceChange24HPercent).isNegative()}
/>
)}
</Styled.InlineRow>
@@ -128,26 +130,25 @@ export const MarketsTable = ({ className }: { className?: string }) => {
<Styled.Output
type={OutputType.SmallPercent}
value={row.nextFundingRate}
isPositive={MustBigNumber(row.nextFundingRate).gt(0)}
isNegative={MustBigNumber(row.nextFundingRate).isNegative()}
/>
),
},
{
columnKey: 'openInterest',
getCellValue: (row) => row.openInterest,
getCellValue: (row) => row.openInterestUSDC,
label: stringGetter({ key: STRING_KEYS.OPEN_INTEREST }),
renderCell: (row) => (
<TableCell stacked>
<Output type={OutputType.Fiat} value={row.openInterestUSDC} />
<Output
fractionDigits={LARGE_TOKEN_DECIMALS}
type={OutputType.Number}
value={row.openInterest}
tag={row.asset?.id}
/>
<Output
type={OutputType.Fiat}
value={MustBigNumber(row.openInterest).times(MustBigNumber(row.oraclePrice))}
/>
</TableCell>
),
},
@@ -155,7 +156,7 @@ export const MarketsTable = ({ className }: { className?: string }) => {
columnKey: 'volume24H',
getCellValue: (row) => row.volume24H,
label: stringGetter({ key: STRING_KEYS.VOLUME_24H }),
renderCell: (row) => <Output type={OutputType.Number} value={row.volume24H} />,
renderCell: (row) => <Output type={OutputType.Fiat} value={row.volume24H} />,
},
{
columnKey: 'trades24H',
@@ -241,6 +242,11 @@ Styled.TabletPriceChange = styled(Styled.InlineRow)`
font: var(--font-small-book);
`;
Styled.Output = styled(Output)<{ isNegative?: boolean }>`
color: ${({ isNegative }) => (isNegative ? `var(--color-negative)` : `var(--color-positive)`)};
Styled.Output = styled(Output)<{ isNegative?: boolean; isPositive?: boolean }>`
color: ${({ isNegative, isPositive }) =>
isNegative
? `var(--color-negative)`
: isPositive
? `var(--color-positive)`
: `var(--color-text-1)`};
`;
+22 -20
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
import { OrderSide } from '@dydxprotocol/v4-client-js';
@@ -9,13 +9,13 @@ import { STRING_KEYS } from '@/constants/localization';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { calculateCanViewAccount } from '@/state/accountCalculators';
import { setTradeFormInputs } from '@/state/inputs';
import { getSubaccountOpenOrdersBySideAndPrice } from '@/state/accountSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getCurrentInput } from '@/state/inputsSelectors';
import abacusStateManager from '@/lib/abacus';
import { MustBigNumber } from '@/lib/numbers';
import { Details } from '@/components/Details';
@@ -208,7 +208,6 @@ const OrderbookTable = ({
// Style props
histogramSide={histogramSide}
hideHeader={hideHeader}
withFocusStickyRows
style={{
'--histogram-range': histogramRange,
}}
@@ -223,11 +222,12 @@ export const Orderbook = ({
maxRowsPerSide = ORDERBOOK_MAX_ROWS_PER_SIDE,
hideHeader = false,
}: ElementProps & StyleProps) => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { isTablet } = useBreakpoints();
const currentInput = useSelector(getCurrentInput);
const { symbol = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
@@ -274,18 +274,16 @@ export const Orderbook = ({
const onRowAction = useCallback(
(key: string, row: RowData) => {
if (currentInput === 'trade' && key !== 'spread' && row?.price)
abacusStateManager.setTradeValue({
value: row?.price,
field: TradeInputField.limitPrice,
});
if (currentInput === 'trade' && key !== 'spread' && row?.price) {
dispatch(setTradeFormInputs({ limitPriceInput: row?.price?.toString() }));
}
},
[currentInput]
);
const orderbookTableProps = {
showMineColumn,
symbol,
symbol: id,
stepSizeDecimals,
tickSizeDecimals,
histogramRange,
@@ -510,17 +508,21 @@ Styled.OrderbookTable = styled(OrderbookTradesTable)<StyleProps>`
content: '';
}
&:not(:active):is(:focus-visible, :focus-within) {
${orderbookMixins.scrollSnapItem}
z-index: 2;
${({ withFocusStickyRows }) =>
withFocusStickyRows &&
css`
&:not(:active):is(:focus-visible, :focus-within) {
${orderbookMixins.scrollSnapItem}
z-index: 2;
&[data-side='bid'] {
top: calc(var(--stickyArea-totalInsetTop) + var(--orderbook-spreadRowHeight));
}
&[data-side='ask'] {
bottom: calc(var(--stickyArea-totalInsetBottom) + var(--orderbook-spreadRowHeight));
}
}
&[data-side='bid'] {
top: calc(var(--stickyArea-totalInsetTop) + var(--orderbook-spreadRowHeight));
}
&[data-side='ask'] {
bottom: calc(var(--stickyArea-totalInsetBottom) + var(--orderbook-spreadRowHeight));
}
}
`}
}
${Styled.HorizontalLayout} & {