Compare commits

...
Author SHA1 Message Date
John Huang b220203c91 Indexer and validator are on https for DEV 2023-10-23 14:26:14 -07:00
Bill 5ca923984d Update acknowledge copies (#94)
* update acknowledgement dialog with new copies

* update
2023-10-23 13:59:04 -07:00
Bill d97626f543 Fix auto-deposit (#93) 2023-10-23 12:02:05 -07:00
Jared Vu 835f82dbcc Support Intercom (#92)
* Add ChatIcon

* FooterDesktop Help&Support

* HelpDialog w/ triggers

* Add other links

* item gap css var passing

* inject script

* Add quotes to script

* Close modal on select

* Remove feedback form
2023-10-20 16:30:22 -07:00
Bill 051bdda73a update v4-client init in useDydxClient (#91)
* update v4-client init in useDydxClient

* fix nit
2023-10-20 11:00:03 -07:00
Bill 27b79957d4 Bump v4 clients (#89)
* Have tokens be configurable, bump v4-client

* bump

* chain -> chaintoken

* use cancelRawOrder

* address comments

* add TODO
2023-10-19 11:01:12 -07:00
aleka 4bacc901dc use wallet connect config from envs (#88)
* use wallet connect config from envs

* update readme
2023-10-19 11:35:17 -04:00
aleka 82d32a91a9 fix dialog not showing up (#87) 2023-10-19 07:16:13 -07:00
aleka 2a794f9459 add empty terms + privacy pages (#80)
* add empty terms + privacy pages

* nit
2023-10-18 19:44:15 -04:00
lucas-dydx 5c26a0c270 Update dev2 validator IP address (#86) 2023-10-18 12:38:08 -07:00
Jared Vu e4991cc31d Update Abacus 0.7.0 & Misc. bug fixes (#84)
* Add Notification icon w/ stroke

* Update to abacus0.7.0

* Fix goodTilTime string

* Fix tradingView initial load

* Temp remove notif onClick

* remove isConnected state from dydxClient
2023-10-18 11:08:39 -07:00
aleka da908c826a Revert "Revert "remove offline message for testnet4 (#76)" (#78)" (#85)
This reverts commit 14316163e4.
2023-10-18 13:04:25 -04:00
57 changed files with 846 additions and 417 deletions
+10 -2
View File
@@ -11,6 +11,7 @@
## Prerequisites ## Prerequisites
- Node.js version 18 and `pnpm` installed on your system - Node.js version 18 and `pnpm` installed on your system
- Wallet Connect account
For deploying with Vercel, create an account with [Vercel](https://vercel.com/signup) if you don't have one already. For deploying with Vercel, create an account with [Vercel](https://vercel.com/signup) if you don't have one already.
@@ -61,7 +62,14 @@ pnpm ladle
This will automatically open your default browser at `http://localhost:61000`. This will automatically open your default browser at `http://localhost:61000`.
## Part 3: Deploying with Vercel ## Part 3: Configuring environment
Add or modify the relevant endpoints, links and options in `/public/configs/env.json`.
You'll need to provide a Wallet Connect project id to enable onboarding and wallet connection:
- 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)
## Part 4: Deploying with Vercel
### Step 1: Connect your repository to Vercel ### Step 1: Connect your repository to Vercel
@@ -78,7 +86,7 @@ If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our
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 4: Deploying to IPFS ## Part 5: 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
+4 -3
View File
@@ -13,6 +13,7 @@
"build:inject-app-deeplinks": "sh scripts/inject-app-deeplinks.sh", "build:inject-app-deeplinks": "sh scripts/inject-app-deeplinks.sh",
"build:inject-amplitude": "node scripts/inject-amplitude.js", "build:inject-amplitude": "node scripts/inject-amplitude.js",
"build:inject-bugsnag": "node scripts/inject-bugsnag.js", "build:inject-bugsnag": "node scripts/inject-bugsnag.js",
"build:inject-intercom": "node scripts/inject-intercom.js",
"build:inject-statuspage": "node scripts/inject-statuspage.js", "build:inject-statuspage": "node scripts/inject-statuspage.js",
"deploy:ipfs": "node scripts/upload-ipfs.js --verbose", "deploy:ipfs": "node scripts/upload-ipfs.js --verbose",
"deploy:update-ipns": "node scripts/update-ipns.js", "deploy:update-ipns": "node scripts/update-ipns.js",
@@ -38,9 +39,9 @@
"@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.6.4", "@dydxprotocol/v4-abacus": "^0.7.6",
"@dydxprotocol/v4-client-js": "^0.36.1", "@dydxprotocol/v4-client-js": "^0.40.3",
"@dydxprotocol/v4-localization": "^0.1.30", "@dydxprotocol/v4-localization": "^0.1.32",
"@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-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",
+25 -24
View File
@@ -27,14 +27,14 @@ dependencies:
specifier: ^0.31.0 specifier: ^0.31.0
version: 0.31.0 version: 0.31.0
'@dydxprotocol/v4-abacus': '@dydxprotocol/v4-abacus':
specifier: ^0.6.4 specifier: ^0.7.6
version: 0.6.4 version: 0.7.6
'@dydxprotocol/v4-client-js': '@dydxprotocol/v4-client-js':
specifier: ^0.36.1 specifier: ^0.40.3
version: 0.36.1 version: 0.40.3
'@dydxprotocol/v4-localization': '@dydxprotocol/v4-localization':
specifier: ^0.1.30 specifier: ^0.1.32
version: 0.1.30 version: 0.1.32
'@ethersproject/providers': '@ethersproject/providers':
specifier: ^5.7.2 specifier: ^5.7.2
version: 5.7.2 version: 5.7.2
@@ -645,7 +645,7 @@ packages:
/@confio/ics23@0.6.8: /@confio/ics23@0.6.8:
resolution: {integrity: sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==} resolution: {integrity: sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==}
dependencies: dependencies:
'@noble/hashes': 1.3.1 '@noble/hashes': 1.3.2
protobufjs: 6.11.4 protobufjs: 6.11.4
dev: false dev: false
@@ -717,7 +717,7 @@ packages:
'@cosmjs/encoding': 0.30.1 '@cosmjs/encoding': 0.30.1
'@cosmjs/math': 0.30.1 '@cosmjs/math': 0.30.1
'@cosmjs/utils': 0.30.1 '@cosmjs/utils': 0.30.1
'@noble/hashes': 1.3.1 '@noble/hashes': 1.3.2
bn.js: 5.2.1 bn.js: 5.2.1
elliptic: 6.5.4 elliptic: 6.5.4
libsodium-wrappers: 0.7.11 libsodium-wrappers: 0.7.11
@@ -979,12 +979,12 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true dev: true
/@dydxprotocol/v4-abacus@0.6.4: /@dydxprotocol/v4-abacus@0.7.6:
resolution: {integrity: sha512-eH+/9Q/OnQtwzBwM1BJyXybFPdivGmFQWrxM71MFWGs6uIQGbZX775mpoa8deweSTyQ/QrRLRQ1mP2BPDgBQYg==} resolution: {integrity: sha512-FdDXRaFMfxhyrJJs0dNrb1KG6X95lPHelua1yCn/SVgzwGaAqlJczrzmdh98INntuRAgIxOu69R5mCGyekumyg==}
dev: false dev: false
/@dydxprotocol/v4-client-js@0.36.1: /@dydxprotocol/v4-client-js@0.40.3:
resolution: {integrity: sha512-KJ3MtWI61wmm+xZtXQd7/hYKIoVFG32TRdyyVaXXO8vpW9Oqd0JD8QSM/wuegkciAh5YmP/BrDzYuu77zHO6yg==} resolution: {integrity: sha512-nMQd6tYisKDKIbfx0BGJSYEOSt+I+t5SsaBSmlgTdso+w2rCRO9vyxS+u6MF9h4w5FsVRfhHo3e6uTWX48WpGQ==}
dependencies: dependencies:
'@cosmjs/amino': 0.30.1 '@cosmjs/amino': 0.30.1
'@cosmjs/encoding': 0.31.1 '@cosmjs/encoding': 0.31.1
@@ -993,14 +993,15 @@ packages:
'@cosmjs/stargate': 0.30.1 '@cosmjs/stargate': 0.30.1
'@cosmjs/tendermint-rpc': 0.30.1 '@cosmjs/tendermint-rpc': 0.30.1
'@cosmjs/utils': 0.31.1 '@cosmjs/utils': 0.31.1
'@dydxprotocol/v4-proto': 0.2.1 '@dydxprotocol/v4-proto': 0.4.1
'@osmonauts/lcd': 0.6.0 '@osmonauts/lcd': 0.6.0
'@scure/bip32': 1.3.1 '@scure/bip32': 1.3.2
'@scure/bip39': 1.2.1 '@scure/bip39': 1.2.1
axios: 1.1.3 axios: 1.1.3
bech32: 1.1.4 bech32: 1.1.4
bignumber.js: 9.1.1 bignumber.js: 9.1.1
ethereum-cryptography: 2.1.2 ethereum-cryptography: 2.1.2
ethers: 6.6.1
long: 4.0.0 long: 4.0.0
protobufjs: 6.11.4 protobufjs: 6.11.4
ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)
@@ -1010,12 +1011,12 @@ packages:
- utf-8-validate - utf-8-validate
dev: false dev: false
/@dydxprotocol/v4-localization@0.1.30: /@dydxprotocol/v4-localization@0.1.32:
resolution: {integrity: sha512-xTlQuQmPz7wfGkjd07giYMAxRrolDbJE3DryJSIMB6R1MBq1wLzAcwp/iR/nELRwilRX9c44Tap0gGt+oAajiQ==} resolution: {integrity: sha512-ZtXScxgFLjUt0Ag52RyFnQe9aEl1Aky5fp+vMGOBduVkFiCEMRMkSsSgg6grMdyw/x0AbnxOTRkhYoX2MsK9Ow==}
dev: false dev: false
/@dydxprotocol/v4-proto@0.2.1: /@dydxprotocol/v4-proto@0.4.1:
resolution: {integrity: sha512-Q5b/g331DoyMjBpO2/jtrxUX3WF5UaIKZp/unK332xuefHNFnkMYtZ05p0V71iSdO35tBdz8yt0VYP2WtUsp+w==} resolution: {integrity: sha512-2pBkokufWcxfIpiXE6QuQOBXUe154OTp6ojCIdOPW9TFLxzchoygxXnb5dL6rAQVNj9Qbg+hhXBllutt4m99dw==}
dependencies: dependencies:
protobufjs: 6.11.4 protobufjs: 6.11.4
dev: false dev: false
@@ -4708,8 +4709,8 @@ packages:
resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==} resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==}
dependencies: dependencies:
'@noble/curves': 1.1.0 '@noble/curves': 1.1.0
'@noble/hashes': 1.3.1 '@noble/hashes': 1.3.2
'@scure/base': 1.1.1 '@scure/base': 1.1.3
dev: false dev: false
/@scure/bip32@1.3.2: /@scure/bip32@1.3.2:
@@ -4731,7 +4732,7 @@ packages:
resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==}
dependencies: dependencies:
'@noble/hashes': 1.3.2 '@noble/hashes': 1.3.2
'@scure/base': 1.1.1 '@scure/base': 1.1.3
dev: false dev: false
/@solana/buffer-layout@4.0.1: /@solana/buffer-layout@4.0.1:
@@ -4745,8 +4746,8 @@ packages:
resolution: {integrity: sha512-up5VG1dK+GPhykmuMIozJZBbVqpm77vbOG6/r5dS7NBGZonwHfTLdBbsYc3rjmaQ4DpCXUa3tUc4RZHRORvZrw==} resolution: {integrity: sha512-up5VG1dK+GPhykmuMIozJZBbVqpm77vbOG6/r5dS7NBGZonwHfTLdBbsYc3rjmaQ4DpCXUa3tUc4RZHRORvZrw==}
dependencies: dependencies:
'@babel/runtime': 7.22.10 '@babel/runtime': 7.22.10
'@noble/curves': 1.1.0 '@noble/curves': 1.2.0
'@noble/hashes': 1.3.1 '@noble/hashes': 1.3.2
'@solana/buffer-layout': 4.0.1 '@solana/buffer-layout': 4.0.1
agentkeepalive: 4.5.0 agentkeepalive: 4.5.0
bigint-buffer: 1.1.5 bigint-buffer: 1.1.5
@@ -7144,7 +7145,7 @@ packages:
/bip39@3.1.0: /bip39@3.1.0:
resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==}
dependencies: dependencies:
'@noble/hashes': 1.3.1 '@noble/hashes': 1.3.2
dev: false dev: false
/bl@5.1.0: /bl@5.1.0:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

+18 -18
View File
@@ -40,7 +40,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -56,15 +56,15 @@
"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" "faucet": "https://faucet.v4dev.dydx.exchange"
}, },
"links": { "links": {
"tos": "https://dydx.exchange/v4-terms", "tos": "https://dydx.exchange/v4-terms",
@@ -100,7 +100,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -121,7 +121,7 @@
} }
], ],
"validators": [ "validators": [
"http://35.75.227.118" "http://54.92.118.111"
], ],
"0xsquid": "https://testnet.api.0xsquid.com" "0xsquid": "https://testnet.api.0xsquid.com"
}, },
@@ -159,7 +159,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -218,7 +218,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -277,7 +277,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -340,7 +340,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -403,7 +403,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -468,7 +468,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -532,7 +532,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -596,7 +596,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -660,7 +660,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -724,7 +724,7 @@
"isMainNet": false, "isMainNet": false,
"tokens": { "tokens": {
"chain": { "chain": {
"name": "DYDX", "name": "Dv4TNT",
"denom": "adv4tnt", "denom": "adv4tnt",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
@@ -789,8 +789,8 @@
"tokens": { "tokens": {
"comment": "Change according to mainnet release", "comment": "Change according to mainnet release",
"chain": { "chain": {
"name": "DYDX", "name": "TokenName",
"denom": "adv4tnt", "denom": "tokenDenom",
"decimals": 18, "decimals": 18,
"image": "/currencies/dydx.png" "image": "/currencies/dydx.png"
}, },
+76
View File
@@ -0,0 +1,76 @@
/* eslint-disable no-console */
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const INTERCOM_APP_ID = process.env.INTERCOM_APP_ID;
const currentPath = fileURLToPath(import.meta.url);
const projectRoot = path.dirname(currentPath);
const htmlFilePath = path.resolve(projectRoot, '../dist/index.html');
if (INTERCOM_APP_ID) {
try {
const html = await fs.readFile(htmlFilePath, 'utf-8');
const intercomScripts = `
<!-- Intercom -->
<script>
window.intercomSettings = {
api_base: 'https://api-iam.intercom.io',
app_id: '${INTERCOM_APP_ID}',
custom_launcher_selector: '.custom_intercom',
hide_default_launcher: true,
};
</script>
<script>
// We pre-filled your app ID in the widget URL: 'https://widget.intercom.io/widget/${INTERCOM_APP_ID}'
(function () {
var w = window;
var ic = w.Intercom;
if (typeof ic === 'function') {
ic('reattach_activator');
ic('update', w.intercomSettings);
} else {
var d = document;
var i = function () {
i.c(arguments);
};
i.q = [];
i.c = function (args) {
i.q.push(args);
};
w.Intercom = i;
var l = function () {
var s = d.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = 'https://widget.intercom.io/widget/${INTERCOM_APP_ID}';
var x = d.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
};
if (document.readyState === 'complete') {
l();
} else if (w.attachEvent) {
w.attachEvent('onload', l);
} else {
w.addEventListener('load', l, false);
}
}
})();
</script>
`;
const injectedHtml = html.replace(
'<div id="root"></div>',
`<div id="root"></div>\n${intercomScripts}\n`
);
await fs.writeFile(htmlFilePath, injectedHtml, 'utf-8');
console.log('Intercom scripts successfully injected.');
} catch (err) {
console.error('Error injecting Intercom scripts:', err);
}
}
+14 -2
View File
@@ -7,7 +7,13 @@ import { GrazProvider } from 'graz';
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes'; import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
import { useBreakpoints, useInitializePage, useShouldShowFooter, useAnalytics } from '@/hooks'; import {
useBreakpoints,
useTokenConfigs,
useInitializePage,
useShouldShowFooter,
useAnalytics,
} from '@/hooks';
import { DydxProvider } from '@/hooks/useDydxClient'; import { DydxProvider } from '@/hooks/useDydxClient';
import { AccountsProvider } from '@/hooks/useAccounts'; import { AccountsProvider } from '@/hooks/useAccounts';
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea'; import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
@@ -27,6 +33,8 @@ import ProfilePage from '@/pages/Profile';
import { SettingsPage } from '@/pages/settings/Settings'; import { SettingsPage } from '@/pages/settings/Settings';
import TradePage from '@/pages/trade/Trade'; import TradePage from '@/pages/trade/Trade';
import { RewardsPage } from '@/pages/rewards/RewardsPage'; 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';
@@ -55,6 +63,7 @@ const Content = () => {
const { isTablet, isNotTablet } = useBreakpoints(); const { isTablet, isNotTablet } = useBreakpoints();
const isShowingHeader = isNotTablet; const isShowingHeader = isNotTablet;
const isShowingFooter = useShouldShowFooter(); const isShowingFooter = useShouldShowFooter();
const { chainTokenLabel } = useTokenConfigs();
return ( return (
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}> <Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
@@ -69,7 +78,7 @@ const Content = () => {
</Route> </Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} /> <Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={AppRoute.Rewards} element={<RewardsPage />} /> <Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && ( {isTablet && (
<> <>
<Route path={AppRoute.Alerts} element={<AlertsPage />} /> <Route path={AppRoute.Alerts} element={<AlertsPage />} />
@@ -82,6 +91,9 @@ const Content = () => {
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} /> <Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route> </Route>
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<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>
+34 -16
View File
@@ -3,31 +3,43 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { type MenuConfig } from '@/constants/menus'; import { type MenuConfig } from '@/constants/menus';
import { Dialog, DialogPlacement } from '@/components/Dialog'; import { Dialog, DialogPlacement, type DialogProps } from '@/components/Dialog';
import { ComboboxMenu } from '@/components/ComboboxMenu'; import { ComboboxMenu, type ComboboxMenuProps } from '@/components/ComboboxMenu';
type ElementProps<MenuItemValue extends string | number, MenuGroupValue extends string | number> = { type ElementProps<MenuItemValue extends string | number, MenuGroupValue extends string | number> = {
isOpen?: boolean;
setIsOpen?: (open: boolean) => void;
title?: React.ReactNode; title?: React.ReactNode;
description?: React.ReactNode; description?: React.ReactNode;
slotTrigger?: React.ReactNode;
slotHeaderInner?: React.ReactNode;
slotFooter?: React.ReactNode;
children?: React.ReactNode; children?: React.ReactNode;
items: MenuConfig<MenuItemValue, MenuGroupValue>; items: MenuConfig<MenuItemValue, MenuGroupValue>;
onItemSelected?: () => void;
inputPlaceholder?: string;
slotEmpty?: React.ReactNode;
}; };
type StyleProps = { type StyleProps = {
placement: DialogPlacement,
className?: string; className?: string;
}; };
export const ComboboxDialogMenu = <MenuItemValue extends string | number, MenuGroupValue extends string | number>({ type PickComboxMenuProps<
MenuItemValue extends string | number,
MenuGroupValue extends string | number
> = Pick<
ComboboxMenuProps<MenuItemValue, MenuGroupValue>,
'inputPlaceholder' | 'onItemSelected' | 'slotEmpty' | 'withSearch' | 'withStickyLayout'
>;
type PickDialogProps = Pick<
DialogProps,
| 'description'
| 'isOpen'
| 'placement'
| 'setIsOpen'
| 'slotHeaderInner'
| 'slotTrigger'
| 'slotFooter'
>;
export const ComboboxDialogMenu = <
MenuItemValue extends string | number,
MenuGroupValue extends string | number
>({
isOpen = false, isOpen = false,
setIsOpen, setIsOpen,
title, title,
@@ -40,11 +52,16 @@ export const ComboboxDialogMenu = <MenuItemValue extends string | number, MenuGr
onItemSelected, onItemSelected,
inputPlaceholder, inputPlaceholder,
slotEmpty, slotEmpty,
withSearch,
withStickyLayout = true,
children, children,
placement = DialogPlacement.Default, placement = DialogPlacement.Default,
className, className,
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => ( }: ElementProps<MenuItemValue, MenuGroupValue> &
PickComboxMenuProps<MenuItemValue, MenuGroupValue> &
PickDialogProps &
StyleProps) => (
// TODO: sub-menu state management // TODO: sub-menu state management
<Styled.Dialog <Styled.Dialog
isOpen={isOpen} isOpen={isOpen}
@@ -54,7 +71,6 @@ export const ComboboxDialogMenu = <MenuItemValue extends string | number, MenuGr
slotHeaderInner={slotHeaderInner} slotHeaderInner={slotHeaderInner}
slotTrigger={slotTrigger} slotTrigger={slotTrigger}
slotFooter={slotFooter} slotFooter={slotFooter}
placement={placement} placement={placement}
className={className} className={className}
> >
@@ -64,7 +80,8 @@ export const ComboboxDialogMenu = <MenuItemValue extends string | number, MenuGr
title={title} title={title}
inputPlaceholder={inputPlaceholder} inputPlaceholder={inputPlaceholder}
slotEmpty={slotEmpty} slotEmpty={slotEmpty}
withStickyLayout withSearch={withSearch}
withStickyLayout={withStickyLayout}
/> />
{children} {children}
</Styled.Dialog> </Styled.Dialog>
@@ -98,4 +115,5 @@ Styled.Dialog = styled(Dialog)`
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);
`; `;
+10 -5
View File
@@ -8,7 +8,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { Tag } from '@/components/Tag'; import { Tag } from '@/components/Tag';
type ElementProps<MenuItemValue extends string, MenuGroupValue extends string> = { type ElementProps<MenuItemValue extends string | number, MenuGroupValue extends string | number> = {
items: MenuConfig<MenuItemValue, MenuGroupValue>; items: MenuConfig<MenuItemValue, MenuGroupValue>;
onItemSelected?: () => void; onItemSelected?: () => void;
@@ -23,6 +23,11 @@ type StyleProps = {
withStickyLayout?: boolean; withStickyLayout?: boolean;
}; };
export type ComboboxMenuProps<
MenuItemValue extends string | number,
MenuGroupValue extends string | number
> = ElementProps<MenuItemValue, MenuGroupValue> & StyleProps;
export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extends string>({ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extends string>({
items, items,
onItemSelected, onItemSelected,
@@ -34,7 +39,7 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
className, className,
withStickyLayout, withStickyLayout,
}: ElementProps<MenuItemValue, MenuGroupValue> & StyleProps) => { }: 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); // const inputRef = useRef<HTMLInputElement | null>(null);
@@ -171,6 +176,7 @@ Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
--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;
display: grid; display: grid;
align-content: start; align-content: start;
@@ -269,18 +275,17 @@ Styled.List = styled(Command.List)<{ $withStickyLayout?: boolean }>`
`; `;
Styled.Item = styled(Command.Item)` Styled.Item = styled(Command.Item)`
${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);
${layoutMixins.scrollSnapItem}
background-color: var(--comboboxMenu-backgroundColor, inherit); background-color: var(--comboboxMenu-backgroundColor, inherit);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem;
&[aria-disabled='true'] { &[aria-disabled='true'] {
opacity: 0.75; opacity: 0.75;
+3 -1
View File
@@ -48,6 +48,8 @@ type StyleProps = {
className?: string; className?: string;
}; };
export type DialogProps = ElementProps & StyleProps;
const DialogPortal = ({ const DialogPortal = ({
withPortal, withPortal,
container, container,
@@ -82,7 +84,7 @@ export const Dialog = ({
hasHeaderBorder = false, hasHeaderBorder = false,
children, children,
className, className,
}: ElementProps & StyleProps) => { }: DialogProps) => {
const closeButtonRef = useRef<HTMLButtonElement>(); const closeButtonRef = useRef<HTMLButtonElement>();
const showOverlay = ![DialogPlacement.Inline, DialogPlacement.FullScreen].includes(placement); const showOverlay = ![DialogPlacement.Inline, DialogPlacement.FullScreen].includes(placement);
+11 -1
View File
@@ -6,11 +6,14 @@ import {
AddressConnectorIcon, AddressConnectorIcon,
ArrowIcon, ArrowIcon,
Bar3Icon, Bar3Icon,
BellIcon,
BellStrokeIcon,
BoxCloseIcon, BoxCloseIcon,
CalculatorIcon, CalculatorIcon,
CaretIcon, CaretIcon,
CautionCircleStrokeIcon, CautionCircleStrokeIcon,
CautionCircleIcon, CautionCircleIcon,
ChatIcon,
CheckIcon, CheckIcon,
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
@@ -71,11 +74,14 @@ export enum IconName {
AddressConnector = 'AddressConnector', AddressConnector = 'AddressConnector',
Arrow = 'Arrow', Arrow = 'Arrow',
Bar3 = 'Bar3', Bar3 = 'Bar3',
Bell = 'Bell',
BellStroked = 'BellStroked',
BoxClose = 'BoxClose', BoxClose = 'BoxClose',
Calculator = 'Calculator', Calculator = 'Calculator',
Caret = 'Caret', Caret = 'Caret',
CautionCircle = 'CautionCircle', CautionCircle = 'CautionCircle',
CautionCircleStroked = 'CautionCircleStroked', CautionCircleStroked = 'CautionCircleStroked',
Chat = 'Chat',
Check = 'Check', Check = 'Check',
ChevronLeft = 'ChevronLeft', ChevronLeft = 'ChevronLeft',
ChevronRight = 'ChevronRight', ChevronRight = 'ChevronRight',
@@ -137,11 +143,14 @@ const icons = {
[IconName.AddressConnector]: AddressConnectorIcon, [IconName.AddressConnector]: AddressConnectorIcon,
[IconName.Arrow]: ArrowIcon, [IconName.Arrow]: ArrowIcon,
[IconName.Bar3]: Bar3Icon, [IconName.Bar3]: Bar3Icon,
[IconName.Bell]: BellIcon,
[IconName.BellStroked]: BellStrokeIcon,
[IconName.BoxClose]: BoxCloseIcon, [IconName.BoxClose]: BoxCloseIcon,
[IconName.Calculator]: CalculatorIcon, [IconName.Calculator]: CalculatorIcon,
[IconName.Caret]: CaretIcon, [IconName.Caret]: CaretIcon,
[IconName.CautionCircle]: CautionCircleIcon, [IconName.CautionCircle]: CautionCircleIcon,
[IconName.CautionCircleStroked]: CautionCircleStrokeIcon, [IconName.CautionCircleStroked]: CautionCircleStrokeIcon,
[IconName.Chat]: ChatIcon,
[IconName.Check]: CheckIcon, [IconName.Check]: CheckIcon,
[IconName.ChevronLeft]: ChevronLeftIcon, [IconName.ChevronLeft]: ChevronLeftIcon,
[IconName.ChevronRight]: ChevronRightIcon, [IconName.ChevronRight]: ChevronRightIcon,
@@ -213,7 +222,8 @@ export const Icon = styled(
iconComponent: Component = iconName && icons[iconName], iconComponent: Component = iconName && icons[iconName],
className, className,
...props ...props
}: ElementProps & StyleProps) => (Component ? <Component className={className} {...props} /> : null) }: ElementProps & StyleProps) =>
Component ? <Component className={className} {...props} /> : null
)` )`
width: 1em; width: 1em;
height: 1em; height: 1em;
+17 -10
View File
@@ -59,16 +59,6 @@ export const TransactionType = Abacus.exchange.dydx.abacus.protocols.Transaction
const transactionTypes = [...TransactionType.values()] as const; const transactionTypes = [...TransactionType.values()] as const;
export type TransactionTypes = (typeof transactionTypes)[number]; export type TransactionTypes = (typeof transactionTypes)[number];
export type NetworkConfig = {
chainId: string;
indexerUrl: string;
indexerSocketUrl: string;
validatorUrl: string;
faucetUrl?: string | null;
};
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
// ------ State ------ // ------ State ------
export type AbacusApiState = Abacus.exchange.dydx.abacus.state.manager.ApiState; export type AbacusApiState = Abacus.exchange.dydx.abacus.state.manager.ApiState;
export const AbacusApiStatus = Abacus.exchange.dydx.abacus.state.manager.ApiStatus; export const AbacusApiStatus = Abacus.exchange.dydx.abacus.state.manager.ApiStatus;
@@ -267,3 +257,20 @@ export const TRADE_TYPES: Record<
[AbacusOrderType.liquidation.name]: null, [AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.trailingStop.name]: null, [AbacusOrderType.trailingStop.name]: null,
}; };
// Custom types involving Abacus
export type NetworkConfig = Partial<{
indexerUrl: Nullable<string>;
websocketUrl: Nullable<string>;
validatorUrl: Nullable<string>;
chainId: Nullable<string>;
faucetUrl: Nullable<string>;
USDC_DENOM: Nullable<string>;
USDC_DECIMALS: Nullable<number>;
USDC_GAS_DENOM: Nullable<string>;
CHAINTOKEN_DENOM: Nullable<string>;
CHAINTOKEN_DECIMALS: Nullable<number>;
}>;
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
+1 -1
View File
@@ -44,4 +44,4 @@ export type EvmDerivedAddresses = {
}; };
}; };
export const AMOUNT_RESERVED_FOR_GAS_USDC = 100_000; export const AMOUNT_RESERVED_FOR_GAS_USDC = 0.1;
+1 -1
View File
@@ -21,7 +21,7 @@ export type MenuItem<MenuItemValue, MenuItemTypes = string> = {
value: MenuItemValue; value: MenuItemValue;
slotBefore?: React.ReactNode; slotBefore?: React.ReactNode;
label: string; label: React.ReactNode;
labelRight?: React.ReactNode; labelRight?: React.ReactNode;
tag?: React.ReactNode; tag?: React.ReactNode;
slotAfter?: React.ReactNode; slotAfter?: React.ReactNode;
+2 -1
View File
@@ -7,7 +7,8 @@ export enum AppRoute {
Profile = '/profile', Profile = '/profile',
Alerts = '/alerts', Alerts = '/alerts',
Settings = '/settings', Settings = '/settings',
Rewards = '/DV4TNT', Terms = '/terms',
Privacy = '/privacy',
} }
export enum PortfolioRoute { export enum PortfolioRoute {
+2 -12
View File
@@ -1,5 +1,5 @@
import type { ExternalProvider } from '@ethersproject/providers'; import type { ExternalProvider } from '@ethersproject/providers';
import { USDC_DENOM, type onboarding, DYDX_DENOM } from '@dydxprotocol/v4-client-js'; import { type onboarding } from '@dydxprotocol/v4-client-js';
import type { suggestChain } from 'graz'; import type { suggestChain } from 'graz';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
@@ -352,15 +352,5 @@ export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
// TODO: export this type from abacus instead // TODO: export this type from abacus instead
export enum DydxChainAsset { export enum DydxChainAsset {
USDC = 'usdc', USDC = 'usdc',
DYDX = 'dydx', CHAINTOKEN = 'chain',
} }
export const DYDX_CHAIN_ASSET_COIN_DENOM: Record<DydxChainAsset, string> = {
[DydxChainAsset.USDC]: USDC_DENOM,
[DydxChainAsset.DYDX]: DYDX_DENOM,
};
export const DYDX_CHAIN_ASSET_TAGS: Record<DydxChainAsset, string> = {
[DydxChainAsset.USDC]: 'USDC',
[DydxChainAsset.DYDX]: 'Dv4TNT',
};
+2
View File
@@ -1,5 +1,6 @@
import { useApiState } from './useApiState'; import { useApiState } from './useApiState';
import { useBreakpoints } from './useBreakpoints'; import { useBreakpoints } from './useBreakpoints';
import { useTokenConfigs } from './useTokenConfigs';
import { useCommandMenu } from './useCommandMenu'; import { useCommandMenu } from './useCommandMenu';
import { useCurrentMarketId } from './useCurrentMarketId'; import { useCurrentMarketId } from './useCurrentMarketId';
import { useDebounce } from './useDebounce'; import { useDebounce } from './useDebounce';
@@ -25,6 +26,7 @@ import { useSubaccount } from './useSubaccount';
export { export {
useApiState, useApiState,
useBreakpoints, useBreakpoints,
useTokenConfigs,
useCommandMenu, useCommandMenu,
useCurrentMarketId, useCurrentMarketId,
useDebounce, useDebounce,
+3 -3
View File
@@ -33,7 +33,7 @@ export const useTradingView = ({
const marketIds = useSelector(getMarketIds, shallowEqual); const marketIds = useSelector(getMarketIds, shallowEqual);
const selectedLocale = useSelector(getSelectedLocale); const selectedLocale = useSelector(getSelectedLocale);
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const { getCandlesForDatafeed } = useDydxClient(); const { getCandlesForDatafeed, isConnected: isClientConnected } = useDydxClient();
const [savedTvChartConfig, setTvChartConfig] = useLocalStorage<object | undefined>({ const [savedTvChartConfig, setTvChartConfig] = useLocalStorage<object | undefined>({
key: LocalStorageKey.TradingViewChartConfig, key: LocalStorageKey.TradingViewChartConfig,
@@ -44,7 +44,7 @@ export const useTradingView = ({
const hasMarkets = marketIds.length > 0; const hasMarkets = marketIds.length > 0;
useEffect(() => { useEffect(() => {
if (hasMarkets) { if (hasMarkets && isClientConnected) {
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, hasMarkets, selectedLocale, selectedNetwork]); }, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork]);
return { savedResolution }; return { savedResolution };
}; };
+8 -12
View File
@@ -5,8 +5,6 @@ import { StargateClient } from '@cosmjs/stargate';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { formatUnits } from 'viem'; import { formatUnits } from 'viem';
import { USDC_DENOM, DYDX_DENOM } from '@dydxprotocol/v4-client-js';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers'; import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { EvmAddress } from '@/constants/wallets'; import { EvmAddress } from '@/constants/wallets';
@@ -18,6 +16,7 @@ import { getBalances, getStakingBalances } from '@/state/accountSelectors';
import { getSelectedNetwork } from '@/state/appSelectors'; import { getSelectedNetwork } from '@/state/appSelectors';
import { useAccounts } from './useAccounts'; import { useAccounts } from './useAccounts';
import { useTokenConfigs } from './useTokenConfigs';
type UseAccountBalanceProps = { type UseAccountBalanceProps = {
// Token Items // Token Items
@@ -52,6 +51,7 @@ export const useAccountBalance = ({
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const balances = useSelector(getBalances, shallowEqual); const balances = useSelector(getBalances, shallowEqual);
const { chainTokenDenom, usdcDenom } = useTokenConfigs();
const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId); const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const stakingBalances = useSelector(getStakingBalances, shallowEqual); const stakingBalances = useSelector(getStakingBalances, shallowEqual);
@@ -93,18 +93,14 @@ export const useAccountBalance = ({
const { formatted: evmBalance } = evmQuery.data || {}; const { formatted: evmBalance } = evmQuery.data || {};
const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance; const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance;
const nativeTokenCoinBalance = balances?.[DYDX_DENOM]; const nativeTokenCoinBalance = balances?.[chainTokenDenom];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount) const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
.div(QUANTUM_MULTIPLIER)
.toNumber();
const usdcCoinBalance = balances?.[USDC_DENOM]; const usdcCoinBalance = balances?.[usdcDenom];
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber(); const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).toNumber();
const nativeStakingCoinBalanace = stakingBalances?.[DYDX_DENOM]; const nativeStakingCoinBalanace = stakingBalances?.[chainTokenDenom];
const nativeStakingBalance = MustBigNumber(nativeStakingCoinBalanace?.amount) const nativeStakingBalance = MustBigNumber(nativeStakingCoinBalanace?.amount).toNumber();
.div(QUANTUM_MULTIPLIER)
.toNumber();
return { return {
balance, balance,
+2 -11
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, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-client-js'; import { 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';
@@ -130,16 +130,8 @@ const useAccountsContext = () => {
// dYdX subaccounts // dYdX subaccounts
const [dydxSubaccounts, setDydxSubaccounts] = useState<Subaccount[] | undefined>(); const [dydxSubaccounts, setDydxSubaccounts] = useState<Subaccount[] | undefined>();
const { getAccountBalance, getSubaccounts } = useMemo( const { getSubaccounts } = useMemo(
() => ({ () => ({
getAccountBalance: async ({
dydxAddress,
denom = USDC_DENOM,
}: {
dydxAddress: DydxAddress;
denom?: string;
}) => await compositeClient?.validatorClient.get.getAccountBalance(dydxAddress, denom),
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);
@@ -326,7 +318,6 @@ const useAccountsContext = () => {
disconnect, disconnect,
// dydxClient Account methods // dydxClient Account methods
getAccountBalance,
getSubaccounts, getSubaccounts,
}; };
}; };
+5 -2
View File
@@ -44,6 +44,9 @@ export const useCurrentMarketId = () => {
}, [validId]); }, [validId]);
useEffect(() => { useEffect(() => {
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID); // Check for marketIds otherwise Abacus will silently fail its isMarketValid check
}, [selectedNetwork, marketId]); if (marketIds) {
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
}
}, [selectedNetwork, marketIds, marketId]);
}; };
+16 -5
View File
@@ -13,8 +13,10 @@ import {
import type { ResolutionString } from 'public/tradingview/charting_library'; import type { ResolutionString } from 'public/tradingview/charting_library';
import type { NetworkConfig, ConnectNetworkEvent } from '@/constants/abacus'; import type { ConnectNetworkEvent, NetworkConfig } from '@/constants/abacus';
import { type Candle, RESOLUTION_MAP } from '@/constants/candles'; import { type Candle, RESOLUTION_MAP } from '@/constants/candles';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { DydxChainAsset } from '@/constants/wallets';
import { getSelectedNetwork } from '@/state/appSelectors'; import { getSelectedNetwork } from '@/state/appSelectors';
@@ -36,8 +38,9 @@ const useDydxClientContext = () => {
// ------ Network ------ // // ------ Network ------ //
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
const [networkConfig, setNetworkConfig] = useState<Partial<NetworkConfig>>(); const [networkConfig, setNetworkConfig] = useState<NetworkConfig>();
useEffect(() => { useEffect(() => {
const onConnectNetwork = (event: ConnectNetworkEvent) => setNetworkConfig(event.detail); const onConnectNetwork = (event: ConnectNetworkEvent) => setNetworkConfig(event.detail);
@@ -57,15 +60,22 @@ const useDydxClientContext = () => {
if ( if (
networkConfig?.chainId && networkConfig?.chainId &&
networkConfig?.indexerUrl && networkConfig?.indexerUrl &&
networkConfig?.indexerSocketUrl && networkConfig?.websocketUrl &&
networkConfig?.validatorUrl networkConfig?.validatorUrl
) { ) {
try { try {
const initializedClient = await CompositeClient.connect( const initializedClient = await CompositeClient.connect(
new Network( new Network(
selectedNetwork, selectedNetwork,
new IndexerConfig(networkConfig.indexerUrl, networkConfig.indexerSocketUrl), new IndexerConfig(networkConfig.indexerUrl, networkConfig.websocketUrl),
new ValidatorConfig(networkConfig.validatorUrl, networkConfig.chainId, { new ValidatorConfig(networkConfig.validatorUrl, networkConfig.chainId,
{
USDC_DENOM: tokensConfigs[DydxChainAsset.USDC].denom,
USDC_DECIMALS: tokensConfigs[DydxChainAsset.USDC].decimals,
USDC_GAS_DENOM: tokensConfigs[DydxChainAsset.USDC].gasDenom,
CHAINTOKEN_DENOM: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
CHAINTOKEN_DECIMALS: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
}, {
broadcastPollIntervalMs: 3_000, broadcastPollIntervalMs: 3_000,
broadcastTimeoutMs: 60_000, broadcastTimeoutMs: 60_000,
}) })
@@ -209,6 +219,7 @@ const useDydxClientContext = () => {
networkConfig, networkConfig,
compositeClient, compositeClient,
faucetClient, faucetClient,
isConnected: !!compositeClient,
// Wallet Methods // Wallet Methods
getWalletFromEvmSignature, getWalletFromEvmSignature,
+35 -19
View File
@@ -4,15 +4,14 @@ import { useSelector, shallowEqual, useDispatch } from 'react-redux';
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import { AlertType } from '@/constants/alerts'; import { AlertType } from '@/constants/alerts';
import { AbacusOrderStatus, ORDER_SIDES, ORDER_STATUS_STRINGS } from '@/constants/abacus'; import { AbacusOrderStatus, ORDER_SIDES } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS, StringKey } from '@/constants/localization';
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications'; import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade'; import { ORDER_SIDE_STRINGS } from '@/constants/trade';
import { useLocalNotifications } from '@/hooks/useLocalNotifications'; import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AlertMessage } from '@/components/AlertMessage';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType } from '@/components/Output'; import { Output, OutputType } from '@/components/Output';
import { TransferStatusToast } from '@/views/TransferStatus'; import { TransferStatusToast } from '@/views/TransferStatus';
@@ -69,11 +68,11 @@ export const notificationTypes = [
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} /> <OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
), ),
title: `${stringGetter({ title: `${stringGetter({
key: TRADE_TYPE_STRINGS[order.type.rawValue as TradeTypes].tradeTypeKey, key: order.resources.typeStringKey as StringKey,
})} ${ })} ${
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0 order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED }) ? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
: stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] }) : stringGetter({ key: order.resources.statusStringKey as StringKey })
}`, }`,
description: `${stringGetter({ description: `${stringGetter({
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]], key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
@@ -91,18 +90,34 @@ export const notificationTypes = [
}, [orderIds]); }, [orderIds]);
}, },
useNotificationAction: () => { // useNotificationAction: () => {
const dispatch = useDispatch(); // const dispatch = useDispatch();
// const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
// const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
return (orderId) => { // const fills = useSelector(getSubaccountFills, shallowEqual) || [];
dispatch( // const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
openDialog({
type: DialogTypes.OrderDetails, // return (id) => {
dialogProps: { orderId }, // if (ordersByOrderId[id]) {
}) // dispatch(
); // openDialog({
}; // type: DialogTypes.OrderDetails,
}, // dialogProps: { orderId: id },
// })
// );
// } else if (fillsByOrderId[id]) {
// const fillId = fillsByOrderId[id][0].id;
// dispatch(
// openDialog({
// type: DialogTypes.FillDetails,
// dialogProps: { fillId },
// })
// );
// }
// };
// },
} as NotificationTypeConfig<string, [string, number]>, } as NotificationTypeConfig<string, [string, number]>,
{ {
type: NotificationType.SquidTransfer, type: NotificationType.SquidTransfer,
@@ -138,7 +153,8 @@ export const notificationTypes = [
{stringGetter({ {stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE, key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: { params: {
ERROR_MESSAGE: error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }), ERROR_MESSAGE:
error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
}, },
})} })}
</Styled.ErrorMessage> </Styled.ErrorMessage>
@@ -182,7 +198,7 @@ Styled.TransferText = styled.span`
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.5ch; gap: 0.5ch;
` `;
Styled.ErrorMessage = styled.div` Styled.ErrorMessage = styled.div`
max-width: 13rem; max-width: 13rem;
+1 -14
View File
@@ -1,7 +1,6 @@
import { useCallback, useEffect } from 'react'; import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { DialogTypes } from '@/constants/dialogs';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
@@ -9,7 +8,6 @@ import { useAccounts, useLocalStorage } from '@/hooks';
import { setSelectedNetwork } from '@/state/app'; import { setSelectedNetwork } from '@/state/app';
import { getSelectedNetwork } from '@/state/appSelectors'; import { getSelectedNetwork } from '@/state/appSelectors';
import { openDialog } from '@/state/dialogs';
import { validateAgainstAvailableEnvironments } from '@/lib/network'; import { validateAgainstAvailableEnvironments } from '@/lib/network';
@@ -27,17 +25,6 @@ export const useSelectedNetwork = (): {
validateFn: validateAgainstAvailableEnvironments, validateFn: validateAgainstAvailableEnvironments,
}); });
useEffect(() => {
if (
import.meta.env.MODE === 'production' &&
ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId === 'dydx-testnet-4'
) {
dispatch(
openDialog({ type: DialogTypes.ExchangeOffline, dialogProps: { preventClose: true } })
);
}
}, [selectedNetwork]);
const switchNetwork = useCallback( const switchNetwork = useCallback(
(network: DydxNetwork) => { (network: DydxNetwork) => {
disconnect(); disconnect();
+27 -27
View File
@@ -3,15 +3,10 @@ import { shallowEqual, useSelector, useDispatch } from 'react-redux';
import type { Nullable } from '@dydxprotocol/v4-abacus'; import type { Nullable } from '@dydxprotocol/v4-abacus';
import Long from 'long'; import Long from 'long';
import type { IndexedTx } from '@cosmjs/stargate'; import type { IndexedTx } from '@cosmjs/stargate';
import type { EncodeObject, Coin } from '@cosmjs/proto-signing'; import type { EncodeObject } from '@cosmjs/proto-signing';
import { Method } from '@cosmjs/tendermint-rpc'; import { Method } from '@cosmjs/tendermint-rpc';
import { import { type LocalWallet, SubaccountClient } from '@dydxprotocol/v4-client-js';
type LocalWallet,
SubaccountClient,
DYDX_DENOM,
USDC_DENOM,
} from '@dydxprotocol/v4-client-js';
import type { import type {
AccountBalance, AccountBalance,
@@ -34,6 +29,7 @@ import { MustBigNumber } from '@/lib/numbers';
import { log } from '@/lib/telemetry'; import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts'; import { useAccounts } from './useAccounts';
import { useTokenConfigs } from './useTokenConfigs';
import { useDydxClient } from './useDydxClient'; import { useDydxClient } from './useDydxClient';
type SubaccountContextType = ReturnType<typeof useSubaccountContext>; type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
@@ -52,6 +48,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 { compositeClient, faucetClient } = useDydxClient(); const { compositeClient, faucetClient } = useDydxClient();
const { getFaucetFunds } = useMemo( const { getFaucetFunds } = useMemo(
@@ -77,13 +74,12 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
() => ({ () => ({
depositToSubaccount: async ({ depositToSubaccount: async ({
subaccountClient, subaccountClient,
assetId = 0,
amount, amount,
}: { }: {
subaccountClient: SubaccountClient; subaccountClient: SubaccountClient;
assetId?: number; assetId?: number;
amount: Long; amount: number;
}) => await compositeClient?.validatorClient.post.deposit(subaccountClient, assetId, amount), }) => await compositeClient?.depositToSubaccount(subaccountClient, amount.toString()),
withdrawFromSubaccount: async ({ withdrawFromSubaccount: async ({
subaccountClient, subaccountClient,
@@ -91,7 +87,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
}: { }: {
subaccountClient: SubaccountClient; subaccountClient: SubaccountClient;
amount: number; amount: number;
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount), }) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount.toString()),
transferFromSubaccountToAddress: async ({ transferFromSubaccountToAddress: async ({
subaccountClient, subaccountClient,
@@ -134,12 +130,21 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
amount: number; amount: number;
recipient: string; recipient: string;
}) => }) =>
await compositeClient?.validatorClient.post.sendToken( await compositeClient?.validatorClient.post.send(
subaccountClient, subaccountClient.wallet,
recipient, () =>
DYDX_DENOM, new Promise((resolve) => {
Long.fromNumber(amount * QUANTUM_MULTIPLIER), const msg = compositeClient?.sendTokenMessage(
subaccountClient.wallet,
amount.toString(),
recipient,
);
resolve([msg]);
}),
false, false,
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
undefined,
Method.BroadcastTxCommit Method.BroadcastTxCommit
), ),
@@ -156,7 +161,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
const transaction = JSON.parse(payload); const transaction = JSON.parse(payload);
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount); const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount.toString());
const ibcMsg: EncodeObject = { const ibcMsg: EncodeObject = {
typeUrl: transaction.msgTypeUrl, typeUrl: transaction.msgTypeUrl,
value: transaction.msg, value: transaction.msg,
@@ -195,13 +200,9 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
async (balance: AccountBalance) => { async (balance: AccountBalance) => {
if (!localDydxWallet) return; if (!localDydxWallet) return;
const amountAfterDust = MustBigNumber(balance.amount) const amount = parseFloat(balance.amount) - AMOUNT_RESERVED_FOR_GAS_USDC;
.minus(AMOUNT_RESERVED_FOR_GAS_USDC) // keep 0.1 USDC in user's wallet for gas
.toString();
const amount = Long.fromString(amountAfterDust || '0'); if (amount > 0) {
if (amount.greaterThan(Long.ZERO)) {
const subaccountClient = new SubaccountClient(localDydxWallet, 0); const subaccountClient = new SubaccountClient(localDydxWallet, 0);
await depositToSubaccount({ amount, subaccountClient }); await depositToSubaccount({ amount, subaccountClient });
} }
@@ -210,7 +211,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
); );
const balances = useSelector(getBalances, shallowEqual); const balances = useSelector(getBalances, shallowEqual);
const usdcCoinBalance = balances?.[USDC_DENOM]; const usdcCoinBalance = balances?.[usdcDenom];
useEffect(() => { useEffect(() => {
if (usdcCoinBalance) { if (usdcCoinBalance) {
@@ -219,7 +220,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
}, [usdcCoinBalance]); }, [usdcCoinBalance]);
const deposit = useCallback( const deposit = useCallback(
async (amount: Long) => { async (amount: number) => {
if (!subaccountClient) { if (!subaccountClient) {
return; return;
} }
@@ -247,8 +248,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
if (!subaccountClient) { if (!subaccountClient) {
return; return;
} }
return (await (coinDenom === usdcDenom
return (await (coinDenom === USDC_DENOM
? transferFromSubaccountToAddress ? transferFromSubaccountToAddress
: transferNativeToken)({ subaccountClient, amount, recipient })) as IndexedTx; : transferNativeToken)({ subaccountClient, amount, recipient })) as IndexedTx;
}, },
+36
View File
@@ -0,0 +1,36 @@
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { DydxChainAsset } from '@/constants/wallets';
import { useSelectedNetwork } from '@/hooks';
export const useTokenConfigs = (): {
tokensConfigs: {
[DydxChainAsset.USDC]: {
denom: string;
name: string;
decimals: number;
gasDenom?: string;
},
[DydxChainAsset.CHAINTOKEN]: {
denom: string;
name: string;
decimals: number;
gasDenom?: string;
},
};
usdcDenom: string;
usdcLabel: string;
chainTokenDenom: string;
chainTokenLabel: string;
} => {
const { selectedNetwork } = useSelectedNetwork();
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
return {
tokensConfigs,
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
};
};
+9
View File
@@ -1,6 +1,8 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { LocalStorageKey } from '@/constants/localStorage'; import { LocalStorageKey } from '@/constants/localStorage';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { import {
type DydxAddress, type DydxAddress,
@@ -28,6 +30,8 @@ import {
WalletType as CosmosWalletType, WalletType as CosmosWalletType,
} from 'graz'; } from 'graz';
import { getSelectedNetwork } from '@/state/appSelectors';
import { resolveWagmiConnector } from '@/lib/wagmi'; import { resolveWagmiConnector } from '@/lib/wagmi';
import { getWalletConnection, parseWalletError } from '@/lib/wallet'; import { getWalletConnection, parseWalletError } from '@/lib/wallet';
import { log } from '@/lib/telemetry'; import { log } from '@/lib/telemetry';
@@ -84,6 +88,9 @@ export const useWalletConnection = () => {
// Wallet connection // Wallet connection
const selectedNetwork = useSelector(getSelectedNetwork);
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
const { connectAsync: connectWagmi } = const { connectAsync: connectWagmi } =
walletType && walletConnectionType walletType && walletConnectionType
? useConnectWagmi({ ? useConnectWagmi({
@@ -92,6 +99,7 @@ export const useWalletConnection = () => {
walletConnection: { walletConnection: {
type: walletConnectionType, type: walletConnectionType,
}, },
walletConnectConfig,
}), }),
}) })
: useConnectWagmi(); : useConnectWagmi();
@@ -127,6 +135,7 @@ export const useWalletConnection = () => {
connector: resolveWagmiConnector({ connector: resolveWagmiConnector({
walletType, walletType,
walletConnection, walletConnection,
walletConnectConfig,
}), }),
}); });
} }
+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3811 13.235C11.9338 13.0512 13.4593 12.6847 14.9261 12.1433C13.6839 10.7673 12.9975 8.97875 13.0003 7.125V6.54167V6.5C13.0003 5.17392 12.4735 3.90215 11.5358 2.96447C10.5981 2.02678 9.32636 1.5 8.00028 1.5C6.67419 1.5 5.40242 2.02678 4.46474 2.96447C3.52706 3.90215 3.00028 5.17392 3.00028 6.5V7.125C3.00279 8.97886 2.3161 10.7675 1.07361 12.1433C2.51778 12.6767 4.04027 13.0475 5.61944 13.235M10.3811 13.235C8.79943 13.4226 7.20112 13.4226 5.61944 13.235M10.3811 13.235C10.5012 13.6099 10.5311 14.0078 10.4683 14.3964C10.4055 14.785 10.2518 15.1533 10.0198 15.4713C9.78772 15.7893 9.48386 16.048 9.13293 16.2263C8.78201 16.4047 8.39392 16.4976 8.00028 16.4976C7.60663 16.4976 7.21854 16.4047 6.86762 16.2263C6.51669 16.048 6.21283 15.7893 5.98079 15.4713C5.74875 15.1533 5.59508 14.785 5.53229 14.3964C5.46949 14.0078 5.49936 13.6099 5.61944 13.235" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.625 6.1875h6.75m-6.75 2.25H9M1.6875 9.57c0 1.2.84225 2.2455 2.03025 2.4203.84675.1245 1.7025.2197 2.56725.2842.2625.0195.5025.1575.64875.3758L9 15.75l2.0663-3.0997c.0724-.1078.1685-.1977.2809-.2628.1125-.0651.2382-.1038.3678-.113.8589-.0641 1.7152-.1589 2.5672-.2842 1.188-.1748 2.0303-1.2195 2.0303-2.42105v-4.5135c0-1.2015-.8423-2.24625-2.0303-2.421A36.294434 36.294434 0 0 0 9 2.25c-1.794 0-3.558.13125-5.28225.38475-1.188.17475-2.03025 1.22025-2.03025 2.421V9.57Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>

After

Width:  |  Height:  |  Size: 672 B

+1 -1
View File
@@ -1 +1 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M15 8C15 9.53527 14.6867 10.8758 14.2269 11.7954C13.77 12.7092 13.3116 12.9664 13.0527 12.9968L12.6636 12.8953C12.4116 12.7511 12.092 12.4331 11.7731 11.7954C11.3133 10.8758 11 9.53527 11 8C11 6.46473 11.3133 5.12424 11.7731 4.20457C12.0924 3.56607 12.4124 3.24815 12.6645 3.10417L13.0519 3.00311C13.3107 3.0331 13.7696 3.28991 14.2269 4.20457C14.6867 5.12424 15 6.46473 15 8ZM12.2928 2.16768L1.41963 5.00415C0.628571 5.08768 0 6.39699 0 7.99992C0 9.60346 0.629045 10.9131 1.42053 10.9958L2.50023 11.2774V12.9998C2.50023 13.8283 3.17181 14.4998 4.00023 14.4998C4.82866 14.4998 5.50023 13.8283 5.50023 12.9998V12.0601L12.2914 13.8317C12.5186 13.9417 12.7559 14 13 14C14.6569 14 16 11.3137 16 8C16 4.68629 14.6569 2 13 2C12.7564 2 12.5196 2.05808 12.2928 2.16768ZM10.9931 3.5402L2.49607 5.75681C2.80523 6.30634 3 7.10764 3 7.99992C3 8.89214 2.80525 9.69339 2.49614 10.2429L10.9929 12.4595C10.3833 11.3612 10 9.77013 10 8C10 6.22968 10.3834 4.63843 10.9931 3.5402ZM2 7.99992C2 8.70677 1.85462 9.29725 1.66623 9.67403C1.59961 9.80728 1.54246 9.88955 1.5 9.93955C1.45754 9.88955 1.40039 9.80728 1.33377 9.67403C1.14538 9.29725 1 8.70677 1 7.99992C1 7.29308 1.14538 6.70259 1.33377 6.32582C1.40039 6.19256 1.45754 6.11029 1.5 6.06029C1.54246 6.11029 1.59961 6.19256 1.66623 6.32582C1.85462 6.70259 2 7.29308 2 7.99992ZM1.41964 10.0124C1.41962 10.0124 1.42071 10.0118 1.42296 10.0109C1.42079 10.0121 1.41966 10.0125 1.41964 10.0124ZM1.57704 10.0109C1.57929 10.0118 1.58038 10.0124 1.58036 10.0124C1.58033 10.0125 1.57921 10.0121 1.57704 10.0109ZM1.58036 5.9874C1.58038 5.98747 1.57929 5.98808 1.57704 5.98899C1.57921 5.98778 1.58034 5.98732 1.58036 5.9874ZM1.42296 5.98899C1.42071 5.98808 1.41962 5.98747 1.41964 5.9874C1.41966 5.98732 1.42079 5.98778 1.42296 5.98899ZM3.50023 12.9998V11.5383L4.50023 11.7992V12.9998C4.50023 13.276 4.27638 13.4998 4.00023 13.4998C3.72409 13.4998 3.50023 13.276 3.50023 12.9998Z" fill="currentColor"/></svg> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M15 8C15 9.53527 14.6867 10.8758 14.2269 11.7954C13.77 12.7092 13.3116 12.9664 13.0527 12.9968L12.6636 12.8953C12.4116 12.7511 12.092 12.4331 11.7731 11.7954C11.3133 10.8758 11 9.53527 11 8C11 6.46473 11.3133 5.12424 11.7731 4.20457C12.0924 3.56607 12.4124 3.24815 12.6645 3.10417L13.0519 3.00311C13.3107 3.0331 13.7696 3.28991 14.2269 4.20457C14.6867 5.12424 15 6.46473 15 8ZM12.2928 2.16768L1.41963 5.00415C0.628571 5.08768 0 6.39699 0 7.99992C0 9.60346 0.629045 10.9131 1.42053 10.9958L2.50023 11.2774V12.9998C2.50023 13.8283 3.17181 14.4998 4.00023 14.4998C4.82866 14.4998 5.50023 13.8283 5.50023 12.9998V12.0601L12.2914 13.8317C12.5186 13.9417 12.7559 14 13 14C14.6569 14 16 11.3137 16 8C16 4.68629 14.6569 2 13 2C12.7564 2 12.5196 2.05808 12.2928 2.16768ZM10.9931 3.5402L2.49607 5.75681C2.80523 6.30634 3 7.10764 3 7.99992C3 8.89214 2.80525 9.69339 2.49614 10.2429L10.9929 12.4595C10.3833 11.3612 10 9.77013 10 8C10 6.22968 10.3834 4.63843 10.9931 3.5402ZM2 7.99992C2 8.70677 1.85462 9.29725 1.66623 9.67403C1.59961 9.80728 1.54246 9.88955 1.5 9.93955C1.45754 9.88955 1.40039 9.80728 1.33377 9.67403C1.14538 9.29725 1 8.70677 1 7.99992C1 7.29308 1.14538 6.70259 1.33377 6.32582C1.40039 6.19256 1.45754 6.11029 1.5 6.06029C1.54246 6.11029 1.59961 6.19256 1.66623 6.32582C1.85462 6.70259 2 7.29308 2 7.99992ZM1.41964 10.0124C1.41962 10.0124 1.42071 10.0118 1.42296 10.0109C1.42079 10.0121 1.41966 10.0125 1.41964 10.0124ZM1.57704 10.0109C1.57929 10.0118 1.58038 10.0124 1.58036 10.0124C1.58033 10.0125 1.57921 10.0121 1.57704 10.0109ZM1.58036 5.9874C1.58038 5.98747 1.57929 5.98808 1.57704 5.98899C1.57921 5.98778 1.58034 5.98732 1.58036 5.9874ZM1.42296 5.98899C1.42071 5.98808 1.41962 5.98747 1.41964 5.9874C1.41966 5.98732 1.42079 5.98778 1.42296 5.98899ZM3.50023 12.9998V11.5383L4.50023 11.7992V12.9998C4.50023 13.276 4.27638 13.4998 4.00023 13.4998C3.72409 13.4998 3.50023 13.276 3.50023 12.9998Z" fill="currentColor"/></svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+2
View File
@@ -2,11 +2,13 @@ export { default as AddressConnectorIcon } from './address-connector.svg';
export { default as ArrowIcon } from './arrow.svg'; export { default as ArrowIcon } from './arrow.svg';
export { default as Bar3Icon } from './bar3.svg'; export { default as Bar3Icon } from './bar3.svg';
export { default as BellIcon } from './bell.svg'; export { default as BellIcon } from './bell.svg';
export { default as BellStrokeIcon } from './bell-stroke.svg';
export { default as BoxCloseIcon } from './box-close.svg'; export { default as BoxCloseIcon } from './box-close.svg';
export { default as CalculatorIcon } from './calculator.svg'; export { default as CalculatorIcon } from './calculator.svg';
export { default as CaretIcon } from './caret-down.svg'; export { default as CaretIcon } from './caret-down.svg';
export { default as CautionCircleIcon } from './caution-circle.svg'; export { default as CautionCircleIcon } from './caution-circle.svg';
export { default as CautionCircleStrokeIcon } from './caution-circle-stroke.svg'; export { default as CautionCircleStrokeIcon } from './caution-circle-stroke.svg';
export { default as ChatIcon } from './chat-bubble.svg';
export { default as CheckIcon } from './check.svg'; export { default as CheckIcon } from './check.svg';
export { default as ChevronLeftIcon } from './chevron-left.svg'; export { default as ChevronLeftIcon } from './chevron-left.svg';
export { default as ChevronRightIcon } from './chevron-right.svg'; export { default as ChevronRightIcon } from './chevron-right.svg';
+64 -23
View File
@@ -1,12 +1,16 @@
import styled, { type AnyStyledComponent, css } from 'styled-components'; import styled, { type AnyStyledComponent, css } from 'styled-components';
import { AbacusApiStatus } from '@/constants/abacus'; import { AbacusApiStatus } from '@/constants/abacus';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useApiState, useStringGetter } from '@/hooks'; import { useApiState, useSelectedNetwork, useStringGetter } from '@/hooks';
import { ChatIcon, LinkOutIcon } from '@/icons';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { Button } from '@/components/Button';
import { Details } from '@/components/Details'; import { Details } from '@/components/Details';
import { Output, OutputType } from '@/components/Output'; import { Output, OutputType } from '@/components/Output';
import { WithTooltip } from '@/components/WithTooltip'; import { WithTooltip } from '@/components/WithTooltip';
@@ -24,6 +28,8 @@ enum ExchangeStatus {
export const FooterDesktop = () => { export const FooterDesktop = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { height, indexerHeight, status, statusErrorMessage } = useApiState(); const { height, indexerHeight, status, statusErrorMessage } = useApiState();
const { selectedNetwork } = useSelectedNetwork();
const { statusPage } = ENVIRONMENT_CONFIG_MAP[selectedNetwork].links;
const { exchangeStatus, label } = const { exchangeStatus, label } =
!status || status === AbacusApiStatus.NORMAL !status || status === AbacusApiStatus.NORMAL
@@ -38,20 +44,37 @@ export const FooterDesktop = () => {
return ( return (
<Styled.Footer> <Styled.Footer>
<WithTooltip <Styled.Row>
slotTooltip={ <WithTooltip
statusErrorMessage && ( slotTooltip={
<dl> statusErrorMessage && (
<dd>{statusErrorMessage}</dd> <dl>
</dl> <dd>{statusErrorMessage}</dd>
) </dl>
} )
> }
<Styled.Row> >
<Styled.StatusDot exchangeStatus={exchangeStatus} /> <Styled.FooterButton
<span>{label}</span> slotLeft={<Styled.StatusDot exchangeStatus={exchangeStatus} />}
</Styled.Row> slotRight={statusPage && <LinkOutIcon />}
</WithTooltip> size={ButtonSize.XSmall}
state={{ isDisabled: !statusPage }}
>
{label}
</Styled.FooterButton>
</WithTooltip>
{globalThis?.Intercom && (
<Styled.FooterButton
slotLeft={<ChatIcon />}
size={ButtonSize.XSmall}
onClick={() => globalThis.Intercom('show')}
>
{stringGetter({ key: STRING_KEYS.HELP_AND_SUPPORT })}
</Styled.FooterButton>
)}
</Styled.Row>
{import.meta.env.MODE !== 'production' && ( {import.meta.env.MODE !== 'production' && (
<Styled.Details <Styled.Details
withSeparators withSeparators
@@ -82,20 +105,22 @@ Styled.Footer = styled.footer`
${layoutMixins.stickyFooter} ${layoutMixins.stickyFooter}
${layoutMixins.spacedRow} ${layoutMixins.spacedRow}
grid-area: Footer; grid-area: Footer;
font-size: 0.66em;
`; `;
Styled.Row = styled.div` Styled.Row = styled.div`
${layoutMixins.row} ${layoutMixins.row}
padding: 0 1rem; ${layoutMixins.spacedRow}
gap: 0.5rem; width: var(--sidebar-width);
padding: 0 0.5rem;
border-right: 1px solid var(--color-border);
`; `;
Styled.StatusDot = styled.div<{ exchangeStatus: ExchangeStatus }>` Styled.StatusDot = styled.div<{ exchangeStatus: ExchangeStatus }>`
width: 1em; width: 0.5rem;
height: 1em; height: 0.5rem;
border-radius: 50%; border-radius: 50%;
margin-right: 0.25rem;
background-color: ${({ exchangeStatus }) => background-color: ${({ exchangeStatus }) =>
({ ({
@@ -104,12 +129,28 @@ Styled.StatusDot = styled.div<{ exchangeStatus: ExchangeStatus }>`
}[exchangeStatus])}; }[exchangeStatus])};
`; `;
Styled.FooterButton = styled(Button)`
--button-height: 1.5rem;
--button-radius: 0.25rem;
--button-backgroundColor: var(--color-layer-2);
--button-border: none;
--button-textColor: var(--color-text-0);
&:hover:not(:disabled) {
--button-backgroundColor: var(--color-layer-3);
--button-textColor: var(--color-text-1);
}
&:disabled {
cursor: default;
}
`;
Styled.WarningOutput = styled(Output)` Styled.WarningOutput = styled(Output)`
color: var(--color-warning); color: var(--color-warning);
`; `;
Styled.Details = styled(Details)` Styled.Details = styled(Details)`
${layoutMixins.scrollArea} ${layoutMixins.scrollArea}
font: var(--font-tiny-book);
padding: 0 0.5em;
`; `;
+15 -10
View File
@@ -6,8 +6,9 @@ import { ButtonShape } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { AppRoute } from '@/constants/routes'; import { AppRoute } from '@/constants/routes';
import { useStringGetter } from '@/hooks'; import { LogoShortIcon, BellStrokeIcon } from '@/icons';
import { LogoShortIcon, BellIcon } from '@/icons';
import { useTokenConfigs, useStringGetter } from '@/hooks';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton'; import { IconButton } from '@/components/IconButton';
@@ -27,6 +28,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
export const HeaderDesktop = () => { export const HeaderDesktop = () => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { chainTokenLabel } = useTokenConfigs();
const navItems = [ const navItems = [
{ {
@@ -43,9 +45,9 @@ export const HeaderDesktop = () => {
href: AppRoute.Trade, href: AppRoute.Trade,
}, },
{ {
value: 'Dv4TNT', value: chainTokenLabel,
label: 'Dv4TNT', label: chainTokenLabel,
href: AppRoute.Rewards, href: `/${chainTokenLabel}`,
}, },
{ {
value: 'PORTFOLIO', value: 'PORTFOLIO',
@@ -78,13 +80,13 @@ export const HeaderDesktop = () => {
value: 'TERMS_OF_USE', value: 'TERMS_OF_USE',
slotBefore: <Icon iconName={IconName.File} />, slotBefore: <Icon iconName={IconName.File} />,
label: stringGetter({ key: STRING_KEYS.TERMS_OF_USE }), label: stringGetter({ key: STRING_KEYS.TERMS_OF_USE }),
href: 'https://dydx.exchange/terms', href: AppRoute.Terms,
}, },
{ {
value: 'PRIVACY_POLICY', value: 'PRIVACY_POLICY',
slotBefore: <Icon iconName={IconName.Privacy} />, slotBefore: <Icon iconName={IconName.Privacy} />,
label: stringGetter({ key: STRING_KEYS.PRIVACY_POLICY }), label: stringGetter({ key: STRING_KEYS.PRIVACY_POLICY }),
href: 'https://dydx.exchange/privacy', href: AppRoute.Privacy,
}, },
{ {
value: 'HELP', value: 'HELP',
@@ -131,7 +133,9 @@ export const HeaderDesktop = () => {
<VerticalSeparator /> <VerticalSeparator />
<NotificationsMenu <NotificationsMenu
slotTrigger={<Styled.IconButton shape={ButtonShape.Rectangle} iconComponent={BellIcon} />} slotTrigger={
<Styled.IconButton shape={ButtonShape.Rectangle} iconComponent={BellStrokeIcon} />
}
/> />
<VerticalSeparator /> <VerticalSeparator />
@@ -165,7 +169,7 @@ Styled.Header = styled.header`
) )
var(--border-width) 1fr var(--border-width) auto; var(--border-width) 1fr var(--border-width) auto;
font-size: 0.9375em; font-size: 0.9375rem;
:before { :before {
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
@@ -214,7 +218,8 @@ Styled.NavAfter = styled.div`
} }
`; `;
Styled.IconButton = styled(IconButton)` Styled.IconButton = styled(IconButton)<{ size?: string }>`
${headerMixins.button} ${headerMixins.button}
--button-border: none; --button-border: none;
--button-icon-size: 1rem;
`; `;
+49 -34
View File
@@ -14,8 +14,6 @@ import {
OrderSide, OrderSide,
OrderTimeInForce, OrderTimeInForce,
OrderExecution, OrderExecution,
DYDX_DENOM,
GAS_PRICE_DYDX_DENOM,
} from '@dydxprotocol/v4-client-js'; } from '@dydxprotocol/v4-client-js';
import { import {
@@ -61,22 +59,42 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
} }
async connectNetwork( async connectNetwork(
indexerUrl: string, paramsInJson: Nullable<string>,
indexerSocketUrl: string,
validatorUrl: string,
chainId: string,
faucetUrl: Nullable<string> | undefined,
callback: (p0: Nullable<string>) => void callback: (p0: Nullable<string>) => void
): Promise<void> { ): Promise<void> {
try { try {
const parsedParams = paramsInJson ? JSON.parse(paramsInJson) : {};
const {
indexerUrl,
websocketUrl,
validatorUrl,
chainId,
USDC_DENOM,
USDC_DECIMALS,
USDC_GAS_DENOM,
CHAINTOKEN_DENOM,
CHAINTOKEN_DECIMALS,
} = parsedParams;
const compositeClient = await CompositeClient.connect( const compositeClient = await CompositeClient.connect(
new Network( new Network(
chainId, chainId,
new IndexerConfig(indexerUrl, indexerSocketUrl), new IndexerConfig(indexerUrl, websocketUrl),
new ValidatorConfig(validatorUrl, chainId, { new ValidatorConfig(
broadcastPollIntervalMs: 3_000, validatorUrl,
broadcastTimeoutMs: 60_000, chainId,
}) {
USDC_DENOM,
USDC_DECIMALS,
USDC_GAS_DENOM,
CHAINTOKEN_DENOM,
CHAINTOKEN_DECIMALS,
},
{
broadcastPollIntervalMs: 3_000,
broadcastTimeoutMs: 60_000,
}
)
) )
); );
@@ -84,13 +102,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
// Dispatch custom event to notify other parts of the app that the network has been connected // Dispatch custom event to notify other parts of the app that the network has been connected
const customEvent = new CustomEvent('abacus:connectNetwork', { const customEvent = new CustomEvent('abacus:connectNetwork', {
detail: { detail: parsedParams,
indexerUrl,
indexerSocketUrl,
validatorUrl,
chainId,
faucetUrl,
},
}); });
globalThis.dispatchEvent(customEvent); globalThis.dispatchEvent(customEvent);
@@ -99,9 +111,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
this.store?.dispatch( this.store?.dispatch(
openDialog({ type: DialogTypes.ExchangeOffline, dialogProps: { preventClose: true } }) openDialog({ type: DialogTypes.ExchangeOffline, dialogProps: { preventClose: true } })
); );
log('DydxChainTransactions/connectNetwork', error); log('DydxChainTransactions/connectNetwork', error);
return;
} }
} }
@@ -213,13 +223,13 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
params ?? {}; params ?? {};
try { try {
const tx = await this.compositeClient?.cancelOrder( const tx = await this.compositeClient?.cancelRawOrder(
new SubaccountClient(this.localWallet, subaccountNumber), new SubaccountClient(this.localWallet, subaccountNumber),
clientId, clientId,
orderFlags, orderFlags,
clobPairId, clobPairId,
goodTilBlock ?? undefined, goodTilBlock || undefined,
goodTilBlockTime ?? undefined goodTilBlockTime || undefined,
); );
const parsedTx = this.parseToPrimitives(tx); const parsedTx = this.parseToPrimitives(tx);
@@ -251,7 +261,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount); const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount);
resolve([msg]); resolve([msg]);
}), })
); );
const parsedTx = this.parseToPrimitives(tx); const parsedTx = this.parseToPrimitives(tx);
@@ -266,7 +276,9 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
} }
} }
async simulateTransferNativeTokenTransaction(params: HumanReadableTransferPayload): Promise<string> { async simulateTransferNativeTokenTransaction(
params: HumanReadableTransferPayload
): Promise<string> {
if (!this.compositeClient || !this.localWallet) { if (!this.compositeClient || !this.localWallet) {
throw new Error('Missing compositeClient or localWallet'); throw new Error('Missing compositeClient or localWallet');
} }
@@ -280,16 +292,18 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
this.localWallet, this.localWallet,
() => () =>
new Promise((resolve) => { new Promise((resolve) => {
const msg = compositeClient?.validatorClient.post.composer.composeMsgSendToken( if (!this.localWallet) {
subaccountClient.address, throw new Error('Missing compositeClient or localWallet');
}
const msg = compositeClient?.sendTokenMessage(
this.localWallet,
amount,
recipient, recipient,
DYDX_DENOM,
Long.fromNumber(amount * QUANTUM_MULTIPLIER)
); );
resolve([msg]); resolve([msg]);
}), }),
GAS_PRICE_DYDX_DENOM, this.compositeClient?.validatorClient?.post.defaultDydxGasPrice,
); );
const parsedTx = this.parseToPrimitives(tx); const parsedTx = this.parseToPrimitives(tx);
@@ -397,9 +411,10 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
break; break;
case QueryType.GetAccountBalances: case QueryType.GetAccountBalances:
if (!this.localWallet?.address) throw new Error('Missing localWallet'); if (!this.localWallet?.address) throw new Error('Missing localWallet');
const accountBalances = await this.compositeClient?.validatorClient.get.getAccountBalances( const accountBalances =
this.localWallet.address await this.compositeClient?.validatorClient.get.getAccountBalances(
); this.localWallet.address
);
const parsedAccountBalances = this.parseToPrimitives(accountBalances); const parsedAccountBalances = this.parseToPrimitives(accountBalances);
callback(JSON.stringify(parsedAccountBalances)); callback(JSON.stringify(parsedAccountBalances));
break; break;
+33 -13
View File
@@ -95,15 +95,28 @@ const injectedConnectorOptions = {
}, },
}; };
const walletconnect2ConnectorOptions: ConstructorParameters<typeof WalletConnectConnector>[0] = { type WalletConnectConfig = {
client: {
name: string;
description: string;
iconUrl: string;
};
v2: {
projectId: string;
};
};
const getWalletconnect2ConnectorOptions = (
config: WalletConnectConfig
): ConstructorParameters<typeof WalletConnectConnector>[0] => ({
chains, chains,
options: { options: {
projectId: import.meta.env.VITE_WALLETCONNECT2_PROJECT_ID, projectId: config.v2.projectId,
metadata: { metadata: {
name: 'dYdX', name: config.client.name,
description: '', description: config.client.description,
url: import.meta.env.VITE_BASE_URL, url: import.meta.env.VITE_BASE_URL,
icons: [`${import.meta.env.VITE_BASE_URL}/cbw-image.png}`], icons: [config.client.iconUrl],
}, },
showQrModal: true, showQrModal: true,
qrModalOptions: { qrModalOptions: {
@@ -115,9 +128,9 @@ const walletconnect2ConnectorOptions: ConstructorParameters<typeof WalletConnect
explorerRecommendedWalletIds: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS, explorerRecommendedWalletIds: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
}, },
}, },
}; });
const connectors = [ const getConnectors = (walletConnectConfig: WalletConnectConfig) => [
new MetaMaskConnector({ new MetaMaskConnector({
chains, chains,
options: { options: {
@@ -131,7 +144,7 @@ const connectors = [
reloadOnDisconnect: false, reloadOnDisconnect: false,
}, },
}), }),
new WalletConnectConnector(walletconnect2ConnectorOptions), new WalletConnectConnector(getWalletconnect2ConnectorOptions(walletConnectConfig)),
new InjectedConnector(injectedConnectorOptions), new InjectedConnector(injectedConnectorOptions),
]; ];
@@ -153,27 +166,34 @@ const createInjectedConnectorWithProvider = (provider: ExternalProvider) =>
provider as unknown as Awaited<ReturnType<InjectedConnector['getProvider']>>; provider as unknown as Awaited<ReturnType<InjectedConnector['getProvider']>>;
})(injectedConnectorOptions) as InjectedConnector; })(injectedConnectorOptions) as InjectedConnector;
const createWalletConnect2ConnectorWithId = (walletconnect2Id: string) => const createWalletConnect2ConnectorWithId = (
new WalletConnectConnector({ walletconnectId: string,
walletConnectConfig: WalletConnectConfig
) => {
const walletconnect2ConnectorOptions = getWalletconnect2ConnectorOptions(walletConnectConfig);
return new WalletConnectConnector({
...walletconnect2ConnectorOptions, ...walletconnect2ConnectorOptions,
options: { options: {
...walletconnect2ConnectorOptions.options, ...walletconnect2ConnectorOptions.options,
qrModalOptions: { qrModalOptions: {
...walletconnect2ConnectorOptions.options.qrModalOptions, ...walletconnect2ConnectorOptions.options.qrModalOptions,
explorerRecommendedWalletIds: [walletconnect2Id], explorerRecommendedWalletIds: [walletconnectId],
explorerExcludedWalletIds: 'ALL', explorerExcludedWalletIds: 'ALL',
}, },
}, },
}); });
};
// Custom connector from wallet selection // Custom connector from wallet selection
export const resolveWagmiConnector = ({ export const resolveWagmiConnector = ({
walletType, walletType,
walletConnection, walletConnection,
walletConnectConfig,
}: { }: {
walletType: WalletType; walletType: WalletType;
walletConnection: WalletConnection; walletConnection: WalletConnection;
walletConnectConfig: WalletConnectConfig;
}) => { }) => {
const walletConfig = wallets[walletType]; const walletConfig = wallets[walletType];
const walletConnectionConfig = walletConnectionTypes[walletConnection.type]; const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
@@ -181,6 +201,6 @@ export const resolveWagmiConnector = ({
return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider
? createInjectedConnectorWithProvider(walletConnection.provider) ? createInjectedConnectorWithProvider(walletConnection.provider)
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id : walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id) ? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id, walletConnectConfig)
: connectors.find(({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId); : getConnectors(walletConnectConfig).find(({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId);
}; };
+17
View File
@@ -0,0 +1,17 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { articleMixins } from '@/styles/articleMixins';
export const PrivacyPolicyPage = () => (
<Styled.Article>
<header>
<h1>Privacy Policy</h1>
</header>
</Styled.Article>
);
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Article = styled.article`
${articleMixins.article}
`;
+17
View File
@@ -0,0 +1,17 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { articleMixins } from '@/styles/articleMixins';
export const TermsOfUsePage = () => (
<Styled.Article>
<header>
<h1>Terms of Use</h1>
</header>
</Styled.Article>
);
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Article = styled.article`
${articleMixins.article}
`;
+10 -4
View File
@@ -3,7 +3,7 @@ import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { useAccountBalance, useAccounts, useStringGetter } from '@/hooks'; import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
import { ButtonAction, ButtonSize } from '@/constants/buttons'; import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
@@ -30,6 +30,7 @@ export const DYDXBalancePanel = () => {
const { walletType } = useAccounts(); const { walletType } = useAccounts();
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual); const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { nativeTokenBalance, nativeStakingBalance } = useAccountBalance(); const { nativeTokenBalance, nativeStakingBalance } = useAccountBalance();
const { chainTokenLabel } = useTokenConfigs();
return ( return (
<Panel <Panel
@@ -37,7 +38,7 @@ export const DYDXBalancePanel = () => {
<Styled.Header> <Styled.Header>
<Styled.Title> <Styled.Title>
{/* <AssetIcon symbol="DYDX" /> */} {/* <AssetIcon symbol="DYDX" /> */}
Dv4TNT {chainTokenLabel}
</Styled.Title> </Styled.Title>
<Styled.ReceiveAndTransferButtons> <Styled.ReceiveAndTransferButtons>
{!canAccountTrade ? ( {!canAccountTrade ? (
@@ -106,7 +107,13 @@ export const DYDXBalancePanel = () => {
{ {
key: 'totalBalance', key: 'totalBalance',
label: 'Total balance', label: 'Total balance',
value: <Output type={OutputType.Asset} value={nativeTokenBalance + nativeStakingBalance} tag="Dv4TNT" />, value: (
<Output
type={OutputType.Asset}
value={nativeTokenBalance + nativeStakingBalance}
tag={chainTokenLabel}
/>
),
}, },
]} ]}
/> />
@@ -174,7 +181,6 @@ Styled.WalletAndStakedBalance = styled(Details)`
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 0.5rem; gap: 0.5rem;
> div { > div {
gap: 1rem; gap: 1rem;
+2
View File
@@ -13,4 +13,6 @@ declare global {
'abacus:connectNetwork': CustomEvent; 'abacus:connectNetwork': CustomEvent;
} }
var Intercom: any;
} }
+2
View File
@@ -24,6 +24,8 @@ export const dialogsSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
openDialog: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => { openDialog: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => {
if (state.activeDialog?.type === action.payload.type) return;
if (state.activeDialog) { if (state.activeDialog) {
state.dialogQueue.push(action.payload); state.dialogQueue.push(action.payload);
} else { } else {
+53
View File
@@ -0,0 +1,53 @@
import {
css,
type FlattenInterpolation,
type FlattenSimpleInterpolation,
type ThemeProps,
} from 'styled-components';
export const articleMixins: Record<
string,
FlattenSimpleInterpolation | FlattenInterpolation<ThemeProps<any>>
> = {
article: css`
padding: 2rem;
overflow-wrap: break-word;
user-select: text;
margin: 0 auto;
width: 60rem;
max-width: 100vw;
header {
color: var(--color-text-0);
margin-bottom: 2rem;
h1 {
font: var(--font-extra-book);
color: var(--color-text-2);
}
}
h2 {
font: var(--font-large-book);
color: var(--color-text-2);
padding: 0.5rem 0;
}
h3,
strong {
font-weight: 700;
}
strong {
color: var(--color-text-2);
}
p {
padding: 0.5rem 0;
}
ul {
padding: 0.5rem 1rem;
}
`,
};
+4 -5
View File
@@ -36,11 +36,10 @@
--single-unit-height: 9.375rem; --single-unit-height: 9.375rem;
/* Modal constants */ /* Modal constants */
--sidebar-modal-width: 18.75rem; --dialog-small-width: 18.75rem;
--modal-small-width: 18.75rem; --dialog-medium-width: 20.5rem;
--modal-medium-width: 20.5rem; --dialog-large-width: 26.25rem;
--modal-large-width: 26.25rem; --dialog-header-height: 4rem;
--modal-header-height: 4rem;
/* Market Selector constants */ /* Market Selector constants */
--marketsDropdown-openWidth: 45rem; --marketsDropdown-openWidth: 45rem;
+2 -1
View File
@@ -188,13 +188,14 @@ export const popoverMixins = {
--item-highlighted-backgroundColor: var(--color-layer-2); --item-highlighted-backgroundColor: var(--color-layer-2);
--item-highlighted-textColor: var(--color-text-2); --item-highlighted-textColor: var(--color-text-2);
--item-gap: 0.5em;
--item-radius: 0px; --item-radius: 0px;
--item-padding: 0.5em 1em; --item-padding: 0.5em 1em;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 0.5em; gap: var(--item-gap);
padding: var(--item-padding); padding: var(--item-padding);
border-radius: var(--item-radius); border-radius: var(--item-radius);
@@ -75,16 +75,14 @@ export const AccountInfoConnectedState = () => {
<Styled.Header> <Styled.Header>
<span>{stringGetter({ key: STRING_KEYS.ACCOUNT })}</span> <span>{stringGetter({ key: STRING_KEYS.ACCOUNT })}</span>
<Styled.TransferButtons> <Styled.TransferButtons>
{import.meta.env.MODE !== 'production' && ( <Styled.Button
<Styled.Button state={{ isDisabled: !dydxAccounts }}
state={{ isDisabled: !dydxAccounts }} onClick={() => dispatch(openDialog({ type: DialogTypes.Withdraw }))}
onClick={() => dispatch(openDialog({ type: DialogTypes.Withdraw }))} shape={ButtonShape.Pill}
shape={ButtonShape.Pill} size={ButtonSize.XSmall}
size={ButtonSize.XSmall} >
> {stringGetter({ key: STRING_KEYS.WITHDRAW })}
{stringGetter({ key: STRING_KEYS.WITHDRAW })} </Styled.Button>
</Styled.Button>
)}
<Styled.Button <Styled.Button
state={{ isDisabled: !dydxAccounts }} state={{ isDisabled: !dydxAccounts }}
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))} onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
+3 -6
View File
@@ -32,12 +32,9 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
> >
<Styled.Content> <Styled.Content>
{showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />} {showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />}
{/* TODO TRCL-1693 - uncomment when deposit form is ready */} <Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
{import.meta.env.MODE !== 'production' && ( {showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}> </Styled.TextToggle>
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
</Styled.TextToggle>
)}
</Styled.Content> </Styled.Content>
</Dialog> </Dialog>
); );
+2 -21
View File
@@ -5,8 +5,6 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { AbacusApiStatus } from '@/constants/abacus'; import { AbacusApiStatus } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { UNICODE } from '@/constants/unicode';
import { useApiState, useStringGetter } from '@/hooks'; import { useApiState, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
@@ -32,16 +30,8 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
const selectedNetwork = useSelector(getSelectedNetwork); const selectedNetwork = useSelector(getSelectedNetwork);
const activeDialog = useSelector(getActiveDialog, shallowEqual); const activeDialog = useSelector(getActiveDialog, shallowEqual);
const showOfflineMessage =
import.meta.env.MODE === 'production' &&
ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId === 'dydx-testnet-4';
useEffect(() => { useEffect(() => {
if ( if (activeDialog?.type === DialogTypes.ExchangeOffline && status === AbacusApiStatus.NORMAL) {
activeDialog?.type === DialogTypes.ExchangeOffline &&
status === AbacusApiStatus.NORMAL &&
!showOfflineMessage
) {
dispatch(closeDialog()); dispatch(closeDialog());
} }
}, [status, selectedNetwork]); }, [status, selectedNetwork]);
@@ -54,16 +44,7 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
title={stringGetter({ key: STRING_KEYS.UNAVAILABLE })} title={stringGetter({ key: STRING_KEYS.UNAVAILABLE })}
> >
<Styled.Content> <Styled.Content>
{showOfflineMessage ? ( <p>{statusErrorMessage}</p>
<>
<p>Testnet is currently offline. Please check back!</p>
<Styled.Link href="https://v4-teacher.vercel.app/testnets/schedule">
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} {UNICODE.ARROW_RIGHT}
</Styled.Link>
</>
) : (
<p>{statusErrorMessage}</p>
)}
{import.meta.env.MODE !== 'production' && <NetworkSelectMenu />} {import.meta.env.MODE !== 'production' && <NetworkSelectMenu />}
</Styled.Content> </Styled.Content>
</Dialog> </Dialog>
+71 -46
View File
@@ -1,69 +1,94 @@
import { useMemo } from 'react';
import styled, { AnyStyledComponent } from 'styled-components'; import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { Close } from '@radix-ui/react-dialog';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks'; import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins'; import { ChatIcon, FeedbackIcon, FileIcon, TerminalIcon } from '@/icons';
import { Button } from '@/components/Button'; import { ComboboxDialogMenu } from '@/components/ComboboxDialogMenu';
import { Dialog } from '@/components/Dialog';
import { ButtonAction, ButtonType } from '@/constants/buttons'; import { isTruthy } from '@/lib/isTruthy';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = { type ElementProps = {
setIsOpen: (open: boolean) => void; setIsOpen: (open: boolean) => void;
}; };
export const HELP_URL = `https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform?usp=sf_link`; const HELP_LINKS = {
apiDocumentation: 'https://v4-teacher.vercel.app/',
helpCenter: null,
feedback: null,
};
/**
* HelpDialog
* - Will temporarily be used as a 'Give Feedback' dialog.
* - It will ask users to navigate to a Google Form in order to record feedback.
*/
export const HelpDialog = ({ setIsOpen }: ElementProps) => { export const HelpDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const HELP_ITEMS = useMemo(
() => [
{
group: 'help-items',
items: [
HELP_LINKS.helpCenter && {
value: 'help-center',
label: stringGetter({ key: STRING_KEYS.HELP_CENTER }),
description: stringGetter({ key: STRING_KEYS.HELP_CENTER_DESCRIPTION }),
onSelect: () => {
HELP_LINKS.helpCenter && globalThis.open(HELP_LINKS.helpCenter, '_blank');
setIsOpen(false);
},
slotBefore: <FileIcon />,
},
HELP_LINKS.apiDocumentation && {
value: 'api-documentation',
label: stringGetter({ key: STRING_KEYS.API_DOCUMENTATION }),
description: stringGetter({ key: STRING_KEYS.API_DOCUMENTATION_DESCRIPTION }),
onSelect: () => {
HELP_LINKS.apiDocumentation && globalThis.open(HELP_LINKS.apiDocumentation, '_blank');
setIsOpen(false);
},
slotBefore: <TerminalIcon />,
},
globalThis?.Intercom && {
value: 'live-chat',
label: stringGetter({ key: STRING_KEYS.LIVE_CHAT }),
description: stringGetter({ key: STRING_KEYS.LIVE_CHAT_DESCRIPTION }),
onSelect: () => {
globalThis.Intercom('show');
setIsOpen(false);
},
slotBefore: <ChatIcon />,
},
HELP_LINKS.feedback && {
value: 'feedback',
label: stringGetter({ key: STRING_KEYS.PROVIDE_FEEDBACK }),
description: stringGetter({ key: STRING_KEYS.PROVIDE_FEEDBACK_DESCRIPTION }),
onSelect: () => {
HELP_LINKS.feedback && globalThis.open(HELP_LINKS.feedback, '_blank');
setIsOpen(false);
},
slotBefore: <FeedbackIcon />,
},
].filter(isTruthy),
},
],
[stringGetter]
);
return ( return (
<Dialog <Styled.ComboboxDialogMenu
isOpen isOpen
withSearch={false}
setIsOpen={setIsOpen} setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.PROVIDE_FEEDBACK })} title={stringGetter({ key: STRING_KEYS.HELP })}
description={stringGetter({ key: STRING_KEYS.PROVIDE_FEEDBACK_DESCRIPTION })} items={HELP_ITEMS}
> />
<Styled.Content>
<p>
You will be navigated to a Google Form where you will be able to provide feedback. Thank
you for your contribution!
</p>
<Styled.ButtonRow>
<Close asChild>
<Button
action={ButtonAction.Primary}
type={ButtonType.Link}
href={HELP_URL}
slotRight={<Icon iconName={IconName.LinkOut} />}
>
{stringGetter({ key: STRING_KEYS.CONTINUE })}
</Button>
</Close>
</Styled.ButtonRow>
</Styled.Content>
</Dialog>
); );
}; };
const Styled: Record<string, AnyStyledComponent> = {}; const Styled: Record<string, AnyStyledComponent> = {};
Styled.ButtonRow = styled.div` Styled.ComboboxDialogMenu = styled(ComboboxDialogMenu)`
${layoutMixins.row} --dialog-width: var(--dialog-small-width);
--dialog-content-paddingTop: 1rem;
gap: 0.5rem; --dialog-content-paddingBottom: 1rem;
justify-content: end; --comboxDialogMenu-item-gap: 1rem;
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`; `;
+11 -4
View File
@@ -37,7 +37,7 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { isMobile } = useBreakpoints(); const { isMobile } = useBreakpoints();
const { walletType } = useAccounts(); const { disconnect, walletType } = useAccounts();
const currentOnboardingStep = useSelector(calculateOnboardingStep); const currentOnboardingStep = useSelector(calculateOnboardingStep);
@@ -45,10 +45,17 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
if (!currentOnboardingStep) setIsOpen?.(false); if (!currentOnboardingStep) setIsOpen?.(false);
}, [currentOnboardingStep]); }, [currentOnboardingStep]);
const setIsOpenFromDialog = (open: boolean) => {
if (!open && currentOnboardingStep === OnboardingSteps.AcknowledgeTerms) {
disconnect();
}
setIsOpen?.(open);
}
return ( return (
<Styled.Dialog <Styled.Dialog
isOpen={Boolean(currentOnboardingStep)} isOpen={Boolean(currentOnboardingStep)}
setIsOpen={setIsOpen} setIsOpen={setIsOpenFromDialog}
{...(currentOnboardingStep && {...(currentOnboardingStep &&
{ {
[OnboardingSteps.ChooseWallet]: { [OnboardingSteps.ChooseWallet]: {
@@ -87,10 +94,10 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
title: stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_TERMS }), title: stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_TERMS }),
children: ( children: (
<Styled.Content> <Styled.Content>
<AcknowledgeTerms /> <AcknowledgeTerms onClose={() => setIsOpenFromDialog?.(false)} />
</Styled.Content> </Styled.Content>
), ),
width: '20rem', width: '30rem',
}, },
[OnboardingSteps.DepositFunds]: { [OnboardingSteps.DepositFunds]: {
title: stringGetter({ key: STRING_KEYS.DEPOSIT }), title: stringGetter({ key: STRING_KEYS.DEPOSIT }),
@@ -2,17 +2,21 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { useAccounts, useStringGetter } from '@/hooks'; import { useAccounts, useStringGetter } from '@/hooks';
import { AppRoute } from '@/constants/routes';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons'; import { ButtonAction } from '@/constants/buttons';
import { layoutMixins } from '@/styles/layoutMixins';
import { Button } from '@/components/Button'; import { Button } from '@/components/Button';
import { Link } from '@/components/Link'; import { Link } from '@/components/Link';
type ElementProps = { type ElementProps = {
onClose?: () => void;
onContinue?: () => void; onContinue?: () => void;
}; };
export const AcknowledgeTerms = ({ onContinue }: ElementProps) => { export const AcknowledgeTerms = ({ onClose, onContinue }: ElementProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { saveHasAcknowledgedTerms } = useAccounts(); const { saveHasAcknowledgedTerms } = useAccounts();
@@ -24,26 +28,40 @@ export const AcknowledgeTerms = ({ onContinue }: ElementProps) => {
return ( return (
<> <>
<span> <p>
{stringGetter({ {stringGetter({
key: STRING_KEYS.LEGAL_UPDATES_DESCRIPTION, key: STRING_KEYS.TOS_TITLE,
params: { params: {
TOU: ( TERMS_LINK: (
<Styled.Link href="https://dydx.exchange/v4-terms"> <Styled.Link href={`/#${AppRoute.Terms}`}>
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })} {stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
</Styled.Link> </Styled.Link>
), ),
PRIVACY_POLICY: ( PRIVACY_POLICY_LINK: (
<Styled.Link href="https://dydx.exchange/privacy"> <Styled.Link href={`/#${AppRoute.Privacy}`}>
{stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })} {stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })}
</Styled.Link> </Styled.Link>
), ),
}, },
})} })}
</span> </p>
<Button onClick={onAcknowledgement} action={ButtonAction.Primary}> <Styled.TOS>
{stringGetter({ key: STRING_KEYS.I_AGREE })} <ul>
</Button> <li>{stringGetter({ key: STRING_KEYS.TOS_LINE1 })}</li>
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE2 })}</li>
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE3 })}</li>
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE4 })}</li>
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE5 })}</li>
</ul>
</Styled.TOS>
<Styled.Row>
<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>
</> </>
); );
}; };
@@ -58,3 +76,23 @@ Styled.Link = styled(Link)`
color: var(--color-accent); color: var(--color-accent);
} }
`; `;
Styled.TOS = styled.section`
background-color: var(--color-layer-4);
padding: 1rem 1rem 1rem 2rem;
border-radius: 0.875rem;
> ul {
${layoutMixins.column};
gap: 1rem;
}
`;
Styled.Row = styled.div`
${layoutMixins.row};
gap: 1rem;
`;
Styled.Button = styled(Button)`
flex-grow: 1;
`;
@@ -264,7 +264,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.StatusCardsContainer = styled.div` Styled.StatusCardsContainer = styled.div`
display: grid; display: grid;
margin-top: 1rem;
gap: 1rem; gap: 1rem;
`; `;
+7 -5
View File
@@ -6,7 +6,7 @@ import { DydxChainAsset } from '@/constants/wallets';
import { formMixins } from '@/styles/formMixins'; import { formMixins } from '@/styles/formMixins';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
import { useAccounts, useStringGetter } from '@/hooks'; import { useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon'; import { AssetIcon } from '@/components/AssetIcon';
import { CopyButton } from '@/components/CopyButton'; import { CopyButton } from '@/components/CopyButton';
@@ -24,9 +24,10 @@ type ElementProps = {
setIsOpen: (open: boolean) => void; setIsOpen: (open: boolean) => void;
}; };
export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }: ElementProps) => { export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.CHAINTOKEN, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const { dydxAddress } = useAccounts(); const { dydxAddress } = useAccounts();
const { chainTokenLabel, usdcLabel } = useTokenConfigs();
const [asset, setAsset] = useState(selectedAsset); const [asset, setAsset] = useState(selectedAsset);
@@ -35,16 +36,17 @@ export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }
value: DydxChainAsset.USDC, value: DydxChainAsset.USDC,
label: ( label: (
<Styled.InlineRow> <Styled.InlineRow>
<AssetIcon symbol="USDC" /> USDC <AssetIcon symbol="USDC" /> {usdcLabel}
</Styled.InlineRow> </Styled.InlineRow>
), ),
}, },
{ {
value: DydxChainAsset.DYDX, value: DydxChainAsset.CHAINTOKEN,
label: ( label: (
<Styled.InlineRow> <Styled.InlineRow>
{/* TODO: conditionally display the native token assetIcon */}
{/* <AssetIcon symbol="DYDX" /> */} {/* <AssetIcon symbol="DYDX" /> */}
Dv4TNT {chainTokenLabel}
</Styled.InlineRow> </Styled.InlineRow>
), ),
}, },
@@ -29,7 +29,7 @@ export const AdvancedTradeOptions = () => {
const currentTradeFormConfig = useSelector(getInputTradeOptions, shallowEqual); const currentTradeFormConfig = useSelector(getInputTradeOptions, shallowEqual);
const inputTradeData = useSelector(getInputTradeData, shallowEqual); const inputTradeData = useSelector(getInputTradeData, shallowEqual);
const { execution, goodTil, postOnly, reduceOnly, timeInForce } = inputTradeData || {}; const { execution, goodTil, postOnly, reduceOnly, timeInForce, type } = inputTradeData || {};
const { executionOptions, needsGoodUntil, needsPostOnly, needsReduceOnly, timeInForceOptions } = const { executionOptions, needsGoodUntil, needsPostOnly, needsReduceOnly, timeInForceOptions } =
currentTradeFormConfig || {}; currentTradeFormConfig || {};
@@ -37,6 +37,7 @@ export const AdvancedTradeOptions = () => {
const { duration, unit } = goodTil || {}; const { duration, unit } = goodTil || {};
const needsExecution = executionOptions || needsPostOnly || needsReduceOnly; const needsExecution = executionOptions || needsPostOnly || needsReduceOnly;
const hasTimeInForce = timeInForceOptions?.toArray()?.length;
return ( return (
<Styled.Collapsible <Styled.Collapsible
@@ -47,7 +48,7 @@ export const AdvancedTradeOptions = () => {
> >
<Styled.AdvancedInputsContainer> <Styled.AdvancedInputsContainer>
<Styled.AdvancedInputsRow needsGoodUntil={needsGoodUntil}> <Styled.AdvancedInputsRow needsGoodUntil={needsGoodUntil}>
{timeInForceOptions?.toArray() && ( {hasTimeInForce && (
<Styled.SelectMenu <Styled.SelectMenu
value={timeInForce} value={timeInForce}
onValueChange={(selectedTimeInForceOption: string) => onValueChange={(selectedTimeInForceOption: string) =>
@@ -72,7 +73,9 @@ export const AdvancedTradeOptions = () => {
id="trade-good-til-time" id="trade-good-til-time"
type={InputType.Number} type={InputType.Number}
decimals={INTEGER_DECIMALS} decimals={INTEGER_DECIMALS}
label={stringGetter({ key: STRING_KEYS.TIME })} label={stringGetter({
key: hasTimeInForce ? STRING_KEYS.TIME : STRING_KEYS.GOOD_TIL_TIME,
})}
onChange={({ value }: NumberFormatValues) => { onChange={({ value }: NumberFormatValues) => {
abacusStateManager.setTradeValue({ abacusStateManager.setTradeValue({
value: Number(value), value: Number(value),
+10 -12
View File
@@ -11,11 +11,7 @@ import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks'; import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { NumberSign } from '@/constants/numbers'; import { NumberSign } from '@/constants/numbers';
import { import { DydxChainAsset } from '@/constants/wallets';
DYDX_CHAIN_ASSET_COIN_DENOM,
DYDX_CHAIN_ASSET_TAGS,
DydxChainAsset,
} from '@/constants/wallets';
import { import {
useAccountBalance, useAccountBalance,
@@ -25,6 +21,7 @@ import {
useSelectedNetwork, useSelectedNetwork,
useStringGetter, useStringGetter,
useSubaccount, useSubaccount,
useTokenConfigs,
} from '@/hooks'; } from '@/hooks';
import { formMixins } from '@/styles/formMixins'; import { formMixins } from '@/styles/formMixins';
@@ -57,7 +54,7 @@ type TransferFormProps = {
}; };
export const TransferForm = ({ export const TransferForm = ({
selectedAsset = DydxChainAsset.DYDX, selectedAsset = DydxChainAsset.CHAINTOKEN,
onDone, onDone,
className, className,
}: TransferFormProps) => { }: TransferFormProps) => {
@@ -72,6 +69,7 @@ export const TransferForm = ({
const { transfer } = useSubaccount(); const { transfer } = useSubaccount();
const { nativeTokenBalance, usdcBalance } = useAccountBalance(); const { nativeTokenBalance, usdcBalance } = useAccountBalance();
const { selectedNetwork } = useSelectedNetwork(); const { selectedNetwork } = useSelectedNetwork();
const { tokensConfigs, usdcLabel, chainTokenLabel } = useTokenConfigs();
// User Input // User Input
const [asset, setAsset] = useState<DydxChainAsset>(selectedAsset); const [asset, setAsset] = useState<DydxChainAsset>(selectedAsset);
@@ -138,7 +136,7 @@ export const TransferForm = ({
try { try {
// Subtract fees from amount if sending native tokens // Subtract fees from amount if sending native tokens
const amountToTransfer = ( const amountToTransfer = (
asset === DydxChainAsset.DYDX ? amountBN.minus(fee) : amountBN asset === DydxChainAsset.CHAINTOKEN ? amountBN.minus(fee) : amountBN
).toNumber(); ).toNumber();
const screenResults = await screenAddresses({ const screenResults = await screenAddresses({
@@ -161,7 +159,7 @@ export const TransferForm = ({
const txResponse = await transfer( const txResponse = await transfer(
amountToTransfer, amountToTransfer,
recipientAddress as string, recipientAddress as string,
DYDX_CHAIN_ASSET_COIN_DENOM[asset] tokensConfigs[asset].denom,
); );
if (txResponse?.code === 0) { if (txResponse?.code === 0) {
@@ -220,16 +218,16 @@ export const TransferForm = ({
value: DydxChainAsset.USDC, value: DydxChainAsset.USDC,
label: ( label: (
<Styled.InlineRow> <Styled.InlineRow>
<AssetIcon symbol="USDC" /> {DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.USDC]} <AssetIcon symbol="USDC" /> {usdcLabel}
</Styled.InlineRow> </Styled.InlineRow>
), ),
}, },
{ {
value: DydxChainAsset.DYDX, value: DydxChainAsset.CHAINTOKEN,
label: ( label: (
<Styled.InlineRow> <Styled.InlineRow>
{/* <AssetIcon symbol="DYDX" /> */} {/* <AssetIcon symbol="DYDX" /> */}
{DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.DYDX]} {chainTokenLabel}
</Styled.InlineRow> </Styled.InlineRow>
), ),
}, },
@@ -251,7 +249,7 @@ export const TransferForm = ({
key: 'amount', key: 'amount',
label: ( label: (
<span> <span>
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[asset]}</Tag> {stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{tokensConfigs[asset].name}</Tag>
</span> </span>
), ),
value: ( value: (
@@ -3,9 +3,9 @@ import { shallowEqual, useSelector } from 'react-redux';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons'; import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { NumberSign } from '@/constants/numbers'; import { NumberSign } from '@/constants/numbers';
import { DYDX_CHAIN_ASSET_TAGS, DydxChainAsset } from '@/constants/wallets'; import { DydxChainAsset } from '@/constants/wallets';
import { useAccountBalance, useStringGetter } from '@/hooks'; import { useAccountBalance, useTokenConfigs, useStringGetter } from '@/hooks';
import { Button } from '@/components/Button'; import { Button } from '@/components/Button';
import { DiffOutput } from '@/components/DiffOutput'; import { DiffOutput } from '@/components/DiffOutput';
@@ -37,6 +37,7 @@ export const TransferButtonAndReceipt = ({
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual); const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { size } = useSelector(getTransferInputs, shallowEqual) || {}; const { size } = useSelector(getTransferInputs, shallowEqual) || {};
const { tokensConfigs } = useTokenConfigs();
const { equity: equityInfo, leverage: leverageInfo } = const { equity: equityInfo, leverage: leverageInfo } =
useSelector(getSubaccount, shallowEqual) || {}; useSelector(getSubaccount, shallowEqual) || {};
@@ -60,7 +61,7 @@ export const TransferButtonAndReceipt = ({
key: 'fees', key: 'fees',
label: ( label: (
<span> <span>
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag> {stringGetter({ key: STRING_KEYS.FEES })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
</span> </span>
), ),
value: <Output type={OutputType.Asset} value={fees} />, value: <Output type={OutputType.Asset} value={fees} />,
@@ -69,7 +70,7 @@ export const TransferButtonAndReceipt = ({
key: 'balance', key: 'balance',
label: ( label: (
<span> <span>
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag> {stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
</span> </span>
), ),
value: ( value: (
+17 -4
View File
@@ -9,7 +9,13 @@ import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS, TOOLTIP_STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS, TOOLTIP_STRING_KEYS } from '@/constants/localization';
import { DydxChainAsset, wallets } from '@/constants/wallets'; import { DydxChainAsset, wallets } from '@/constants/wallets';
import { useAccounts, useBreakpoints, useStringGetter, useAccountBalance } from '@/hooks'; import {
useAccounts,
useBreakpoints,
useTokenConfigs,
useStringGetter,
useAccountBalance,
} from '@/hooks';
import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton'; import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton';
@@ -39,6 +45,7 @@ export const AccountMenu = () => {
const onboardingState = useSelector(getOnboardingState); const onboardingState = useSelector(getOnboardingState);
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {}; const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { nativeTokenBalance } = useAccountBalance(); const { nativeTokenBalance } = useAccountBalance();
const { usdcLabel, chainTokenLabel } = useTokenConfigs();
const { evmAddress, walletType, dydxAddress, hdKey } = useAccounts(); const { evmAddress, walletType, dydxAddress, hdKey } = useAccounts();
@@ -107,17 +114,23 @@ export const AccountMenu = () => {
<div> <div>
<div> <div>
<Styled.label> <Styled.label>
{stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'Dv4TNT' } })} {stringGetter({
key: STRING_KEYS.ASSET_BALANCE,
params: { ASSET: chainTokenLabel },
})}
{/* <AssetIcon symbol="DYDX" /> */} {/* <AssetIcon symbol="DYDX" /> */}
</Styled.label> </Styled.label>
<Styled.BalanceOutput type={OutputType.Asset} value={nativeTokenBalance} /> <Styled.BalanceOutput type={OutputType.Asset} value={nativeTokenBalance} />
</div> </div>
<AssetActions asset={DydxChainAsset.DYDX} dispatch={dispatch} /> <AssetActions asset={DydxChainAsset.CHAINTOKEN} dispatch={dispatch} />
</div> </div>
<div> <div>
<div> <div>
<Styled.label> <Styled.label>
{stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'USDC' } })} {stringGetter({
key: STRING_KEYS.ASSET_BALANCE,
params: { ASSET: usdcLabel },
})}
<AssetIcon symbol="USDC" /> <AssetIcon symbol="USDC" />
</Styled.label> </Styled.label>
<Styled.BalanceOutput <Styled.BalanceOutput
+5 -4
View File
@@ -178,10 +178,11 @@ export const NotificationsMenu = ({
}; };
const $UnreadIndicator = styled.div` const $UnreadIndicator = styled.div`
width: 0.5em; width: 0.5rem;
height: 0.5em; height: 0.5rem;
border-radius: 50%; border-radius: 50%;
background-color: var(--color-accent); background-color: var(--color-accent);
border: 1px solid var(--color-layer-2);
`; `;
const $TriggerContainer = styled.div` const $TriggerContainer = styled.div`
@@ -192,8 +193,8 @@ const $TriggerUnreadIndicator = styled($UnreadIndicator)`
place-self: center; place-self: center;
position: relative; position: relative;
right: -0.425em; right: -0.2rem;
top: -0.425em; top: -0.325rem;
`; `;
const $Output = styled(Output)` const $Output = styled(Output)`