Compare commits

...
Author SHA1 Message Date
John Huang d40465e9ca TRCL-3010 Change test net chain ID and token denom 2023-10-16 13:37:11 -07:00
aleka 348e82293e show exchange offline dialog for testnet reset (#71) 2023-10-16 13:08:54 -04:00
Jared Vu 48a48b4d70 Screen (#56)
* useDydxClient: Add screen endpoint

* Screen api in onboarding flow

* PnlChart rounding

* UnauthorizedDialog

* Add RateLimitDialog

* Update dialog open logic

* nits

* Screen before Transfer

* Validate network, remove input autofill color

* Use screenAddresses from dydxClient

* Check recipientAddresses

* lock-file

* Change Wallet Restricted Dialog to v4-blocked message

* nits

* Unused param

* consolidate environment validateFn

* Remove extra space, simplify WithdrawForm error msg

* Simply map logic, rearrange priority of screened messages

* WalletRestricted -> RestrictedWallet

* Update restriction modal title and add something went wrong

* Handle 429 rate limit
2023-10-16 09:36:09 -07:00
Bill 944cea0d3d Hide wallet options (#70)
* Hide wallet options

* remove WC 1

* gitignore .env

* remove .env

* remove wc1
2023-10-16 09:07:35 -07:00
Rui 4ea2bdf53d Updated wallets.json and add chain name and logo to env.json (#68) 2023-10-13 14:44:34 -07:00
aleka c40f5eb549 update about wallets link and localized string nits (#69)
* update about wallets link and localized string nits

* udpate wallet connect2 metadata
2023-10-12 17:10:51 -04:00
Bill d53e8f693e Update Squid URL (#67) 2023-10-11 11:34:36 -07:00
Rui f9e301b3f0 Add script to inject iOS app ID to public/.well-known/apple-app-site-association (#66)
* Add script to inject iOS app ID to public/.well-known/apple-app-site-association

* package.json

* Fix typo

* Fix typo

* Fix build

* Debug

* Updated to use "dist" folder
2023-10-10 15:58:26 -07:00
John Huang 67ecbd75b4 Added USDC gasDenom, and Staging West label (#65)
* TRCL-2892-configs-changes

* Additional changes for main net template, and WalletConnect payloads

* Added logo file used for WalletConnect, and changed name and description in WC

* Removed a comma

* TRCL-2898 Additional node provider

* Added USDC gasDenom, and Staging West label
2023-10-09 17:08:04 -04:00
Bill a05d88136d Polishes for Onboarding (#57)
* Polishes for Onboarding

* update string

* bump packages

* address nit
2023-10-09 09:34:49 -07:00
Jared Vu f1ca86326d Fix GoodTil var changes (#64) 2023-10-09 08:02:32 -07:00
aleka 5d1d734c59 add config for sign typed data domain name and action for networks (#62) 2023-10-09 10:51:47 -04:00
aleka 9ba6b3d6ba fix typo in walletconnect wallet type (#63) 2023-10-06 15:50:52 -07:00
Jared Vu 62eeab06f5 Fix marketDetails stringKey, do not update state with markets not tracked by Abacus (#61) 2023-10-06 14:12:40 -07:00
John Huang fa621a085b TRCL-2892-configs-changes (#60)
* TRCL-2892-configs-changes

* Additional changes for main net template, and WalletConnect payloads

* Added logo file used for WalletConnect, and changed name and description in WC

* Removed a comma

* TRCL-2898 Additional node provider
2023-10-06 13:50:41 -07:00
Bill 0730e71109 Do not hide rewardpage on prod (#58) 2023-10-04 22:57:39 -07:00
Bill fdd3fbd774 Reward page (#52)
* Implement Rewards Page

* more rewards page panels

* add keplr dialog

* Finish up rewards page

* bump localization

* add to nav

* address comments

* Comment out migration work

* rename

* address comments

* add TODO
2023-10-04 13:11:35 -04:00
Jared Vu 15bf8a1b34 Update Abacus v0.6.0 (#55)
* Abacus 6: New init, deprecate asset.symbol

* deprecate asset.symbol

* Update networks constants

* filesystem

* Update networks.ts

* 0.6.0 -> 0.6.3

* Remove Market filter
2023-10-03 13:10:55 -07:00
Bill 6508a8b857 Add all squid evm networks to wagmi (#50)
* Add all squid evm networks to wagmi

* remove alchemy

* address nit
2023-10-02 18:50:39 -04:00
aleka e10e7c10e0 fix dialog area too long on mobile (#54) 2023-10-02 13:57:41 -04:00
John Huang 175a6fe523 Added tokenDenom to the configs. Also changed the environments payload to a map from list (#53)
* Added tokenDenom to the configs. Also changed the environments payload to a map from list

* Make tokens section more comprehensive
2023-09-28 16:10:09 -07:00
John Huang 54181a1f52 TRCL-2701 Move market configs to web app from Vercel (#51) 2023-09-27 17:53:56 -07:00
John Huang e6750589db Added a config file, as a retrievable public json (#49)
* Added a config file, retrievable as public json

* Change deployment name

* Refactor the configs

* Add more links

* typo fix
2023-09-27 15:28:46 -07:00
Bill 6c63e66bd6 bump abacus (#48)
* bump abacus

* bump again
2023-09-27 13:54:22 -04:00
Bill 50606a750f Add Rewards abacus transactions and trade form receipt (#47)
* temp

* add into reciept

* bump localization

* add tag
2023-09-25 17:45:46 -04:00
Bill fe46a4d927 Do not poll evm unless addressOrDenom is provided (#46) 2023-09-25 14:56:20 -04:00
Bill 68e5cb8680 Do not get token balance on withdraw (#45) 2023-09-22 17:12:54 -07:00
Bill dea36da7bf Use abacus for simulate transactions and balance polling (#43)
* Use abacus for simulate transactions and balance polling

* address comment

* use dydx gas token

* address nits
2023-09-22 14:21:42 -07:00
102 changed files with 3469 additions and 897 deletions
-6
View File
@@ -1,6 +0,0 @@
VITE_BASE_URL=https://v4.testnet.dydx.exchange
VITE_ALCHEMY_API_KEY=FP275q327Yh7qswtZWenbPXdZZdnAFmC
VITE_WALLETCONNECT1_BRIDGE=wss://api.dydx.exchange/wc/
VITE_WALLETCONNECT2_PROJECT_ID=fbe94eaa691fa8d929561f8567062b32
+2 -1
View File
@@ -4,5 +4,6 @@ VITE_ALCHEMY_API_KEY=
VITE_PK_ENCRYPTION_KEY=
VITE_WALLETCONNECT1_BRIDGE=
VITE_WALLETCONNECT2_PROJECT_ID=
VITE_V3_TOKEN_ADDRESS=
+2
View File
@@ -33,11 +33,13 @@ jobs:
AMPLITUDE_API_KEY: ${{ secrets.AMPLITUDE_API_KEY }}
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
STATUS_PAGE_SCRIPT_URI: ${{ secrets.STATUS_PAGE_SCRIPT_URI }}
IOS_APP_ID: ${{ secrets.IOS_APP_ID }}
run: |
pnpm run build
pnpm run build:inject-amplitude
pnpm run build:inject-bugsnag
pnpm run build:inject-statuspage
sh scripts/inject-app-deeplinks.sh
- name: Upload to IPFS via web3.storage
uses: dydxprotocol/add-to-web3@v1
+1
View File
@@ -13,6 +13,7 @@ dist
dist-ssr
*.local
*.key
.env
# Editor directories and files
.vscode/*
+4 -3
View File
@@ -10,6 +10,7 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"build:inject-app-deeplinks": "sh scripts/inject-app-deeplinks.sh",
"build:inject-amplitude": "node scripts/inject-amplitude.js",
"build:inject-bugsnag": "node scripts/inject-bugsnag.js",
"build:inject-statuspage": "node scripts/inject-statuspage.js",
@@ -37,9 +38,9 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^0.5.0",
"@dydxprotocol/v4-abacus": "^0.6.4",
"@dydxprotocol/v4-client-js": "^0.36.1",
"@dydxprotocol/v4-localization": "^0.1.11",
"@dydxprotocol/v4-localization": "^0.1.30",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-collapsible": "^1.0.3",
@@ -105,7 +106,7 @@
"reselect": "^4.1.8",
"styled-components": "^5.3.11",
"use-latest": "^1.2.1",
"viem": "^1.1.6",
"viem": "^1.12.2",
"wagmi": "^1.3.7"
},
"devDependencies": {
+85 -63
View File
@@ -27,14 +27,14 @@ dependencies:
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/v4-abacus':
specifier: ^0.5.0
version: 0.5.0
specifier: ^0.6.4
version: 0.6.4
'@dydxprotocol/v4-client-js':
specifier: ^0.36.1
version: 0.36.1
'@dydxprotocol/v4-localization':
specifier: ^0.1.11
version: 0.1.11
specifier: ^0.1.30
version: 0.1.30
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@@ -231,11 +231,11 @@ dependencies:
specifier: ^1.2.1
version: 1.2.1(@types/react@18.2.14)(react@18.2.0)
viem:
specifier: ^1.1.6
version: 1.1.6(typescript@5.1.3)
specifier: ^1.12.2
version: 1.12.2(typescript@5.1.3)
wagmi:
specifier: ^1.3.7
version: 1.3.7(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.1.6)
version: 1.3.7(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
devDependencies:
'@babel/core':
@@ -380,14 +380,14 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/@adraffy/ens-normalize@1.9.0:
resolution: {integrity: sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ==}
dev: false
/@adraffy/ens-normalize@1.9.2:
resolution: {integrity: sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg==}
dev: false
/@adraffy/ens-normalize@1.9.4:
resolution: {integrity: sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw==}
dev: false
/@ampproject/remapping@2.2.1:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
@@ -979,8 +979,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
/@dydxprotocol/v4-abacus@0.5.0:
resolution: {integrity: sha512-K/aLJeOWzmToCn6p9vjBqfwn79/nuKxjZLSzaj1mKNxw25KBFh2q93njNbONFRA72zBElD0K+gw8J/skTpGg1Q==}
/@dydxprotocol/v4-abacus@0.6.4:
resolution: {integrity: sha512-eH+/9Q/OnQtwzBwM1BJyXybFPdivGmFQWrxM71MFWGs6uIQGbZX775mpoa8deweSTyQ/QrRLRQ1mP2BPDgBQYg==}
dev: false
/@dydxprotocol/v4-client-js@0.36.1:
@@ -1010,8 +1010,8 @@ packages:
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@0.1.11:
resolution: {integrity: sha512-eW88t8KJuDT0fNEgaw615+n3gEYxDZYPeHhdBYquVDXjnkhLNznaH8ftmlLPVPyfu7o5aFeU/cw8ZiqRrf/Stw==}
/@dydxprotocol/v4-localization@0.1.30:
resolution: {integrity: sha512-xTlQuQmPz7wfGkjd07giYMAxRrolDbJE3DryJSIMB6R1MBq1wLzAcwp/iR/nELRwilRX9c44Tap0gGt+oAajiQ==}
dev: false
/@dydxprotocol/v4-proto@0.2.1:
@@ -2032,6 +2032,12 @@ packages:
'@noble/hashes': 1.3.1
dev: false
/@noble/curves@1.2.0:
resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
dependencies:
'@noble/hashes': 1.3.2
dev: false
/@noble/ed25519@1.7.3:
resolution: {integrity: sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==}
dev: true
@@ -2049,6 +2055,11 @@ packages:
engines: {node: '>= 16'}
dev: false
/@noble/hashes@1.3.2:
resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==}
engines: {node: '>= 16'}
dev: false
/@noble/secp256k1@1.7.1:
resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==}
@@ -4647,7 +4658,7 @@ packages:
resolution: {integrity: sha512-gYw0ki/EAuV1oSyMxpqandHjnthZjYYy+YWpTAzf8BqfXM3ItcZLpjxfg+3+mXW8HIO+3jw6T9iiqEXsqHaMMw==}
dependencies:
'@safe-global/safe-gateway-typescript-sdk': 3.9.0
viem: 1.1.6(typescript@5.1.3)
viem: 1.12.2(typescript@5.1.3)
transitivePeerDependencies:
- bufferutil
- encoding
@@ -4660,7 +4671,7 @@ packages:
resolution: {integrity: sha512-XJbEPuaVc7b9n23MqlF6c+ToYIS3f7P2Sel8f3cSBQ9WORE4xrSuvhMpK9fDSFqJ7by/brc+rmJR/5HViRr0/w==}
dependencies:
'@safe-global/safe-gateway-typescript-sdk': 3.9.0
viem: 1.1.6(typescript@5.1.3)
viem: 1.12.2(typescript@5.1.3)
transitivePeerDependencies:
- bufferutil
- encoding
@@ -4681,6 +4692,10 @@ packages:
resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==}
dev: false
/@scure/base@1.1.3:
resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==}
dev: false
/@scure/bip32@1.3.0:
resolution: {integrity: sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==}
dependencies:
@@ -4697,6 +4712,14 @@ packages:
'@scure/base': 1.1.1
dev: false
/@scure/bip32@1.3.2:
resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==}
dependencies:
'@noble/curves': 1.2.0
'@noble/hashes': 1.3.2
'@scure/base': 1.1.3
dev: false
/@scure/bip39@1.2.0:
resolution: {integrity: sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==}
dependencies:
@@ -4707,7 +4730,7 @@ packages:
/@scure/bip39@1.2.1:
resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==}
dependencies:
'@noble/hashes': 1.3.1
'@noble/hashes': 1.3.2
'@scure/base': 1.1.1
dev: false
@@ -5408,6 +5431,12 @@ packages:
'@types/node': 20.3.1
dev: false
/@types/ws@8.5.6:
resolution: {integrity: sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==}
dependencies:
'@types/node': 20.3.1
dev: false
/@typescript-eslint/eslint-plugin@5.60.0(@typescript-eslint/parser@5.60.0)(eslint@8.43.0)(typescript@5.1.3):
resolution: {integrity: sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -5904,17 +5933,6 @@ packages:
pretty-format: 27.5.1
dev: true
/@wagmi/chains@1.2.0(typescript@5.1.3):
resolution: {integrity: sha512-dmDRipsE54JfyudOBkuhEexqQWcrZqxn/qiujG8SBzMh/az/AH5xlJSA+j1CPWTx9+QofSMF3B7A4gb6XRmSaQ==}
peerDependencies:
typescript: '>=5.0.4'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
typescript: 5.1.3
dev: false
/@wagmi/chains@1.4.0(typescript@5.1.3):
resolution: {integrity: sha512-9HwJrhcZ1TxyrCbE10y7s1eSiSiyfGam7AHIOLYExaOX+vpOZ+MNTt4orFEDbEpz1fxwJDPPI38lanAUix1OSA==}
peerDependencies:
@@ -5926,7 +5944,7 @@ packages:
typescript: 5.1.3
dev: false
/@wagmi/connectors@2.6.5(@wagmi/chains@1.4.0)(react@18.2.0)(typescript@5.1.3)(viem@1.1.6):
/@wagmi/connectors@2.6.5(@wagmi/chains@1.4.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
resolution: {integrity: sha512-klF31togMDd0qQqEcLl5cCGxjMbL0RRXQ8I1vxmEa3KdGzw6Z3ICVzX7/bDfnNEZcOW7BKyAnZDq7rCt5jTOiw==}
peerDependencies:
'@wagmi/chains': '>=1.3.0'
@@ -5950,7 +5968,7 @@ packages:
abitype: 0.8.7(typescript@5.1.3)
eventemitter3: 4.0.7
typescript: 5.1.3
viem: 1.1.6(typescript@5.1.3)
viem: 1.12.2(typescript@5.1.3)
transitivePeerDependencies:
- '@react-native-async-storage/async-storage'
- bufferutil
@@ -5962,7 +5980,7 @@ packages:
- zod
dev: false
/@wagmi/core@1.3.6(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.1.6):
/@wagmi/core@1.3.6(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
resolution: {integrity: sha512-TXv9ZlRR5aySfERFuWMuo+lKXC/CoqtxVJZVHPqhK1jY+nldMx3AvrWzzF4CccRaMYcVdvPFepvmxzq2A2VvWg==}
peerDependencies:
typescript: '>=5.0.4'
@@ -5972,11 +5990,11 @@ packages:
optional: true
dependencies:
'@wagmi/chains': 1.4.0(typescript@5.1.3)
'@wagmi/connectors': 2.6.5(@wagmi/chains@1.4.0)(react@18.2.0)(typescript@5.1.3)(viem@1.1.6)
'@wagmi/connectors': 2.6.5(@wagmi/chains@1.4.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
abitype: 0.8.7(typescript@5.1.3)
eventemitter3: 4.0.7
typescript: 5.1.3
viem: 1.1.6(typescript@5.1.3)
viem: 1.12.2(typescript@5.1.3)
zustand: 4.4.1(@types/react@18.2.14)(react@18.2.0)
transitivePeerDependencies:
- '@react-native-async-storage/async-storage'
@@ -6677,6 +6695,20 @@ packages:
typescript: 5.1.3
dev: false
/abitype@0.9.8(typescript@5.1.3):
resolution: {integrity: sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ==}
peerDependencies:
typescript: '>=5.0.4'
zod: ^3 >=3.19.1
peerDependenciesMeta:
typescript:
optional: true
zod:
optional: true
dependencies:
typescript: 5.1.3
dev: false
/abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
@@ -9986,12 +10018,12 @@ packages:
ws: 7.5.9
dev: false
/isomorphic-ws@5.0.0(ws@8.12.0):
/isomorphic-ws@5.0.0(ws@8.13.0):
resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==}
peerDependencies:
ws: '*'
dependencies:
ws: 8.12.0
ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)
dev: false
/it-all@1.0.6:
@@ -13520,21 +13552,24 @@ packages:
vfile-message: 3.1.4
dev: true
/viem@1.1.6(typescript@5.1.3):
resolution: {integrity: sha512-7nK3HMucLr1Yz0QnDXiD6viigKS6QeYD/YhRHpi3Bby/g0hCyZqK8+YJNtp3/Ri64tpl4kaTIeCScJWV2jqXHQ==}
/viem@1.12.2(typescript@5.1.3):
resolution: {integrity: sha512-aCaUCyg72ES+jK4s6tVYOMnOt4if71RwzgiUAUpAuaCgvHFfh9DCnwuEfwkxEZLE2vafOsirgJ3fcn7nsDVQoQ==}
peerDependencies:
typescript: '>=5.0.4'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@adraffy/ens-normalize': 1.9.0
'@noble/curves': 1.0.0
'@noble/hashes': 1.3.0
'@scure/bip32': 1.3.0
'@scure/bip39': 1.2.0
'@wagmi/chains': 1.2.0(typescript@5.1.3)
abitype: 0.8.7(typescript@5.1.3)
isomorphic-ws: 5.0.0(ws@8.12.0)
'@adraffy/ens-normalize': 1.9.4
'@noble/curves': 1.2.0
'@noble/hashes': 1.3.2
'@scure/bip32': 1.3.2
'@scure/bip39': 1.2.1
'@types/ws': 8.5.6
abitype: 0.9.8(typescript@5.1.3)
isomorphic-ws: 5.0.0(ws@8.13.0)
typescript: 5.1.3
ws: 8.12.0
ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -13702,7 +13737,7 @@ packages:
- supports-color
dev: true
/wagmi@1.3.7(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.1.6):
/wagmi@1.3.7(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2):
resolution: {integrity: sha512-n4Qwby0QB8/JSGjc0gGn4MWZ0BT229agEg6saPGS6VswmPjJLdkLq9qxKn1JptXxPREmb/ZvwHxU++3Z+gQJVA==}
peerDependencies:
react: '>=17.0.0'
@@ -13715,12 +13750,12 @@ packages:
'@tanstack/query-sync-storage-persister': 4.32.6
'@tanstack/react-query': 4.32.6(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query-persist-client': 4.32.6(@tanstack/react-query@4.32.6)
'@wagmi/core': 1.3.6(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.1.6)
'@wagmi/core': 1.3.6(@types/react@18.2.14)(react@18.2.0)(typescript@5.1.3)(viem@1.12.2)
abitype: 0.8.7(typescript@5.1.3)
react: 18.2.0
typescript: 5.1.3
use-sync-external-store: 1.2.0(react@18.2.0)
viem: 1.1.6(typescript@5.1.3)
viem: 1.12.2(typescript@5.1.3)
transitivePeerDependencies:
- '@react-native-async-storage/async-storage'
- '@types/react'
@@ -13945,19 +13980,6 @@ packages:
optional: true
dev: false
/ws@8.12.0:
resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10):
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
engines: {node: '>=10.0.0'}
@@ -4,7 +4,7 @@
"details": [
{
"appIDs": [
"75C6UARB5H.exchangeV4.dydx.trading"
"{PLACE_YOUR_APP_ID_HERE}"
],
"components": [
{
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 122 KiB

+860
View File
@@ -0,0 +1,860 @@
{
"deployments": {
"MAINNET": {
"environments": [
"dydxprotocol-mainnet"
],
"default": "dydxprotocol-mainnet"
},
"TESTNET": {
"environments": [
"dydxprotocol-testnet"
],
"default": "dydxprotocol-testnet"
},
"DEV": {
"environments": [
"dydxprotocol-dev",
"dydxprotocol-dev-2",
"dydxprotocol-dev-4",
"dydxprotocol-dev-5",
"dydxprotocol-staging",
"dydxprotocol-staging-west",
"dydxprotocol-testnet",
"dydxprotocol-testnet-dydx",
"dydxprotocol-testnet-dsrv",
"dydxprotocol-testnet-nodefleet",
"dydxprotocol-testnet-kingnodes",
"dydxprotocol-testnet-liquify"
],
"default": "dydxprotocol-testnet"
}
},
"environments": {
"dydxprotocol-dev": {
"name": "v4 Dev",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "http://indexer.v4dev.dydx.exchange",
"socket": "wss://indexer.v4dev.dydx.exchange"
}
],
"validators": [
"http://validator.v4dev.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"faucet": "http://faucet.v4dev.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-dev-2": {
"name": "v4 Dev 2",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "http://dev2-indexer-apne1-lb-public-2076363889.ap-northeast-1.elb.amazonaws.com",
"socket": "ws://dev2-indexer-apne1-lb-public-2076363889.ap-northeast-1.elb.amazonaws.com"
}
],
"validators": [
"http://35.75.227.118"
],
"0xsquid": "https://testnet.api.0xsquid.com"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-dev-4": {
"name": "v4 Dev 4",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "http://indexer.v4dev4.dydx.exchange",
"socket": "ws://indexer.v4dev4.dydx.exchange"
}
],
"validators": [
"http://validator.v4dev4.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-dev-5": {
"name": "v4 Dev 5",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "http://dev5-indexer-apne1-lb-public-1721328151.ap-northeast-1.elb.amazonaws.com",
"socket": "ws://dev5-indexer-apne1-lb-public-1721328151.ap-northeast-1.elb.amazonaws.com"
}
],
"validators": [
"http://18.223.78.50"
],
"0xsquid": "https://testnet.api.0xsquid.com"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-staging": {
"name": "v4 Staging",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4staging.dydx.exchange",
"socket": "wss://indexer.v4staging.dydx.exchange"
}
],
"faucet": "https://faucet.v4staging.dydx.exchange",
"validators": [
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-staging-west": {
"name": "v4 Staging West",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4staging.dydx.exchange",
"socket": "wss://indexer.v4staging.dydx.exchange"
}
],
"faucet": "https://faucet.v4staging.dydx.exchange",
"validators": [
"https://validator-uswest1.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-testnet": {
"name": "v4 Public Testnet",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-testnet.nodefleet.org",
"https://test-dydx.kingnodes.com",
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX V4 Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4testnet.dydx.exchange",
"socket": "wss://indexer.v4testnet.dydx.exchange"
}
],
"validators": [
"https://validator.v4testnet.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX V4 Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-dsrv": {
"name": "v4 Public Testnet/DSRV",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-testnet-archive.allthatnode.com:26657/XZvMM41hESf8PJrEQiTzbCOMVyFca79R"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX V4 Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-testnet.nodefleet.org"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX V4 Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://test-dydx.kingnodes.com"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX V4 Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://dydx-testnet.imperator.co",
"socket": "wss://dydx-testnet.imperator.co"
}
],
"validators": [
"https://dydx-rpc.liquify.com/api=8878132/dydx"
],
"0xsquid": "https://testnet.api.squidrouter.com",
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v1": {
"bridgeUrl": "wss://api.stage.dydx.exchange/wc/"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX V4 Onboarding",
"signTypedDataDomainName": "dYdX V4"
}
},
"dydxprotocol-mainet": {
"name": "v4",
"ethereumChainId": "1",
"dydxChainId": "[mainnet chain id]",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": true,
"tokens": {
"comment": "Change according to mainnet release",
"chain": {
"name": "DYDX",
"denom": "adv4tnt",
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "[REST endpoint]",
"socket": "[Websocket endpoint]"
}
],
"validators": [
"[Validator endpoint 1",
"[Validator endpoint n]"
],
"0xsquid": "[0xSquid endpoint for mainnet]"
},
"links": {
"tos": "[HTTP link to TOS]",
"privacy": "[HTTP link to Privacy Policy]",
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
"documentation": "[HTTP link to documentation, can be null]",
"community": "[HTTP link to community, can be null]",
"feedback": "[HTTP link to feedback form, can be null]",
"blogs": "[HTTP link to blogs, can be null]"
},
"wallets": {
"walletconnect": {
"client": {
"name": "[Name of the app]",
"description": "[Description of the app]",
"iconUrl": "[Relative URL of the icon URL]"
},
"v1": {
"bridgeUrl": "[Bridge URL]"
},
"v2": {
"projectId": "[Project ID]"
}
},
"walletSegue": {
"callbackUrl": "[Relative callback URL for WalletSegue, should match apple-app-site-association]"
},
"images": "[Relative URL for wallet images]",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
}
}
}
}
+454
View File
@@ -0,0 +1,454 @@
{
"1INCH-USD":{
"name":"1inch",
"tags":[
"Defi"
],
"websiteLink":"https://1inch.io/",
"whitepaperLink":"https://github.com/1inch",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/1inch/"
},
"AAVE-USD":{
"name":"Aave",
"tags":[
"Defi"
],
"websiteLink":"https://aave.com/",
"whitepaperLink":"https://github.com/aave/protocol-v2/blob/master/aave-v2-whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aave/"
},
"ADA-USD":{
"name":"Cardano",
"tags":[
"Layer 1"
],
"websiteLink":"https://cardano.org/",
"whitepaperLink":"https://why.cardano.org/en/introduction/motivation/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cardano/"
},
"ALGO-USD":{
"name":"Algorand",
"tags":[
"Layer 1"
],
"websiteLink":"https://algorand.com/",
"whitepaperLink":"https://algorand.com/technology/white-papers",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/algorand/"
},
"APE-USD":{
"name":"ApeCoin",
"tags":[
],
"websiteLink":"https://apecoin.com/",
"whitepaperLink":"https://apecoin.com/about",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/apecoin-ape/"
},
"APT-USD":{
"name":"Aptos",
"tags":[
"Layer 1"
],
"websiteLink":"https://aptoslabs.com/",
"whitepaperLink":"https://aptos.dev/aptos-white-paper/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/aptos/"
},
"ARB-USD":{
"name":"Arbitrum",
"tags":[
],
"websiteLink":"https://arbitrum.io/",
"whitepaperLink":"https://github.com/OffchainLabs/nitro",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/arbitrum/"
},
"ATOM-USD":{
"name":"Cosmos",
"tags":[
"Layer 1"
],
"websiteLink":"https://cosmos.network/",
"whitepaperLink":"https://v1.cosmos.network/resources/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/cosmos/"
},
"AVAX-USD":{
"name":"Avalanche",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.avalabs.org/",
"whitepaperLink":"https://assets.website-files.com/5d80307810123f5ffbb34d6e/6008d7bbf8b10d1eb01e7e16_Avalanche%20Platform%20Whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/avalanche/"
},
"BCH-USD":{
"name":"Bitcoin Cash",
"tags":[
"Layer 1"
],
"websiteLink":"https://bitcoincash.org/",
"whitepaperLink":"https://bitcoincash.org/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin-cash/"
},
"BLUR-USD":{
"name":"Blur",
"tags":[
],
"websiteLink":"https://blur.io/",
"whitepaperLink":"https://docs.blur.foundation/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/blur-token/"
},
"CELO-USD":{
"name":"Celo",
"tags":[
],
"websiteLink":"https://celo.org",
"whitepaperLink":"https://docs.celo.org",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/celo/"
},
"BTC-USD":{
"name":"Bitcoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://bitcoin.org/",
"whitepaperLink":"https://bitcoin.org/bitcoin.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/bitcoin/"
},
"COMP-USD":{
"name":"Compound",
"tags":[
"Defi"
],
"websiteLink":"https://compound.finance/",
"whitepaperLink":"https://compound.finance/documents/Compound.Whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/compound/"
},
"CRV-USD":{
"name":"Curve",
"tags":[
"Governance"
],
"websiteLink":"https://curve.fi/",
"whitepaperLink":"https://curve.fi/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/curve-dao-token/"
},
"DOGE-USD":{
"name":"Dogecoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://dogecoin.com/",
"whitepaperLink":"https://github.com/dogecoin/dogecoin",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/dogecoin/"
},
"DOT-USD":{
"name":"Polkadot",
"tags":[
"Layer 1"
],
"websiteLink":"https://polkadot.network/",
"whitepaperLink":"https://polkadot.network/PolkaDotPaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polkadot-new/"
},
"ENJ-USD":{
"name":"Enjin",
"tags":[
],
"websiteLink":"https://enjin.io/",
"whitepaperLink":"https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/enjin-coin/"
},
"EOS-USD":{
"name":"EOS",
"tags":[
"Layer 1"
],
"websiteLink":"https://eos.io/",
"whitepaperLink":"https://github.com/EOSIO/Documentation/blob/master/TechnicalWhitePaper.md",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/eos/"
},
"ETC-USD":{
"name":"Ethereum Classic",
"tags":[
"Layer 1"
],
"websiteLink":"https://ethereumclassic.org/",
"whitepaperLink":"https://ethereumclassic.org/why-classic",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum-classic/"
},
"ETH-USD":{
"name":"Ethereum",
"tags":[
"Layer 1"
],
"websiteLink":"https://ethereum.org/",
"whitepaperLink":"https://ethereum.org/whitepaper/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/ethereum/",
"displayStepSize":"0.001",
"displayTickSize":"0.1"
},
"FIL-USD":{
"name":"Filecoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://filecoin.io/",
"whitepaperLink":"https://filecoin.io/filecoin.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/filecoin/"
},
"HNT-USD":{
"name":"Helium",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.helium.com",
"whitepaperLink":"http://whitepaper.helium.com",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/helium/"
},
"ICP-USD":{
"name":"Internet Computer",
"tags":[
"Layer 1"
],
"websiteLink":"https://dfinity.org",
"whitepaperLink":"https://dfinity.org/whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/internet-computer/"
},
"LDO-USD":{
"name":"Lido DAO",
"tags":[
"Defi"
],
"websiteLink":"https://lido.fi/",
"whitepaperLink":"https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/lido-dao/"
},
"LINK-USD":{
"name":"Chainlink",
"tags":[
"Defi"
],
"websiteLink":"https://chain.link/",
"whitepaperLink":"https://link.smartcontract.com/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/chainlink/"
},
"LTC-USD":{
"name":"Litecoin",
"tags":[
"Layer 1"
],
"websiteLink":"https://litecoin.org/",
"whitepaperLink":"https://litecoin.info/index.php/Main_Page",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/litecoin/"
},
"MATIC-USD":{
"name":"Polygon",
"tags":[
"Layer 2"
],
"websiteLink":"https://polygon.technology/",
"whitepaperLink":"https://polygon.technology/lightpaper-polygon.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/polygon/"
},
"MKR-USD":{
"name":"Maker",
"tags":[
"Governance"
],
"websiteLink":"https://makerdao.com",
"whitepaperLink":"https://makerdao.com/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/maker/"
},
"NEAR-USD":{
"name":"NEAR Protocol",
"tags":[
"Layer 1"
],
"websiteLink":"https://near.org",
"whitepaperLink":"https://near.org/papers/the-official-near-white-paper/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/near-protocol/"
},
"OP-USD":{
"name":"Optimism",
"tags":[
],
"websiteLink":"https://www.optimism.io/",
"whitepaperLink":"https://github.com/ethereum-optimism",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/optimism-ethereum/"
},
"PEPE-USD":{
"name":"Pepe",
"tags":[
],
"websiteLink":"https://www.pepe.vip/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/pepe/"
},
"RUNE-USD":{
"name":"THORChain",
"tags":[
"Layer 1"
],
"websiteLink":"https://thorchain.org",
"whitepaperLink":"https://whitepaper.io/document/709/thorchain-whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/thorchain/"
},
"SEI-USD":{
"name":"Sei",
"tags":[
"Layer 1",
"Defi"
],
"websiteLink":"https://www.sei.io/",
"whitepaperLink":"https://github.com/sei-protocol/sei-chain/blob/3c9576fee3494ce039df684624f918dd8066ba3f/whitepaper/Sei_Whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sei/"
},
"SHIB-USD":{
"name":"Shiba Inu",
"tags":[
],
"websiteLink":"https://shibatoken.com/",
"whitepaperLink":"https://docs.shibatoken.com/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/shiba-inu/"
},
"SNX-USD":{
"name":"Synthetix",
"tags":[
"Defi"
],
"websiteLink":"https://synthetix.io/",
"whitepaperLink":"https://docs.synthetix.io/litepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/synthetix-network-token/"
},
"SOL-USD":{
"name":"Solana",
"tags":[
"Layer 1"
],
"websiteLink":"https://solana.com/",
"whitepaperLink":"https://solana.com/solana-whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/solana/"
},
"SUI-USD":{
"name":"Sui",
"tags":[
"Layer 1"
],
"websiteLink":"https://sui.io/",
"whitepaperLink":"https://github.com/MystenLabs",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sui/"
},
"SUSHI-USD":{
"name":"Sushi",
"tags":[
"Defi"
],
"websiteLink":"https://sushi.com/",
"whitepaperLink":"https://docs.sushi.com/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/sushiswap/"
},
"TRX-USD":{
"name":"TRON",
"tags":[
"Defi"
],
"websiteLink":"https://tron.network/",
"whitepaperLink":"https://tron.network/static/doc/white_paper_v_2_0.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tron/"
},
"UMA-USD":{
"name":"UMA",
"tags":[
"Defi"
],
"websiteLink":"https://umaproject.org/",
"whitepaperLink":"https://github.com/UMAprotocol/whitepaper",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uma/"
},
"UNI-USD":{
"name":"Uniswap",
"tags":[
"Defi"
],
"websiteLink":"https://uniswap.org/",
"whitepaperLink":"https://uniswap.org/whitepaper-v3.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/uniswap/"
},
"WLD-USD":{
"name":"Worldcoin",
"tags":[
],
"websiteLink":"https://worldcoin.org/",
"whitepaperLink":"https://whitepaper.worldcoin.org/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/worldcoin-org/"
},
"XLM-USD":{
"name":"Stellar",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.stellar.org/",
"whitepaperLink":"https://www.stellar.org/papers/stellar-consensus-protocol",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/stellar/"
},
"XMR-USD":{
"name":"Monero",
"tags":[
"Layer 1"
],
"websiteLink":"https://www.getmonero.org/",
"whitepaperLink":"https://www.getmonero.org/resources/research-lab/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/monero/"
},
"XRP-USD":{
"name":"Ripple",
"tags":[
"Layer 1"
],
"websiteLink":"https://ripple.com/currency/",
"whitepaperLink":"https://github.com/ripple",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/xrp/"
},
"XTZ-USD":{
"name":"Tezos",
"tags":[
"Layer 1"
],
"websiteLink":"https://tezos.com",
"whitepaperLink":"https://tezos.com/whitepaper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/tezos/"
},
"YFI-USD":{
"name":"Yearn",
"tags":[
"Defi"
],
"websiteLink":"https://yearn.finance/",
"whitepaperLink":"https://docs.yearn.finance/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/yearn-finance/"
},
"ZEC-USD":{
"name":"Zcash",
"tags":[
"Layer 1"
],
"websiteLink":"https://z.cash/",
"whitepaperLink":"https://z.cash/technology/",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/zcash/"
},
"ZRX-USD":{
"name":"0x",
"tags":[
"Defi"
],
"websiteLink":"https://0x.org/",
"whitepaperLink":"https://0x.org/pdfs/0x_white_paper.pdf",
"coinMarketCapsLink":"https://coinmarketcap.com/currencies/0x/"
}
}
+464
View File
@@ -0,0 +1,464 @@
[
{
"id": "c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96",
"name": "MetaMask",
"description": "",
"homepage": "https://metamask.io/",
"chains": [
"eip155:1",
"eip155:42161",
"eip155:43114"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://apps.apple.com/us/app/metamask/id1438144202",
"android": "https://play.google.com/store/apps/details?id=io.metamask",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "metamask:",
"universal": "https://metamask.app.link"
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "MetaMask",
"colors": {
"primary": "rgb(255, 255, 255)",
"secondary": ""
}
},
"config": {
"comment": "MetaMask",
"iosMinVersion": "0",
"imageUrl": "",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"connections": [
{
"type": "walletConnectV2",
"native": "metamask:",
"universal": "https://metamask.app.link"
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData",
"wallet_addEthereumChain"
]
},
"userFields": {
"analyticEvent": "METAMASK",
"imageName": "metamask.png"
}
},
{
"id": "coinbase-wallet",
"name": "Coinbase Wallet",
"description": "",
"homepage": "https://www.coinbase.com/wallet",
"chains": [
"eip155:1"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://apps.apple.com/us/app/coinbase-wallet-nfts-crypto/id1278383455",
"android": "https://play.google.com/store/apps/details?id=org.toshi",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "cbwallet:",
"universal": ""
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "Coinbase",
"colors": {
"primary": "rgb(255, 255, 255)",
"secondary": ""
}
},
"config": {
"comment": "Coinbase",
"iosMinVersion": "1.8.0",
"imageUrl": "",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"connections": [
{
"type": "walletSegue",
"native": "cbwallet:",
"universal": ""
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData",
"wallet_addEthereumChain"
]
},
"userFields": {
"analyticEvent": "COINBASE_WALLET",
"imageName": "coinbase-wallet.png"
}
},
{
"id": "9d373b43ad4d2cf190fb1a774ec964a1addf406d6fd24af94ab7596e58c291b2",
"name": "imToken",
"description": "",
"homepage": "https://token.im/",
"chains": [
"eip155:1"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://itunes.apple.com/us/app/imtoken2/id1384798940",
"android": "https://play.google.com/store/apps/details?id=im.token.app",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "imtokenv2:",
"universal": ""
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "imToken",
"colors": {
"primary": "rgb(255, 255, 255)",
"secondary": ""
}
},
"config": {
"comment": "imToken",
"iosMinVersion": "0",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "imtokenv2:",
"universal": ""
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData"
]
},
"userFields": {
"analyticEvent": "IMTOKEN",
"imageName": "imtoken.png"
}
},
{
"id": "4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0",
"name": "Trust Wallet",
"description": "",
"homepage": "https://trustwallet.com/",
"chains": [
"eip155:1",
"eip155:42161",
"eip155:43114"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://apps.apple.com/app/apple-store/id1288339409",
"android": "https://play.google.com/store/apps/details?id=com.wallet.crypto.trustapp",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "trust:",
"universal": "https://link.trustwallet.com"
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "Trust",
"colors": {
"primary": "rgb(51, 117, 187)",
"secondary": ""
}
},
"config": {
"comment": "Trust Wallet",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "trust:",
"universal": "https://link.trustwallet.com"
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData"
]
},
"userFields": {
"analyticEvent": "TRUST_WALLET",
"imageName": "trust-wallet.png"
}
},
{
"id": "Zerion",
"name": "Zerion",
"description": "",
"homepage": "https://zerion.io/",
"chains": [
"eip155:1"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://apps.apple.com/app/apple-store/id1456732565",
"android": "https://play.google.com/store/apps/details?id=io.zerion.android",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "zerion:",
"universal": ""
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "Zerion",
"colors": {
"primary": "rgb(51, 117, 187)",
"secondary": ""
}
},
"config": {
"comment": "Zerion Wallet",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "zerion:",
"universal": ""
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData"
]
},
"userFields": {
"analyticEvent": "ZERION",
"imageName": "zerion.png"
}
},
{
"id": "20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66",
"name": "TokenPocket",
"description": "",
"homepage": "https://tokenpocket.pro/",
"chains": [
"eip155:1"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://apps.apple.com/us/app/tokenpocket-trusted-wallet/id1436028697",
"android": "https://play.google.com/store/apps/details?id=vip.mytokenpocket",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "tpoutside:",
"universal": ""
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "TokenPocket",
"colors": {
"primary": "rgb(41, 128, 254)",
"secondary": ""
}
},
"config": {
"comment": "TokenPocket",
"iosMinVersion": "0",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "tpoutside:",
"universal": ""
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData"
]
},
"userFields": {
"analyticEvent": "TOKEN_POCKET",
"imageName": "tokenpocket.png"
}
},
{
"id": "1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369",
"name": "Rainbow",
"description": "",
"homepage": "https://rainbow.me/",
"chains": [
"eip155:1"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://apps.apple.com/us/app/rainbow-ethereum-wallet/id1457119021",
"android": "",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "rainbow:",
"universal": "https://rnbwapp.com"
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "Rainbow",
"colors": {
"primary": "rgb(0, 30, 89)",
"secondary": ""
}
},
"config": {
"comment": "Rainbow",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"backlinked": true,
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "rainbow:",
"universal": "https://rnbwapp.com"
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData"
]
},
"userFields": {
"analyticEvent": "RAINBOW_WALLET",
"imageName": "rainbow-wallet.png"
}
},
{
"id": "09102e7bbbd3f92001eda104abe23803181629f695e8f1b95af96d88ff7d5890",
"name": "1inch Wallet",
"description": "",
"homepage": "https://1inch.io/wallet/",
"chains": [
"eip155:1",
"eip155:56"
],
"versions": [
"1"
],
"app": {
"browser": "",
"ios": "https://apps.apple.com/us/app/1inch-defi-wallet/id1546049391",
"android": "",
"mac": "",
"windows": "",
"linux": ""
},
"mobile": {
"native": "oneinch:",
"universal": "https://wallet.1inch.io"
},
"desktop": {
"native": "",
"universal": ""
},
"metadata": {
"shortName": "1inch",
"colors": {
"primary": "rgb(31, 36, 46)",
"secondary": ""
}
},
"config": {
"comment": "1inch",
"iosMinVersion": "0",
"encoding": "=\"#%/<>?@\\^`{|}:&",
"imageUrl": "",
"connections": [
{
"type": "walletConnectV2",
"native": "oneinch:",
"universal": "https://wallet.1inch.io"
}
],
"methods": [
"eth_sendTransaction",
"personal_sign",
"eth_signTypedData"
]
},
"userFields": {
"analyticEvent": "1INCH",
"imageName": "1inch.png"
}
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
source="dist/.well-known/apple-app-site-association"
if [ ! -f "$source" ]; then
echo "Error: $source file not found"
exit 1
fi
app_id=$IOS_APP_ID
if [ -z "$app_id" ]; then
echo "Error: IOS_APP_ID is empty"
exit 1
fi
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s/{PLACE_YOUR_APP_ID_HERE}/$app_id/g" $source
else
sed -i "s/{PLACE_YOUR_APP_ID_HERE}/$app_id/g" $source
fi
+6 -4
View File
@@ -14,6 +14,7 @@ import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications';
import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
import { RestrictionProvider } from '@/hooks/useRestrictions';
import { SubaccountProvider } from '@/hooks/useSubaccount';
import { SquidProvider } from '@/hooks/useSquid';
@@ -66,10 +67,9 @@ const Content = () => {
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
</Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} />
{import.meta.env.MODE !== 'production' && (
<Route path={AppRoute.Rewards} element={<RewardsPage />} />
)}
<Route path={AppRoute.Rewards} element={<RewardsPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
@@ -81,6 +81,7 @@ const Content = () => {
<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
@@ -111,6 +112,7 @@ const providers = [
wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider),
wrapProvider(AccountsProvider),
wrapProvider(SubaccountProvider),
@@ -197,7 +199,7 @@ Styled.NotificationsToastArea = styled(NotificationsToastArea)`
Styled.DialogArea = styled.aside`
position: fixed;
height: 100vh;
height: 100%;
z-index: 1;
inset: 0;
overflow: clip;
+10
View File
@@ -207,6 +207,16 @@ const InputStyle = css`
color: var(--color-text-0);
opacity: 1;
}
// Input autofill Styles
&:-webkit-autofill,
&:-webkit-autofill:focus {
transition: background-color 600000s 0s, color 600000s 0s;
}
&[data-autocompleted] {
background-color: var(--input-backgroundColor) !important;
}
`;
Styled.NumericFormat = styled(NumericFormat)`
+3 -3
View File
@@ -12,7 +12,7 @@ type ElementProps = {
};
children: React.ReactNode;
href?: string;
onClick?: () => void;
onClick?: (e: MouseEvent) => void;
withIcon?: boolean;
};
@@ -37,12 +37,12 @@ export const Link = forwardRef<HTMLAnchorElement, ElementProps & StyleProps>(
ref={ref}
className={className}
href={href}
onClick={() => {
onClick={(e: MouseEvent) => {
if (analyticsConfig) {
console.log(analyticsConfig);
}
onClick?.();
onClick?.(e);
}}
rel="noopener noreferrer"
target="_blank"
+3 -1
View File
@@ -11,6 +11,7 @@ type PanelProps = {
children?: React.ReactNode;
href?: string;
onHeaderClick?: () => void;
onClick?: () => void;
};
type PanelStyleProps = {
@@ -24,10 +25,11 @@ export const Panel = ({
children,
href,
onHeaderClick,
onClick,
hasSeparator,
className,
}: PanelProps & PanelStyleProps) => (
<Styled.Panel>
<Styled.Panel onClick={onClick}>
{href ? (
<Link to={href}>
{slotHeader ? (
+1 -1
View File
@@ -24,7 +24,7 @@ export const MarketTableCell = ({
slotLeft={
<>
{showFavorite && <Icon iconName={IconName.Star} />}
<Styled.AssetIcon symbol={asset?.symbol} />
<Styled.AssetIcon symbol={asset?.id} />
</>
}
>
+20 -9
View File
@@ -6,6 +6,7 @@ import { STRING_KEYS } from './localization';
export type Nullable<T> = T | null | undefined;
// ------ V4 Protocols ------ //
export const AbacusAppConfig = Abacus.exchange.dydx.abacus.state.manager.AppConfigs;
export const IOImplementations = Abacus.exchange.dydx.abacus.utils.IOImplementations;
export const UIImplementations = Abacus.exchange.dydx.abacus.utils.UIImplementations;
export type AbacusDYDXChainTransactionsProtocol = Omit<
@@ -69,16 +70,21 @@ export type NetworkConfig = {
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
// ------ State ------
export type AbacusApiState = Abacus.exchange.dydx.abacus.state.app.ApiState;
export const AbacusApiStatus = Abacus.exchange.dydx.abacus.state.app.ApiStatus;
export type AbacusApiState = Abacus.exchange.dydx.abacus.state.manager.ApiState;
export const AbacusApiStatus = Abacus.exchange.dydx.abacus.state.manager.ApiStatus;
const abacusApiStatuses = [...AbacusApiStatus.values()];
export type AbacusApiStatuses = (typeof abacusApiStatuses)[number];
export const Changes = Abacus.exchange.dydx.abacus.state.changes.Changes;
export type PerpetualStateChanges = Abacus.exchange.dydx.abacus.state.changes.StateChanges;
export const AsyncAbacusStateManager =
Abacus.exchange.dydx.abacus.state.manager.AsyncAbacusStateManager;
// ------ Parsing Errors ------ //
export type ParsingError = Abacus.exchange.dydx.abacus.responses.ParsingError;
export type ParsingErrors = kollections.List<ParsingError>;
export const ParsingErrorType = Abacus.exchange.dydx.abacus.responses.ParsingErrorType;
const parsingErrorTypes = [...ParsingErrorType.values()] as const;
export type ParsingErrorTypes = (typeof parsingErrorTypes)[number];
// ------ Perpetuals/Markets ------ //
export type PerpetualState = Abacus.exchange.dydx.abacus.output.PerpetualState;
@@ -122,6 +128,7 @@ export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.Sel
// ------ Wallet ------ //
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
export type AccountBalance = Abacus.exchange.dydx.abacus.output.AccountBalance;
export type Subaccount = Abacus.exchange.dydx.abacus.output.Subaccount;
export type SubaccountPosition = Abacus.exchange.dydx.abacus.output.SubaccountPosition;
export type SubaccountOrder = Abacus.exchange.dydx.abacus.output.SubaccountOrder;
@@ -138,7 +145,7 @@ export type SubaccountTransfers = Abacus.exchange.dydx.abacus.output.SubaccountT
// ------ Historical PnL ------ //
export type SubAccountHistoricalPNL = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL;
export type SubAccountHistoricalPNLs = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL[];
export const HistoricalPnlPeriod = Abacus.exchange.dydx.abacus.state.app.HistoricalPnlPeriod;
export const HistoricalPnlPeriod = Abacus.exchange.dydx.abacus.protocols.HistoricalPnlPeriod;
const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
@@ -178,16 +185,14 @@ export const AbacusOrderSide = Abacus.exchange.dydx.abacus.output.input.OrderSid
export const AbacusPositionSide = Abacus.exchange.dydx.abacus.output.PositionSide;
export type AbacusPositionSides = Abacus.exchange.dydx.abacus.output.PositionSide;
export type SubaccountPlaceOrderPayload =
Abacus.exchange.dydx.abacus.state.app.V4SubaccountPlaceOrderPayload2;
export type SubaccountCancelOrderPayload =
Abacus.exchange.dydx.abacus.state.app.V4SubaccountCancelOrderPayload2;
export type HumanReadablePlaceOrderPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload;
export type HumanReadableCancelOrderPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload;
export type HumanReadableWithdrawPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadableWithdrawPayload;
export type HumanReadableTransferPayload =
Abacus.exchange.dydx.abacus.state.manager.HumanReadableTransferPayload;
// ------ Helpers ------ //
export const AbacusHelper = Abacus.exchange.dydx.abacus.utils.AbacusHelper;
@@ -199,6 +204,12 @@ export type RiskLevels = (typeof riskLevels)[number];
// ------ Notifications ------ //
export type AbacusNotification = Abacus.exchange.dydx.abacus.output.Notification;
// ------ Restrictions ------ //
export type UsageRestriction = Abacus.exchange.dydx.abacus.output.UsageRestriction;
export const RestrictionType = Abacus.exchange.dydx.abacus.output.Restriction;
const restrictionTypes = [...RestrictionType.values()] as const;
export type RestrictionTypes = (typeof restrictionTypes)[number];
// ------ Enum Conversions ------ //
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? A
+4
View File
@@ -5,11 +5,15 @@ export enum DialogTypes {
ExchangeOffline = 'ExchangeOffline',
FillDetails = 'FillDetails',
Help = 'Help',
ExternalNavKeplr = 'ExternalNavKeplr',
MnemonicExport = 'MnemonicExport',
MobileSignIn = 'MobileSignIn',
Onboarding = 'Onboarding',
OrderDetails = 'OrderDetails',
RateLimit = 'RateLimit',
Receive = 'Receive',
RestrictedGeo = 'RestrictedGeo',
RestrictedWallet = 'RestrictedWallet',
Trade = 'Trade',
Transfer = 'Transfer',
Withdraw = 'Withdraw',
+12
View File
@@ -0,0 +1,12 @@
import environments from '../../public/configs/env.json';
// TODO: Update for Mainnet deployment
export const AVAILABLE_ENVIRONMENTS =
import.meta.env.MODE === 'production'
? environments.deployments.TESTNET
: environments.deployments.DEV;
export const CURRENT_ABACUS_DEPLOYMENT = import.meta.env.MODE === 'production' ? 'TESTNET' : 'DEV';
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
-29
View File
@@ -1,29 +0,0 @@
import { ENDPOINTS, DEV_ENDPOINTS } from '@dydxprotocol/v4-localization';
export const NETWORK_ENDPOINTS = import.meta.env.MODE === 'production' ? ENDPOINTS : DEV_ENDPOINTS;
export const CLIENT_NETWORK_CONFIGS: Record<
string,
(typeof NETWORK_ENDPOINTS.environments)[number]
> = Object.fromEntries(
NETWORK_ENDPOINTS.environments.map((environment) => [environment.environment, environment])
);
export const DEFAULT_APP_ENVIRONMENT = NETWORK_ENDPOINTS.defaultEnvironment;
export enum DydxV4Network {
V4Mainnet = 'dydxprotocol-mainnet',
V4Staging = 'dydxprotocol-staging',
V4Testnet2 = 'dydxprotocol-testnet',
V4Local = 'dydxprotocol-dev',
}
export type DydxNetwork = keyof typeof CLIENT_NETWORK_CONFIGS;
export function isDydxV4Network(network: any): boolean {
return CLIENT_NETWORK_CONFIGS[network]?.version === 'v4';
}
export function isValidDydxNetwork(network: any): boolean {
return Object.keys(CLIENT_NETWORK_CONFIGS).includes(network);
}
+4
View File
@@ -94,6 +94,10 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MAINTENANCE_MARGIN_FRACTION_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MAINTENANCE_MARGIN_FRACTION_BODY }),
}),
'max-reward': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MAXIMUM_REWARDS_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MAXIMUM_REWARDS_BODY }),
}),
'max-withdraw': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.MAX_WITHDRAW_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.MAX_WITHDRAW_BODY }),
+37 -85
View File
@@ -24,13 +24,14 @@ import {
import { isMetaMask } from '@/lib/wallet/providers';
import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
// Wallet connection types
export enum WalletConnectionType {
CoinbaseWalletSdk = 'coinbaseWalletSdk',
CosmosSigner = 'CosmosSigner',
InjectedEip1193 = 'injectedEip1193',
WalletConnect1 = 'walletConnect1',
WalletConnect2 = 'walletConnect2',
}
@@ -60,10 +61,6 @@ export const walletConnectionTypes: Record<WalletConnectionType, WalletConnectio
name: 'injected EIP-1193 provider',
wagmiConnectorId: 'injected',
},
[WalletConnectionType.WalletConnect1]: {
name: 'WalletConnect 1.0',
wagmiConnectorId: 'walletConnectLegacy',
},
[WalletConnectionType.WalletConnect2]: {
name: 'WalletConnect 2.0',
wagmiConnectorId: 'walletConnect',
@@ -90,7 +87,6 @@ export enum WalletType {
Rainbow = 'RAINBOW_WALLET',
TokenPocket = 'TOKEN_POCKET',
TrustWallet = 'TRUST_WALLET',
WalletConnect = 'WALLETCONNECT',
WalletConnect2 = 'WALLETCONNECT_2',
// TestWallet = 'TEST_WALLET',
OtherWallet = 'OTHER_WALLET',
@@ -102,7 +98,6 @@ type WalletConfig = {
icon: string;
connectionTypes: WalletConnectionType[];
matchesInjectedEip1193?: (provider: ExternalProvider & any) => boolean;
walletconnect1Name?: string;
walletconnect2Id?: string;
};
@@ -115,7 +110,6 @@ export const wallets: Record<WalletType, WalletConfig> = {
WalletConnectionType.InjectedEip1193,
// WalletConnectionType.CoinbaseWalletSdk,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
matchesInjectedEip1193: (provider) =>
Object.entries(wallets).every(
@@ -129,30 +123,23 @@ export const wallets: Record<WalletType, WalletConfig> = {
type: WalletType.BitKeep,
stringKey: STRING_KEYS.BITKEEP,
icon: BitkeepIcon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isBitKeep, // isBitKeepChrome, isBitEthereum
walletconnect1Name: 'bitkeep',
walletconnect2Id: '38f5d18bd8522c244bdd70cb4a68e0e718865155811c043f052fb9f1c51de662',
},
[WalletType.BitPie]: {
type: WalletType.BitPie,
stringKey: STRING_KEYS.BITPIE,
icon: BitpieIcon,
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect1],
connectionTypes: [WalletConnectionType.InjectedEip1193],
matchesInjectedEip1193: (provider) => provider.isBitpie,
walletconnect1Name: 'bitpie',
},
[WalletType.CloverWallet]: {
type: WalletType.CloverWallet,
stringKey: STRING_KEYS.CLOVER_WALLET,
icon: CloverWalletIcon,
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect1],
connectionTypes: [WalletConnectionType.InjectedEip1193],
matchesInjectedEip1193: (provider) => provider.isClover,
// walletconnect1Name: 'clover',
},
[WalletType.CoinbaseWallet]: {
type: WalletType.CoinbaseWallet,
@@ -161,33 +148,23 @@ export const wallets: Record<WalletType, WalletConfig> = {
connectionTypes: [
WalletConnectionType.CoinbaseWalletSdk,
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect1,
WalletConnectionType.WalletConnect2,
],
matchesInjectedEip1193: (provider) => provider.isCoinbaseWallet, // provider.selectedProvider?.isCoinbaseWallet,
walletconnect1Name: 'coinbase',
},
[WalletType.Coin98]: {
type: WalletType.Coin98,
stringKey: STRING_KEYS.COIN98,
icon: Coin98Icon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isCoin98,
walletconnect1Name: 'coin98',
walletconnect2Id: '2a3c89040ac3b723a1972a33a125b1db11e258a6975d3a61252cd64e6ea5ea01',
},
[WalletType.HuobiWallet]: {
type: WalletType.HuobiWallet,
stringKey: STRING_KEYS.HUOBI_WALLET,
icon: HuobiIcon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isHbWallet,
walletconnect2Id: '797c615e2c556b610c048eb35535f212c0dd58de5d03e763120e90a7d1350a77',
},
@@ -195,88 +172,56 @@ export const wallets: Record<WalletType, WalletConfig> = {
type: WalletType.ImToken,
stringKey: STRING_KEYS.IMTOKEN,
icon: ImTokenIcon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isImToken,
walletconnect1Name: 'imtoken',
walletconnect2Id: 'ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef',
},
[WalletType.MathWallet]: {
type: WalletType.MathWallet,
stringKey: STRING_KEYS.MATH_WALLET,
icon: MathWalletIcon,
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect1],
connectionTypes: [WalletConnectionType.InjectedEip1193],
matchesInjectedEip1193: (provider) => provider.isMathWallet,
walletconnect1Name: 'math',
walletconnect2Id: '7674bb4e353bf52886768a3ddc2a4562ce2f4191c80831291218ebd90f5f5e26',
},
[WalletType.MetaMask]: {
type: WalletType.MetaMask,
stringKey: STRING_KEYS.METAMASK,
icon: MetaMaskIcon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: isMetaMask,
walletconnect1Name: 'metamask',
walletconnect2Id: 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96',
},
[WalletType.Rainbow]: {
type: WalletType.Rainbow,
stringKey: STRING_KEYS.RAINBOW_WALLET,
icon: RainbowIcon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isRainbowWallet,
walletconnect1Name: 'rainbow',
walletconnect2Id: '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369',
},
[WalletType.TokenPocket]: {
type: WalletType.TokenPocket,
stringKey: STRING_KEYS.TOKEN_POCKET,
icon: TokenPocketIcon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isTokenPocket,
walletconnect1Name: 'tokenpocket',
walletconnect2Id: '20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66',
},
[WalletType.TrustWallet]: {
type: WalletType.TrustWallet,
stringKey: STRING_KEYS.TRUST_WALLET,
icon: TrustWalletIcon,
connectionTypes: [
WalletConnectionType.InjectedEip1193,
WalletConnectionType.WalletConnect2,
WalletConnectionType.WalletConnect1,
],
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isTrust,
walletconnect1Name: 'trust',
walletconnect2Id: '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0',
},
[WalletType.WalletConnect2]: {
type: WalletType.WalletConnect,
type: WalletType.WalletConnect2,
stringKey: STRING_KEYS.WALLET_CONNECT_2,
icon: WalletConnectIcon,
connectionTypes: [WalletConnectionType.WalletConnect2],
},
[WalletType.WalletConnect]: {
type: WalletType.WalletConnect,
stringKey: STRING_KEYS.WALLET_CONNECT,
icon: WalletConnectIcon,
connectionTypes: [WalletConnectionType.WalletConnect1],
},
[WalletType.Keplr]: {
type: WalletType.Keplr,
stringKey: STRING_KEYS.KEPLR,
@@ -318,20 +263,21 @@ export type WalletConnection = {
export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
/**
* @description typed data to sign for v4 onboarding
* @description typed data to sign for dYdX Chain onboarding
*/
export const SIGN_TYPED_DATA = {
primaryType: 'dYdX',
domain: {
name: 'dYdX V4',
},
types: {
dYdX: [{ name: 'action', type: 'string' }],
},
message: {
action: 'dYdX V4 Onboarding',
},
} as const;
export const getSignTypedData = (selectedNetwork: DydxNetwork) =>
({
primaryType: 'dYdX',
domain: {
name: ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.signTypedDataDomainName,
},
types: {
dYdX: [{ name: 'action', type: 'string' }],
},
message: {
action: ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.signTypedDataAction,
},
} as const);
export type PrivateInformation = ReturnType<typeof onboarding.deriveHDKeyFromEthereumSignature>;
@@ -383,12 +329,18 @@ export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
features: [],
};
// TODO: export this type from abacus instead
export enum DydxChainAsset {
USDC = 'USDC',
DYDX = 'Dv4TNT',
USDC = 'usdc',
DYDX = 'dydx',
}
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
@@ -16,6 +16,7 @@ import { useLocalStorage } from './useLocalStorage';
import { useNow } from './useNow';
import { useOnClickOutside } from './useOnClickOutside';
import { usePageTitlePriceUpdates } from './usePageTitlePriceUpdates';
import { useRestrictions } from './useRestrictions';
import { useShouldShowFooter } from './useShouldShowFooter';
import { useSelectedNetwork } from './useSelectedNetwork';
import { useStringGetter } from './useStringGetter';
@@ -40,6 +41,7 @@ export {
useNow,
useOnClickOutside,
usePageTitlePriceUpdates,
useRestrictions,
useShouldShowFooter,
useSelectedNetwork,
useStringGetter,
+19 -12
View File
@@ -1,22 +1,23 @@
import { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';
import { useBalance } from 'wagmi';
import { StargateClient } from '@cosmjs/stargate';
import { useQuery } from 'react-query';
import { formatUnits } from 'viem';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { USDC_DENOM, DYDX_DENOM } from '@dydxprotocol/v4-client-js';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { EvmAddress } from '@/constants/wallets';
import { convertBech32Address } from '@/lib/addressUtils';
import { MustBigNumber } from '@/lib/numbers';
import { getBalances, getStakingBalances } from '@/state/accountSelectors';
import { getSelectedNetwork } from '@/state/appSelectors';
import { useAccounts } from './useAccounts';
import { usePollNativeTokenBalance } from './usePollNativeTokenBalance';
import { usePollUSDCBalance } from './usePollUSDCBalance';
type UseAccountBalanceProps = {
// Token Items
@@ -36,10 +37,10 @@ type UseAccountBalanceProps = {
* 0xSquid uses this 0x address as the chain's default token.
* @todo We will need to add additional logic here if we 'useAccountBalance' on non-Squid related forms.
*/
const CHAIN_DEFAULT_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
export const CHAIN_DEFAULT_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
export const useAccountBalance = ({
addressOrDenom = CHAIN_DEFAULT_TOKEN_ADDRESS,
addressOrDenom,
assetSymbol,
bech32AddrPrefix,
chainId,
@@ -50,16 +51,16 @@ export const useAccountBalance = ({
const { evmAddress, dydxAddress } = useAccounts();
const selectedNetwork = useSelector(getSelectedNetwork);
const evmChainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId);
const balances = useSelector(getBalances, shallowEqual);
const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const stakingBalances = useSelector(getStakingBalances, shallowEqual);
const evmQuery = useBalance({
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
address: evmAddress,
chainId: typeof chainId === 'number' ? chainId : Number(evmChainId),
token:
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS
? undefined
: (addressOrDenom as EvmAddress),
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS ? undefined : (addressOrDenom as EvmAddress),
watch: true,
});
@@ -92,17 +93,23 @@ export const useAccountBalance = ({
const { formatted: evmBalance } = evmQuery.data || {};
const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance;
const nativeTokenCoinBalance = usePollNativeTokenBalance({ dydxAddress });
const nativeTokenCoinBalance = balances?.[DYDX_DENOM];
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount)
.div(QUANTUM_MULTIPLIER)
.toNumber();
const usdcCoinBalance = usePollUSDCBalance({ dydxAddress });
const usdcCoinBalance = balances?.[USDC_DENOM];
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber();
const nativeStakingCoinBalanace = stakingBalances?.[DYDX_DENOM];
const nativeStakingBalance = MustBigNumber(nativeStakingCoinBalanace?.amount)
.div(QUANTUM_MULTIPLIER)
.toNumber();
return {
balance,
nativeTokenBalance,
nativeStakingBalance,
usdcBalance,
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
+23 -5
View File
@@ -5,18 +5,20 @@ import { AES, enc } from 'crypto-js';
import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-client-js';
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs';
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
import { setOnboardingState, setOnboardingGuard } from '@/state/account';
import { forceOpenDialog } from '@/state/dialogs';
import abacusStateManager from '@/lib/abacus';
import { log } from '@/lib/telemetry';
import { useLocalStorage } from './useLocalStorage';
import { useWalletConnection } from './useWalletConnection';
import { useDydxClient } from './useDydxClient';
import { useLocalStorage } from './useLocalStorage';
import { useRestrictions } from './useRestrictions';
import { useWalletConnection } from './useWalletConnection';
const AccountsContext = createContext<ReturnType<typeof useAccountsContext> | undefined>(undefined);
@@ -57,7 +59,9 @@ const useAccountsContext = () => {
forgetEvmSignature(previousEvmAddress);
}
if (evmAddress) abacusStateManager.setTransfersSourceAddress(evmAddress);
if (evmAddress) {
abacusStateManager.setTransfersSourceAddress(evmAddress);
}
setPreviousEvmAddress(evmAddress);
}, [evmAddress]);
@@ -217,7 +221,6 @@ const useAccountsContext = () => {
}, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]);
// abacus
// TODO: useAbacus({ dydxAddress })
useEffect(() => {
if (dydxAddress) abacusStateManager.setAccount(localDydxWallet);
else abacusStateManager.attemptDisconnectAccount();
@@ -258,6 +261,21 @@ const useAccountsContext = () => {
);
}, [dydxSubaccounts]);
// Restrictions
const { isBadActor, sanctionedAddresses } = useRestrictions();
useEffect(() => {
if (
dydxAddress &&
(isBadActor ||
sanctionedAddresses.has(dydxAddress) ||
(evmAddress && sanctionedAddresses.has(evmAddress)))
) {
dispatch(forceOpenDialog({ type: DialogTypes.RestrictedWallet }));
disconnect();
}
}, [isBadActor, evmAddress, dydxAddress, sanctionedAddresses]);
// Disconnect wallet / accounts
const disconnectLocalDydxWallet = () => {
setLocalDydxWallet(undefined);
+10 -15
View File
@@ -1,29 +1,24 @@
import { isDydxV4Network } from '@/constants/networks';
import { WalletType } from '@/constants/wallets';
import { isTruthy } from '@/lib/isTruthy';
import { useSelectedNetwork } from './useSelectedNetwork';
export const useDisplayedWallets = () => {
const { selectedNetwork } = useSelectedNetwork();
return [
WalletType.MetaMask,
import.meta.env.MODE !== 'production' && isDydxV4Network(selectedNetwork) && WalletType.Keplr,
import.meta.env.MODE !== 'production' && WalletType.Keplr,
WalletType.WalletConnect2,
WalletType.WalletConnect,
WalletType.CoinbaseWallet,
WalletType.ImToken,
WalletType.Rainbow,
WalletType.TrustWallet,
WalletType.HuobiWallet,
WalletType.BitKeep,
WalletType.Coin98,
// Hide these wallet options until they can be properly tested on mainnet
// WalletType.ImToken,
// WalletType.Rainbow,
// WalletType.TrustWallet,
// WalletType.HuobiWallet,
// WalletType.BitKeep,
// WalletType.Coin98,
WalletType.OtherWallet,
].filter(isTruthy);
+25 -1
View File
@@ -17,8 +17,11 @@ import type { NetworkConfig, ConnectNetworkEvent } from '@/constants/abacus';
import { type Candle, RESOLUTION_MAP } from '@/constants/candles';
import { getSelectedNetwork } from '@/state/appSelectors';
import { log } from '@/lib/telemetry';
import { useRestrictions } from './useRestrictions';
type DydxContextType = ReturnType<typeof useDydxClientContext>;
const DydxContext = createContext<DydxContextType>({} as DydxContextType);
DydxContext.displayName = 'dYdXClient';
@@ -178,7 +181,27 @@ const useDydxClientContext = () => {
[requestCandles]
);
// ------ Subacount Methods ------ //
const { updateSanctionedAddresses } = useRestrictions();
const screenAddresses = useCallback(
async ({ addresses }: { addresses: string[] }) => {
if (compositeClient) {
const promises = addresses.map((address) =>
compositeClient.indexerClient.utility.screen(address)
);
const results = await Promise.all(promises);
const screenedAddresses = Object.fromEntries(
addresses.map((address, index) => [address, results[index]?.restricted])
);
updateSanctionedAddresses(screenedAddresses);
return screenedAddresses;
}
},
[compositeClient]
);
return {
// Client initialization
@@ -192,5 +215,6 @@ const useDydxClientContext = () => {
// Public Methods
getCandlesForDatafeed,
screenAddresses,
};
};
+3 -2
View File
@@ -3,13 +3,14 @@ import { useDispatch } from 'react-redux';
import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, isValidDydxNetwork } from '@/constants/networks';
import { DEFAULT_APP_ENVIRONMENT, type DydxNetwork } from '@/constants/networks';
import { useLocalStorage } from '@/hooks';
import { initializeLocalization } from '@/state/app';
import abacusStateManager from '@/lib/abacus';
import { validateAgainstAvailableEnvironments } from '@/lib/network';
export const useInitializePage = () => {
const dispatch = useDispatch();
@@ -18,7 +19,7 @@ export const useInitializePage = () => {
const [localStorageNetwork] = useLocalStorage<DydxNetwork>({
key: LocalStorageKey.SelectedNetwork,
defaultValue: DEFAULT_APP_ENVIRONMENT,
validateFn: isValidDydxNetwork, // backwards compatibility
validateFn: validateAgainstAvailableEnvironments,
});
useEffect(() => {
+4 -3
View File
@@ -6,7 +6,7 @@ import { LocalStorageKey } from '@/constants/localStorage';
import { type TransferNotifcation } from '@/constants/notifications';
import { useAccounts } from '@/hooks/useAccounts';
import { SQUID_ERROR_TYPES, useSquid } from '@/hooks/useSquid';
import { STATUS_ERROR_GRACE_PERIOD, useSquid } from '@/hooks/useSquid';
import { useLocalStorage } from './useLocalStorage';
const LocalNotificationsContext = createContext<
@@ -62,6 +62,7 @@ const useLocalNotificationsContext = () => {
txHash,
toChainId,
fromChainId,
triggeredAt,
status: currentStatus,
} of transferNotifications) {
try {
@@ -70,8 +71,8 @@ const useLocalNotificationsContext = () => {
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
if (status) statuses[txHash] = status;
} catch (error) {
// ignore not found errors since the route might not be available yet
if (error?.errors?.length && error.errors[0].errorType !== SQUID_ERROR_TYPES.NotFoundError) {
// ignore errors for the first 120s since the status might not be available yet
if (!triggeredAt || Date.now() - triggeredAt > STATUS_ERROR_GRACE_PERIOD) {
statuses[txHash] = error;
}
}
+9 -11
View File
@@ -28,15 +28,13 @@ export const useMarketsData = (
const allAssets = useSelector(getAssets, shallowEqual) || {};
const markets = useMemo(() => {
return Object.values(allPerpetualMarkets)
.filter((market) => allAssets[market?.assetId]) // Filter out markets with no asset information
.map((marketData) => ({
asset: allAssets[marketData.assetId],
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
...marketData,
...marketData.perpetual,
...marketData.configs,
})) as MarketData[];
return Object.values(allPerpetualMarkets).map((marketData) => ({
asset: allAssets[marketData.assetId],
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
...marketData,
...marketData.perpetual,
...marketData.configs,
})) as MarketData[];
}, [allPerpetualMarkets, allAssets]);
const filteredMarkets = useMemo(() => {
@@ -45,8 +43,8 @@ export const useMarketsData = (
if (searchFilter) {
return filtered.filter(
({ asset }) =>
asset.name?.toLocaleLowerCase().includes(searchFilter.toLowerCase()) ||
asset.symbol?.toLocaleLowerCase().includes(searchFilter.toLowerCase())
asset?.name?.toLocaleLowerCase().includes(searchFilter.toLowerCase()) ||
asset?.id?.toLocaleLowerCase().includes(searchFilter.toLowerCase())
);
}
return filtered;
-31
View File
@@ -1,31 +0,0 @@
import { useQuery } from 'react-query';
import { useAccounts } from '@/hooks';
import { DydxAddress } from '@/constants/wallets';
import { DYDX_DENOM } from '@dydxprotocol/v4-client-js';
const ACCOUNT_BALANCE_POLLING_INTERVAL = 60_000;
export const usePollNativeTokenBalance = ({
dydxAddress,
interval = ACCOUNT_BALANCE_POLLING_INTERVAL,
}: {
dydxAddress?: DydxAddress;
interval?: number;
}) => {
const { getAccountBalance } = useAccounts();
const { data } = useQuery({
enabled: dydxAddress !== undefined,
queryKey: ['usePollNativeTokenBalance', { dydxAddress }],
queryFn: async () => {
if (!dydxAddress) return;
return await getAccountBalance({ dydxAddress, denom: DYDX_DENOM });
},
refetchInterval: interval,
staleTime: interval,
});
return data;
};
-30
View File
@@ -1,30 +0,0 @@
import { useQuery } from 'react-query';
import { useAccounts } from '@/hooks';
import { DydxAddress } from '@/constants/wallets';
const ACCOUNT_BALANCE_POLLING_INTERVAL = 60_000;
export const usePollUSDCBalance = ({
dydxAddress,
interval = ACCOUNT_BALANCE_POLLING_INTERVAL,
}: {
dydxAddress?: DydxAddress;
interval?: number;
}) => {
const { getAccountBalance } = useAccounts();
const { data } = useQuery({
enabled: dydxAddress !== undefined,
queryKey: ['usePollUSDCBalance', { dydxAddress }],
queryFn: async () => {
if (!dydxAddress) return;
return await getAccountBalance({ dydxAddress });
},
refetchInterval: interval,
staleTime: interval,
});
return data;
};
+53
View File
@@ -0,0 +1,53 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { RestrictionType } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs';
import { getRestrictionType } from '@/state/accountSelectors';
import { forceOpenDialog } from '@/state/dialogs';
import { isTruthy } from '@/lib/isTruthy';
const useRestrictionContext = () => {
const dispatch = useDispatch();
const restrictionType = useSelector(getRestrictionType, shallowEqual);
const [sanctionedAddresses, setSanctionedAddresses] = useState<string[]>([]);
useEffect(() => {
if (restrictionType === RestrictionType.GEO_RESTRICTED) {
dispatch(
forceOpenDialog({ type: DialogTypes.RestrictedGeo, dialogProps: { preventClose: true } })
);
}
}, [restrictionType, dispatch]);
const updateSanctionedAddresses = useCallback(
(screenedAddresses: { [address: string]: boolean }) => {
const toAdd = Object.entries(screenedAddresses)
.filter(([, isSanctioned]) => isSanctioned)
.map(([address]) => address);
if (toAdd.length) {
setSanctionedAddresses([...sanctionedAddresses, ...toAdd]);
}
},
[sanctionedAddresses]
);
return {
isBadActor: restrictionType === RestrictionType.USER_RESTRICTED,
isGeoRestricted: restrictionType === RestrictionType.GEO_RESTRICTED,
updateSanctionedAddresses,
sanctionedAddresses: useMemo(() => new Set(sanctionedAddresses), [sanctionedAddresses]),
};
};
type RestrictionContextType = ReturnType<typeof useRestrictionContext>;
const RestrictionContext = createContext<RestrictionContextType>({} as RestrictionContextType);
RestrictionContext.displayName = 'Restriction';
export const RestrictionProvider = ({ ...props }) => (
<RestrictionContext.Provider value={useRestrictionContext()} {...props} />
);
export const useRestrictions = () => useContext(RestrictionContext);
+18 -2
View File
@@ -1,13 +1,17 @@
import { useCallback } from 'react';
import { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { DialogTypes } from '@/constants/dialogs';
import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork } from '@/constants/networks';
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useAccounts, useLocalStorage } from '@/hooks';
import { setSelectedNetwork } from '@/state/app';
import { getSelectedNetwork } from '@/state/appSelectors';
import { openDialog } from '@/state/dialogs';
import { validateAgainstAvailableEnvironments } from '@/lib/network';
export const useSelectedNetwork = (): {
switchNetwork: (network: DydxNetwork) => void;
@@ -20,8 +24,20 @@ export const useSelectedNetwork = (): {
const [, setLocalStorageNetwork] = useLocalStorage<DydxNetwork>({
key: LocalStorageKey.SelectedNetwork,
defaultValue: DEFAULT_APP_ENVIRONMENT,
validateFn: validateAgainstAvailableEnvironments,
});
useEffect(() => {
if (
import.meta.env.MODE === 'production' &&
ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId === 'dydx-testnet-3'
) {
dispatch(
openDialog({ type: DialogTypes.ExchangeOffline, dialogProps: { preventClose: true } })
);
}
}, [selectedNetwork]);
const switchNetwork = useCallback(
(network: DydxNetwork) => {
disconnect();
+5 -10
View File
@@ -2,15 +2,13 @@ import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { Squid } from '@0xsquid/sdk';
import { CLIENT_NETWORK_CONFIGS, isDydxV4Network } from '@/constants/networks';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { getSelectedNetwork } from '@/state/appSelectors';
export const NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
export enum SQUID_ERROR_TYPES {
NotFoundError = 'NotFoundError',
}
export const STATUS_ERROR_GRACE_PERIOD = 120_000;
const useSquidContext = () => {
const selectedNetwork = useSelector(getSelectedNetwork);
@@ -24,10 +22,7 @@ const useSquidContext = () => {
};
const squid = useMemo(
() =>
isDydxV4Network(selectedNetwork)
? new Squid({ baseUrl: CLIENT_NETWORK_CONFIGS[selectedNetwork]?.endpoints['0xsquid'] })
: undefined,
() => new Squid({ baseUrl: ENVIRONMENT_CONFIG_MAP[selectedNetwork]?.endpoints['0xsquid'] }),
[selectedNetwork]
);
@@ -41,7 +36,7 @@ const useSquidContext = () => {
};
type SquidContextType = ReturnType<typeof useSquidContext>;
const SquidContext = createContext<SquidContextType>(undefined);
const SquidContext = createContext<SquidContextType | undefined>(undefined);
SquidContext.displayName = '0xSquid';
export const SquidProvider = ({ ...props }) => (
+12 -86
View File
@@ -1,5 +1,5 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { shallowEqual, useSelector, useDispatch } from 'react-redux';
import type { Nullable } from '@dydxprotocol/v4-abacus';
import Long from 'long';
import type { IndexedTx } from '@cosmjs/stargate';
@@ -11,10 +11,10 @@ import {
SubaccountClient,
DYDX_DENOM,
USDC_DENOM,
GAS_PRICE_DYDX_DENOM,
} from '@dydxprotocol/v4-client-js';
import type {
AccountBalance,
HumanReadablePlaceOrderPayload,
ParsingError,
SubAccountHistoricalPNLs,
@@ -26,6 +26,7 @@ import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { DydxAddress } from '@/constants/wallets';
import { setSubaccount, setHistoricalPnl, removeUncommittedOrderClientId } from '@/state/account';
import { getBalances } from '@/state/accountSelectors';
import abacusStateManager from '@/lib/abacus';
import { track } from '@/lib/analytics';
@@ -34,7 +35,6 @@ import { log } from '@/lib/telemetry';
import { useAccounts } from './useAccounts';
import { useDydxClient } from './useDydxClient';
import { usePollUSDCBalance } from './usePollUSDCBalance';
type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
@@ -70,10 +70,8 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
const {
depositToSubaccount,
withdrawFromSubaccount,
simulateWithdrawFromSubaccount,
transferFromSubaccountToAddress,
transferNativeToken,
simulateTransferNativeToken,
sendSquidWithdrawFromSubaccount,
} = useMemo(
() => ({
@@ -95,31 +93,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
amount: number;
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount),
simulateWithdrawFromSubaccount: async ({
subaccountClient,
amount,
recipient,
}: {
subaccountClient: SubaccountClient;
amount: number;
recipient?: string;
}) => {
return await compositeClient?.simulate(
subaccountClient?.wallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.withdrawFromSubaccountMessage(
subaccountClient,
amount,
recipient
);
resolve([msg]);
}),
undefined
);
},
transferFromSubaccountToAddress: async ({
subaccountClient,
assetId = 0,
@@ -170,31 +143,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
Method.BroadcastTxCommit
),
simulateTransferNativeToken: async ({
subaccountClient,
amount,
recipient,
}: {
subaccountClient: SubaccountClient;
amount: number;
recipient: string;
}) =>
await compositeClient?.simulate(
subaccountClient?.wallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.validatorClient.post.composer.composeMsgSendToken(
subaccountClient.address,
recipient,
DYDX_DENOM,
Long.fromNumber(amount * QUANTUM_MULTIPLIER)
);
resolve([msg]);
}),
GAS_PRICE_DYDX_DENOM,
undefined
),
sendSquidWithdrawFromSubaccount: async ({
subaccountClient,
amount,
@@ -244,10 +192,10 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
// ------ Deposit/Withdraw Methods ------ //
const depositFunds = useCallback(
async (balance?: Coin) => {
async (balance: AccountBalance) => {
if (!localDydxWallet) return;
const amountAfterDust = MustBigNumber(balance?.amount)
const amountAfterDust = MustBigNumber(balance.amount)
.minus(AMOUNT_RESERVED_FOR_GAS_USDC) // keep 0.1 USDC in user's wallet for gas
.toString();
@@ -261,11 +209,14 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
[localDydxWallet, depositToSubaccount]
);
const balance = usePollUSDCBalance({ dydxAddress });
const balances = useSelector(getBalances, shallowEqual);
const usdcCoinBalance = balances?.[USDC_DENOM];
useEffect(() => {
depositFunds(balance);
}, [balance]);
if (usdcCoinBalance) {
depositFunds(usdcCoinBalance);
}
}, [usdcCoinBalance]);
const deposit = useCallback(
async (amount: Long) => {
@@ -304,30 +255,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
[subaccountClient, transferFromSubaccountToAddress, transferNativeToken]
);
const simulateTransfer = useCallback(
async (amount: number, recipient: string, coinDenom: string) => {
if (!subaccountClient) {
return;
}
return await (coinDenom === USDC_DENOM
? simulateWithdrawFromSubaccount
: simulateTransferNativeToken)({ subaccountClient, amount, recipient });
},
[subaccountClient, simulateWithdrawFromSubaccount, simulateTransferNativeToken]
);
const simulateWithdraw = useCallback(
async (amount: number) => {
if (!subaccountClient) {
return;
}
return await simulateWithdrawFromSubaccount({ subaccountClient, amount });
},
[subaccountClient, simulateWithdrawFromSubaccount]
);
const sendSquidWithdraw = useCallback(
async (amount: number, payload: string) => {
if (!subaccountClient) {
@@ -347,6 +274,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
await getFaucetFunds({ dydxAddress, subaccountNumber });
} catch (error) {
log('useSubaccount/getFaucetFunds', error);
throw error;
}
}, [dydxAddress, getFaucetFunds, subaccountNumber]);
@@ -437,9 +365,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
requestFaucetFunds,
// Transfer Methods
simulateTransfer,
transfer,
simulateWithdraw,
sendSquidWithdraw,
// Trading Methods
+8
View File
@@ -11,12 +11,16 @@ import { DepositDialog } from '@/views/dialogs/DepositDialog';
import { DisconnectDialog } from '@/views/dialogs/DisconnectDialog';
import { ExchangeOfflineDialog } from '@/views/dialogs/ExchangeOfflineDialog';
import { HelpDialog } from '@/views/dialogs/HelpDialog';
import { ExternalNavKeplrDialog } from '@/views/dialogs/ExternalNavKeplrDialog';
import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog';
import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog';
import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
import { RateLimitDialog } from '@/views/dialogs/RateLimitDialog';
import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog';
import { RestrictedGeoDialog } from '@/views/dialogs/RestrictedGeoDialog';
import { TradeDialog } from '@/views/dialogs/TradeDialog';
import { TransferDialog } from '@/views/dialogs/TransferDialog';
import { RestrictedWalletDialog } from '@/views/dialogs/RestrictedWalletDialog';
import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog';
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
@@ -47,11 +51,15 @@ export const DialogManager = () => {
[DialogTypes.ExchangeOffline]: <ExchangeOfflineDialog {...modalProps} />,
[DialogTypes.FillDetails]: <FillDetailsDialog {...modalProps} />,
[DialogTypes.Help]: <HelpDialog {...modalProps} />,
[DialogTypes.ExternalNavKeplr]: <ExternalNavKeplrDialog {...modalProps} />,
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
[DialogTypes.RateLimit]: <RateLimitDialog {...modalProps} />,
[DialogTypes.Receive]: <ReceiveDialog {...modalProps} />,
[DialogTypes.RestrictedGeo]: <RestrictedGeoDialog {...modalProps} />,
[DialogTypes.RestrictedWallet]: <RestrictedWalletDialog {...modalProps} />,
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
[DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />,
+5
View File
@@ -42,6 +42,11 @@ export const HeaderDesktop = () => {
label: stringGetter({ key: STRING_KEYS.TRADE }),
href: AppRoute.Trade,
},
{
value: 'Dv4TNT',
label: 'Dv4TNT',
href: AppRoute.Rewards,
},
{
value: 'PORTFOLIO',
label: stringGetter({ key: STRING_KEYS.PORTFOLIO }),
+109
View File
@@ -14,6 +14,8 @@ import {
OrderSide,
OrderTimeInForce,
OrderExecution,
DYDX_DENOM,
GAS_PRICE_DYDX_DENOM,
} from '@dydxprotocol/v4-client-js';
import {
@@ -24,10 +26,13 @@ import {
type TransactionTypes,
type HumanReadablePlaceOrderPayload,
type HumanReadableCancelOrderPayload,
type HumanReadableWithdrawPayload,
type HumanReadableTransferPayload,
} from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs';
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { RootStore } from '@/state/_store';
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
@@ -229,6 +234,76 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
}
}
async simulateWithdrawTransaction(params: HumanReadableWithdrawPayload): Promise<string> {
if (!this.compositeClient || !this.localWallet) {
throw new Error('Missing compositeClient or localWallet');
}
const { subaccountNumber, amount } = params ?? {};
const compositeClient = this.compositeClient;
const subaccountClient = new SubaccountClient(this.localWallet, subaccountNumber);
try {
const tx = await compositeClient.simulate(
this.localWallet,
() =>
new Promise((resolve) => {
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount);
resolve([msg]);
}),
);
const parsedTx = this.parseToPrimitives(tx);
return JSON.stringify(parsedTx);
} catch (error) {
log('DydxChainTransactions/simulateWithdrawTransaction', error);
return JSON.stringify({
error,
});
}
}
async simulateTransferNativeTokenTransaction(params: HumanReadableTransferPayload): Promise<string> {
if (!this.compositeClient || !this.localWallet) {
throw new Error('Missing compositeClient or localWallet');
}
const { subaccountNumber, amount, recipient } = params ?? {};
const compositeClient = this.compositeClient;
const subaccountClient = new SubaccountClient(this.localWallet, subaccountNumber);
try {
const tx = await compositeClient.simulate(
this.localWallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.validatorClient.post.composer.composeMsgSendToken(
subaccountClient.address,
recipient,
DYDX_DENOM,
Long.fromNumber(amount * QUANTUM_MULTIPLIER)
);
resolve([msg]);
}),
GAS_PRICE_DYDX_DENOM,
);
const parsedTx = this.parseToPrimitives(tx);
return JSON.stringify(parsedTx);
} catch (error) {
log('DydxChainTransactions/simulateTransferNativeTokenTransaction', error);
return JSON.stringify({
error,
});
}
}
async transaction(
type: TransactionTypes,
paramsInJson: Abacus.Nullable<string>,
@@ -248,6 +323,16 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
callback(result);
break;
}
case TransactionType.simulateWithdraw: {
const result = await this.simulateWithdrawTransaction(params);
callback(result);
break;
}
case TransactionType.simulateTransferNativeToken: {
const result = await this.simulateTransferNativeTokenTransaction(params);
callback(result);
break;
}
default: {
break;
}
@@ -310,6 +395,30 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
const parsedUserStats = this.parseToPrimitives(userStats);
callback(JSON.stringify(parsedUserStats));
break;
case QueryType.GetAccountBalances:
if (!this.localWallet?.address) throw new Error('Missing localWallet');
const accountBalances = await this.compositeClient?.validatorClient.get.getAccountBalances(
this.localWallet.address
);
const parsedAccountBalances = this.parseToPrimitives(accountBalances);
callback(JSON.stringify(parsedAccountBalances));
break;
case QueryType.RewardsParams:
const rewardsParams = await this.compositeClient?.validatorClient.get.getRewardsParams();
const parsedRewardsParams = this.parseToPrimitives(rewardsParams);
callback(JSON.stringify(parsedRewardsParams));
break;
case QueryType.GetMarketPrice:
const price = await this.compositeClient?.validatorClient.get.getPrice(params.marketId);
const parsedPrice = this.parseToPrimitives(price);
callback(JSON.stringify(parsedPrice));
break;
case QueryType.GetDelegations:
const delegations =
await this.compositeClient?.validatorClient.get.getDelegatorDelegations(params.address);
const parseDelegations = this.parseToPrimitives(delegations);
callback(JSON.stringify(parseDelegations));
break;
// Do not implement Transfers (yet)
case QueryType.Transfers:
default:
-5
View File
@@ -1,5 +1,4 @@
import type { AbacusFileSystemProtocol, FileLocation, Nullable } from '@/constants/abacus';
import { NETWORK_ENDPOINTS } from '@/constants/networks';
export const ENDPOINTS_PATH =
import.meta.env.MODE === 'production'
@@ -8,10 +7,6 @@ export const ENDPOINTS_PATH =
class AbacusFileSystem implements AbacusFileSystemProtocol {
readTextFile(location: FileLocation, path: string): Nullable<string> {
if (path === ENDPOINTS_PATH) {
return JSON.stringify(NETWORK_ENDPOINTS);
}
return null;
}
+5 -3
View File
@@ -22,10 +22,11 @@ import {
UIImplementations,
CoroutineTimer,
TransferType,
AbacusAppConfig,
} from '@/constants/abacus';
import { DEFAULT_MARKETID } from '@/constants/markets';
import { type DydxNetwork } from '@/constants/networks';
import { CURRENT_ABACUS_DEPLOYMENT, type DydxNetwork } from '@/constants/networks';
import type { RootStore } from '@/state/_store';
@@ -80,8 +81,9 @@ class AbacusStateManager {
);
this.stateManager = new AsyncAbacusStateManager(
import.meta.env.SHARED_RESOURCES_URI,
ENDPOINTS_PATH,
'',
CURRENT_ABACUS_DEPLOYMENT,
AbacusAppConfig.Companion.forWeb,
ioImplementations,
uiImplementations,
// @ts-ignore
+5
View File
@@ -47,6 +47,11 @@ class AbacusRest implements AbacusRestProtocol {
body,
};
if (!url) {
callback(null, 0);
return;
}
fetch(url, options)
.then(async (response) => {
const data = await response.text();
+32 -4
View File
@@ -1,6 +1,7 @@
import { kollections } from '@dydxprotocol/v4-abacus';
import type {
AccountBalance,
AbacusApiState,
AbacusNotification,
AbacusStateNotificationProtocol,
@@ -18,10 +19,13 @@ import { Changes } from '@/constants/abacus';
import type { RootStore } from '@/state/_store';
import {
setBalances,
setStakingBalances,
setFills,
setFundingPayments,
setHistoricalPnl,
setLatestOrder,
setRestrictionType,
setSubaccount,
setTransfers,
setWallet,
@@ -32,6 +36,7 @@ import { setAssets } from '@/state/assets';
import { setConfigs } from '@/state/configs';
import { setInputs } from '@/state/inputs';
import { setHistoricalFundings, setLiveTrades, setMarkets, setOrderbook } from '@/state/perpetuals';
import { isTruthy } from '../isTruthy';
class AbacusStateNotifier implements AbacusStateNotificationProtocol {
private store: RootStore | undefined;
@@ -72,6 +77,23 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
);
}
if (changes.has(Changes.accountBalances)) {
if (updatedState.account?.balances) {
const balances: Record<string, AccountBalance> = {};
for (const { k, v } of updatedState.account.balances.toArray()) {
balances[k] = v;
}
dispatch(setBalances(balances));
}
if (updatedState.account?.stakingBalances) {
const stakingBalances: Record<string, AccountBalance> = {};
for (const { k, v } of updatedState.account.stakingBalances.toArray()) {
stakingBalances[k] = v;
}
dispatch(setStakingBalances(stakingBalances));
}
}
if (changes.has(Changes.configs)) {
dispatch(setConfigs(updatedState.configs));
}
@@ -88,16 +110,22 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
dispatch(
setMarkets({
markets: Object.fromEntries(
(marketIds || updatedState.marketIds()?.toArray() || []).map((marketId: string) => {
const marketData = updatedState.market(marketId);
return [marketId, marketData];
})
(marketIds || updatedState.marketIds()?.toArray() || [])
.map((marketId: string) => {
const marketData = updatedState.market(marketId);
return [marketId, marketData];
})
.filter(isTruthy)
) as Record<string, PerpetualMarket>,
update: !!marketIds,
})
);
}
if (changes.has(Changes.restriction)) {
dispatch(setRestrictionType(updatedState.restriction));
}
subaccountNumbers?.forEach((subaccountId: number) => {
if (subaccountId !== null) {
if (changes.has(Changes.subaccount)) {
+4
View File
@@ -0,0 +1,4 @@
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
export const validateAgainstAvailableEnvironments = (value: DydxNetwork) =>
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
+36 -30
View File
@@ -10,7 +10,23 @@ import {
bscTestnet,
optimism,
optimismGoerli,
} from 'wagmi/chains';
base,
baseGoerli,
polygon,
polygonMumbai,
linea,
lineaTestnet,
mantle,
mantleTestnet,
moonbeam,
moonbaseAlpha,
filecoin,
filecoinHyperspace,
fantom,
fantomTestnet,
celo,
celoAlfajores,
} from 'viem/chains';
import { alchemyProvider } from 'wagmi/providers/alchemy';
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc';
@@ -36,10 +52,26 @@ export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
bscTestnet,
optimism,
optimismGoerli,
base,
baseGoerli,
polygon,
polygonMumbai,
linea,
lineaTestnet,
mantle,
mantleTestnet,
moonbeam,
moonbaseAlpha,
filecoin,
filecoinHyperspace,
fantom,
fantomTestnet,
celo,
celoAlfajores,
];
const { chains, publicClient, webSocketPublicClient } = configureChains(WAGMI_SUPPORTED_CHAINS, [
alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }),
// alchemyProvider({ apiKey: import.meta.env.VITE_ALCHEMY_API_KEY }),
jsonRpcProvider({
rpc: (chain) => ({ http: chain.rpcUrls.default.http[0] }),
}),
@@ -55,24 +87,15 @@ const injectedConnectorOptions = {
},
};
const walletconnect1ConnectorOptions = {
chains,
options: {
bridge: import.meta.env.VITE_WALLETCONNECT1_BRIDGE,
qrcode: true,
},
};
const walletconnect2ConnectorOptions: ConstructorParameters<typeof WalletConnectConnector>[0] = {
chains,
options: {
projectId: import.meta.env.VITE_WALLETCONNECT2_PROJECT_ID,
metadata: {
// TODO: update to local URLs/images
name: 'dYdX',
description: '',
url: 'https://trade.dydx.exchange',
icons: ['https://trade.dydx.exchange/cbw-image.png'],
url: import.meta.env.VITE_BASE_URL,
icons: [`${import.meta.env.VITE_BASE_URL}/cbw-image.png}`],
},
showQrModal: true,
qrModalOptions: {
@@ -111,7 +134,6 @@ const connectors = [
reloadOnDisconnect: false,
},
}),
new WalletConnectLegacyConnector(walletconnect1ConnectorOptions),
new WalletConnectConnector(walletconnect2ConnectorOptions),
new InjectedConnector(injectedConnectorOptions),
];
@@ -134,19 +156,6 @@ const createInjectedConnectorWithProvider = (provider: ExternalProvider) =>
provider as unknown as Awaited<ReturnType<InjectedConnector['getProvider']>>;
})(injectedConnectorOptions) as InjectedConnector;
// Create a custom wagmi WalletConnectLegacyConnector with a modal showing only wallet links matching the given name
const createWalletConnect1ConnectorWithName = (walletconnect1Name: string) =>
new WalletConnectLegacyConnector({
...walletconnect1ConnectorOptions,
options: {
...walletconnect1ConnectorOptions.options,
qrcodeModalOptions: {
desktopLinks: [walletconnect1Name],
mobileLinks: [walletconnect1Name],
},
},
});
const createWalletConnect2ConnectorWithId = (walletconnect2Id: string) =>
new WalletConnectConnector({
...walletconnect2ConnectorOptions,
@@ -182,9 +191,6 @@ export const resolveWagmiConnector = ({
return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider
? createInjectedConnectorWithProvider(walletConnection.provider)
: walletConnection.type === WalletConnectionType.WalletConnect1 &&
walletConfig.walletconnect1Name
? createWalletConnect1ConnectorWithName(walletConfig.walletconnect1Name)
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id)
: connectors.find(({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId);
-6
View File
@@ -45,12 +45,6 @@ export const getWalletConnection = ({
break;
}
case WalletConnectionType.WalletConnect1: {
return {
type: WalletConnectionType.WalletConnect1,
};
}
case WalletConnectionType.WalletConnect2: {
return {
type: WalletConnectionType.WalletConnect2,
@@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import { TooltipContextType } from '@visx/xychart';
import BigNumber from 'bignumber.js';
import type { Nullable } from '@/constants/abacus';
import { OnboardingState } from '@/constants/account';
@@ -25,7 +26,6 @@ import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton
import { isTruthy } from '@/lib/isTruthy';
import { MustBigNumber } from '@/lib/numbers';
export const usePortfolioValues = ({
equity,
visibleData,
@@ -140,7 +140,12 @@ export const AccountDetailsAndHistory = () => {
<Styled.AccountValue>
<Styled.WithLabel label={accountValueLabel}>
<Styled.AccountEquity>
<Output type={OutputType.Fiat} value={accountEquity} withBaseFont />
<Output
type={OutputType.Fiat}
value={accountEquity}
roundingMode={BigNumber.ROUND_FLOOR}
withBaseFont
/>
</Styled.AccountEquity>
<Styled.PnlDiff isPositive={MustBigNumber(pnlDiff).gte(0)}>
{pnlDiff && <TriangleIndicator value={MustBigNumber(pnlDiff)} />}
+8 -10
View File
@@ -29,10 +29,10 @@ export const DYDXBalancePanel = () => {
const { walletType } = useAccounts();
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { nativeTokenBalance } = useAccountBalance();
const { nativeTokenBalance, nativeStakingBalance } = useAccountBalance();
return (
<Styled.TransfersCard
<Panel
slotHeader={
<Styled.Header>
<Styled.Title>
@@ -97,7 +97,7 @@ export const DYDXBalancePanel = () => {
</Styled.Label>
),
value: <Output type={OutputType.Asset} value={undefined} />,
value: <Output type={OutputType.Asset} value={nativeStakingBalance} />,
},
]}
/>
@@ -106,21 +106,17 @@ export const DYDXBalancePanel = () => {
{
key: 'totalBalance',
label: 'Total balance',
value: <Output type={OutputType.Asset} value={nativeTokenBalance} tag="Dv4TNT" />,
value: <Output type={OutputType.Asset} value={nativeTokenBalance + nativeStakingBalance} tag="Dv4TNT" />,
},
]}
/>
</Styled.Content>
</Styled.TransfersCard>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.TransfersCard = styled(Panel)`
width: 21.25rem;
`;
Styled.Header = styled.div`
${layoutMixins.spacedRow}
padding: 1.25rem 1.5rem 0.5rem;
@@ -174,13 +170,15 @@ Styled.IconContainer = styled.div`
Styled.WalletAndStakedBalance = styled(Details)`
--details-item-backgroundColor: var(--color-layer-6);
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
> div {
gap: 1rem;
padding: 1rem;
width: 8.625rem;
border-radius: 0.75em;
background-color: var(--color-layer-5);
+322 -6
View File
@@ -1,17 +1,333 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch, useSelector } from 'react-redux';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction, ButtonSize, ButtonState, ButtonType } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { Button } from '@/components/Button';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { Output, OutputType } from '@/components/Output';
import { VerticalSeparator } from '@/components/Separator';
import { WithReceipt } from '@/components/WithReceipt';
import { openDialog } from '@/state/dialogs';
import { getSelectedNetwork } from '@/state/appSelectors';
import { DYDXBalancePanel } from './DYDXBalancePanel';
export const RewardsPage = () => (
<Styled.Page>
<DYDXBalancePanel />
</Styled.Page>
);
// TODO: replace placeholder URL with real URLs when avaialble
const GOVERNANCE_HELP_URL = 'https://help.dydx.exchange/';
const STAKING_HELP_URL = 'https://help.dydx.exchange/';
export const RewardsPage = () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { isTablet, isNotTablet } = useBreakpoints();
const selectedNetwork = useSelector(getSelectedNetwork);
// const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
// const { balance } = useAccountBalance({
// addressOrDenom: import.meta.env.VITE_V3_TOKEN_ADDRESS,
// assetSymbol: 'DYDX',
// chainId,
// isCosmosChain: false,
// });
// const tokenBalance = import.meta.env.VITE_V3_TOKEN_ADDRESS ? balance : 0;
return (
<Styled.Page>
{/* {isNotTablet ? (
<Styled.Migrate>
<Styled.TwoItemRow>
<div>
<Styled.MigrateTitle>
{stringGetter({ key: STRING_KEYS.MIGRATE })}
</Styled.MigrateTitle>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.MIGRATE_DESCRIPTION })}
<Link href={MIGRATE_HELP_URL}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
</Link>
</Styled.Description>
</div>
<Styled.MigrateAction>
<div>
<div>{stringGetter({ key: STRING_KEYS.AVAILABLE_TO_MIGRATE })}</div>
<div>
<Styled.Token type={OutputType.Asset} value={tokenBalance} />
</div>
</div>
<Button
action={tokenBalance ? ButtonAction.Primary : ButtonAction.Base}
// type={ButtonType.Link}
// href={BRIDGE_URL}
disabled
>
{tokenBalance
? `${stringGetter({ key: STRING_KEYS.MIGRATE_NOW })} →`
: stringGetter({ key: STRING_KEYS.NO_TOKENS_TO_MIGRATE })}
</Button>
</Styled.MigrateAction>
</Styled.TwoItemRow>
</Styled.Migrate>
) : (
<Styled.MobileMigrateCard
slotHeader={
<Styled.MobileMigrateHeader>
<h3>{stringGetter({ key: STRING_KEYS.MIGRATE })}</h3>
<VerticalSeparator />
<span>
{stringGetter({
key: STRING_KEYS.FROM_TO,
params: { FROM: <strong>Ethereum</strong>, TO: <strong>dYdX Chain</strong> },
})}
</span>
</Styled.MobileMigrateHeader>
}
>
<Styled.WithReceipt
slotReceipt={
<Styled.Details
items={[
{
key: 'available-to-migrate',
label: stringGetter({ key: STRING_KEYS.AVAILABLE_TO_MIGRATE }),
value: <Output type={OutputType.Asset} value={tokenBalance} />,
},
]}
/>
}
>
<Button
action={tokenBalance ? ButtonAction.Primary : ButtonAction.Base}
size={ButtonSize.Medium}
disabled
>
{tokenBalance
? `${stringGetter({ key: STRING_KEYS.MIGRATE_NOW })} →`
: stringGetter({ key: STRING_KEYS.NO_TOKENS_TO_MIGRATE })}
</Button>
</Styled.WithReceipt>
<Styled.LearnMore>
{stringGetter({ key: STRING_KEYS.WANT_TO_LEARN })}
<Link href={MIGRATE_HELP_URL}>{stringGetter({ key: STRING_KEYS.CLICK_HERE })}</Link>
</Styled.LearnMore>
</Styled.MobileMigrateCard>
)} */}
<Styled.PanelRow>
{isTablet && (
<Styled.BalancePanelContainer>
<DYDXBalancePanel />
</Styled.BalancePanelContainer>
)}
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Row>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={GOVERNANCE_HELP_URL} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
{/* TODO: vertically center based on Panel height */}
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Row>
</Styled.Panel>
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Row>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={STAKING_HELP_URL} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
{/* TODO: vertically center based on Panel height */}
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Row>
</Styled.Panel>
{isNotTablet && (
<Styled.BalancePanelContainer>
<DYDXBalancePanel />
</Styled.BalancePanelContainer>
)}
</Styled.PanelRow>
</Styled.Page>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Page = styled.div`
${layoutMixins.centered}
${layoutMixins.contentContainerPage}
gap: 1.5rem;
@media ${breakpoints.tablet} {
padding: 1rem;
}
`;
Styled.Panel = styled(Panel)`
padding: 0 1.5rem 1rem;
@media ${breakpoints.tablet} {
max-width: calc(100vw - 2rem);
}
`;
Styled.Row = styled.div`
${layoutMixins.spacedRow}
gap: 1rem;
align-items: center;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
a {
color: var(--color-text-1);
}
`;
Styled.Migrate = styled.section`
max-width: min(100vw, var(--content-max-width));
padding: 1.5rem;
background-color: var(--color-layer-3);
border-radius: 0.875rem;
`;
Styled.TwoItemRow = styled(Styled.Row)`
grid-template-columns: 1fr 1fr;
`;
Styled.MigrateAction = styled(Styled.TwoItemRow)`
padding: 1rem;
background-color: var(--color-layer-2);
border: solid var(--border-width) var(--color-border);
border-radius: 0.75rem;
`;
Styled.Token = styled(Output)`
font: var(--font-large-book);
`;
Styled.PanelRow = styled(Styled.Row)`
gap: 1.5rem;
max-width: min(100vw, var(--content-max-width));
align-items: flex-start;
> section {
cursor: pointer;
}
@media ${breakpoints.tablet} {
grid-auto-flow: row;
grid-template-columns: 1fr;
max-width: auto;
}
`;
Styled.BalancePanelContainer = styled.div`
width: 21.25rem;
@media ${breakpoints.tablet} {
width: auto;
}
`;
Styled.Title = styled.h3`
${layoutMixins.inlineRow}
padding: 1.25rem 1.5rem 0.5rem;
font: var(--font-medium-book);
color: var(--color-text-2);
`;
Styled.MigrateTitle = styled(Styled.Title)`
padding: 0 0 0.5rem;
`;
Styled.MobileMigrateCard = styled(Styled.Panel)`
${layoutMixins.flexColumn}
gap: 1rem;
align-items: center;
`;
Styled.MobileMigrateHeader = styled(Styled.Title)`
${layoutMixins.inlineRow}
gap: 1ch;
padding-bottom: 1rem;
font: var(--font-small-book);
color: var(--color-text-0);
h3 {
${layoutMixins.inlineRow}
font: var(--font-large-book);
color: var(--color-text-2);
svg {
font-size: 1.75rem;
}
}
span {
margin-top: 0.2rem;
b {
font-weight: var(--fontWeight-book);
color: var(--color-text-1);
}
}
`;
Styled.Details = styled(Details)`
padding: 0.5rem 1rem;
`;
Styled.WithReceipt = styled(WithReceipt)`
width: 100%;
`;
Styled.LearnMore = styled(Styled.Description)`
${layoutMixins.row}
gap: 1ch;
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
`;
+2 -2
View File
@@ -12,12 +12,12 @@ import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
export const MarketSelectorAndStats = ({ className }: { className?: string }) => {
const { symbol = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const currentMarketId = useSelector(getCurrentMarketId);
return (
<Styled.Container className={className}>
<MarketsDropdown currentMarketId={currentMarketId} symbol={symbol} />
<MarketsDropdown currentMarketId={currentMarketId} symbol={id} />
<VerticalSeparator />
-4
View File
@@ -3,7 +3,6 @@ import { configureStore } from '@reduxjs/toolkit';
import abacusStateManager from '@/lib/abacus';
import appMiddleware from './appMiddleware';
import localizationMiddleware from './localizationMiddleware';
import routerMiddleware from './routerMiddleware';
import { accountSlice } from './account';
import { appSlice } from './app';
@@ -13,7 +12,6 @@ import { dialogsSlice } from './dialogs';
import { inputsSlice } from './inputs';
import { layoutSlice } from './layout';
import { localizationSlice } from './localization';
import { navigationSlice } from './navigation';
import { perpetualsSlice } from './perpetuals';
export const commandMenuSlices = [layoutSlice, localizationSlice];
@@ -28,7 +26,6 @@ export const store = configureStore({
inputs: inputsSlice.reducer,
layout: layoutSlice.reducer,
localization: localizationSlice.reducer,
navigation: navigationSlice.reducer,
perpetuals: perpetualsSlice.reducer,
},
@@ -38,7 +35,6 @@ export const store = configureStore({
}),
appMiddleware,
localizationMiddleware,
routerMiddleware,
],
devTools: process.env.NODE_ENV !== 'production',
+49 -16
View File
@@ -1,6 +1,7 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type {
AccountBalance,
SubaccountFill,
Nullable,
Subaccount,
@@ -11,6 +12,7 @@ import type {
SubaccountTransfers,
HistoricalPnlPeriods,
SubAccountHistoricalPNLs,
UsageRestriction,
} from '@/constants/abacus';
import { OnboardingGuard, OnboardingState } from '@/constants/account';
@@ -20,32 +22,46 @@ import { WalletType } from '@/constants/wallets';
import { getLocalStorage } from '@/lib/localStorage';
export type AccountState = {
balances?: Record<string, AccountBalance>;
stakingBalances?: Record<string, AccountBalance>;
wallet?: Nullable<Wallet>;
walletType?: WalletType;
subaccount?: Nullable<Subaccount>;
fills?: SubaccountFills;
fundingPayments?: SubaccountFundingPayments;
transfers?: SubaccountTransfers;
historicalPnl?: SubAccountHistoricalPNLs;
onboardingGuards: Record<OnboardingGuard, boolean | undefined>;
onboardingState: OnboardingState;
clearedOrderIds?: string[];
hasUnseenFillUpdates: boolean;
hasUnseenOrderUpdates: boolean;
historicalPnl?: SubAccountHistoricalPNLs;
latestOrder?: Nullable<SubaccountOrder>;
onboardingGuards: Record<OnboardingGuard, boolean | undefined>;
onboardingState: OnboardingState;
subaccount?: Nullable<Subaccount>;
uncommittedOrderClientIds: number[];
wallet?: Nullable<Wallet>;
walletType?: WalletType;
historicalPnlPeriod?: HistoricalPnlPeriods;
uncommittedOrderClientIds: number[];
restriction?: Nullable<UsageRestriction>;
};
const initialState: AccountState = {
// Wallet
balances: undefined,
wallet: undefined,
walletType: getLocalStorage<WalletType>({
key: LocalStorageKey.OnboardingSelectedWalletType,
}),
// Subaccount
subaccount: undefined,
fills: undefined,
fundingPayments: undefined,
transfers: undefined,
clearedOrderIds: undefined,
hasUnseenFillUpdates: false,
hasUnseenOrderUpdates: false,
historicalPnl: undefined,
latestOrder: undefined,
// Onboarding
onboardingGuards: {
[OnboardingGuard.hasAcknowledgedTerms]: Boolean(
getLocalStorage<boolean>({
@@ -56,12 +72,17 @@ const initialState: AccountState = {
[OnboardingGuard.hasPreviousTransactions]: undefined,
},
onboardingState: OnboardingState.Disconnected,
subaccount: undefined,
// UI
clearedOrderIds: undefined,
hasUnseenFillUpdates: false,
hasUnseenOrderUpdates: false,
latestOrder: undefined,
uncommittedOrderClientIds: [],
wallet: undefined,
walletType: getLocalStorage<WalletType>({
key: LocalStorageKey.OnboardingSelectedWalletType,
}),
historicalPnlPeriod: undefined,
// Restriction
restriction: undefined,
};
export const accountSlice = createSlice({
@@ -121,6 +142,9 @@ export const accountSlice = createSlice({
state.historicalPnl = action.payload;
}
},
setRestrictionType: (state, action: PayloadAction<Nullable<UsageRestriction>>) => {
state.restriction = action.payload;
},
setSubaccount: (state, action: PayloadAction<Nullable<Subaccount>>) => {
const existingOrderIds = state.subaccount?.orders
? state.subaccount.orders.toArray().map((order) => order.id)
@@ -149,6 +173,12 @@ export const accountSlice = createSlice({
viewedOrders: (state) => {
state.hasUnseenOrderUpdates = false;
},
setBalances: (state, action: PayloadAction<Record<string, AccountBalance>>) => {
state.balances = action.payload;
},
setStakingBalances: (state, action: PayloadAction<Record<string, AccountBalance>>) => {
state.stakingBalances = action.payload;
},
addUncommittedOrderClientId: (state, action: PayloadAction<number>) => {
state.uncommittedOrderClientIds.push(action.payload);
},
@@ -169,10 +199,13 @@ export const {
setOnboardingGuard,
setOnboardingState,
setHistoricalPnl,
setRestrictionType,
setSubaccount,
setWallet,
viewedFills,
viewedOrders,
setBalances,
setStakingBalances,
addUncommittedOrderClientId,
removeUncommittedOrderClientId,
} = accountSlice.actions;
+3 -6
View File
@@ -2,7 +2,6 @@ import { createSelector } from 'reselect';
import { SubaccountPosition } from '@/constants/abacus';
import { OnboardingState, OnboardingSteps } from '@/constants/account';
import { isDydxV4Network } from '@/constants/networks';
import {
getExistingOpenPositions,
@@ -12,8 +11,6 @@ import {
getUncommittedOrderClientIds,
} from '@/state/accountSelectors';
import { getSelectedNetwork } from '@/state/appSelectors';
export const calculateOnboardingStep = createSelector(
[getOnboardingState, getOnboardingGuards],
(onboardingState: OnboardingState, onboardingGuards: ReturnType<typeof getOnboardingGuards>) => {
@@ -35,9 +32,9 @@ export const calculateOnboardingStep = createSelector(
* @description calculate whether an account is in a state where they can trade
*/
export const calculateCanAccountTrade = createSelector(
[getOnboardingState, getSelectedNetwork],
(onboardingState: OnboardingState, selectedNetwork: string) => {
return onboardingState === OnboardingState.AccountConnected && isDydxV4Network(selectedNetwork);
[getOnboardingState],
(onboardingState: OnboardingState) => {
return onboardingState === OnboardingState.AccountConnected;
}
);
+20
View File
@@ -332,3 +332,23 @@ export const getUserStats = (state: RootState) => ({
makerVolume30D: state.account?.wallet?.user?.makerVolume30D,
takerVolume30D: state.account?.wallet?.user?.takerVolume30D,
});
/**
* @returns user wallet balances
*/
export const getBalances = (state: RootState) => state.account?.balances;
/**
* @returns user wallet staking balances
* */
export const getStakingBalances = (state: RootState) => state.account?.stakingBalances;
/**
* @returns UsageRestriction of the current session
*/
export const getUsageRestriction = (state: RootState) => state.account.restriction;
/**
* @returns RestrictionType from the current session
*/
export const getRestrictionType = (state: RootState) => state.account.restriction?.restriction;
+3 -7
View File
@@ -2,14 +2,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AbacusApiState } from '@/constants/abacus';
import { LocalStorageKey } from '@/constants/localStorage';
import {
DEFAULT_APP_ENVIRONMENT,
isValidDydxNetwork,
type DydxNetwork,
} from '@/constants/networks';
import { DEFAULT_APP_ENVIRONMENT, type DydxNetwork } from '@/constants/networks';
import { getLocalStorage } from '@/lib/localStorage';
import { validateAgainstAvailableEnvironments } from '@/lib/network';
export interface AppState {
apiState: AbacusApiState | undefined;
@@ -23,7 +19,7 @@ const initialState: AppState = {
selectedNetwork: getLocalStorage({
key: LocalStorageKey.SelectedNetwork,
defaultValue: DEFAULT_APP_ENVIRONMENT,
validateFn: isValidDydxNetwork,
validateFn: validateAgainstAvailableEnvironments,
}),
};
+22 -25
View File
@@ -23,39 +23,36 @@ export const dialogsSlice = createSlice({
name: 'Dialogs',
initialState,
reducers: {
addDialogToQueue: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => {
const dialogQueue = state.dialogQueue;
dialogQueue.push(action.payload);
return {
...state,
dialogQueue,
};
openDialog: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => {
if (state.activeDialog) {
state.dialogQueue.push(action.payload);
} else {
state.activeDialog = action.payload;
}
},
closeDialog: (state: DialogsState) => {
state.activeDialog = state.dialogQueue.shift();
},
forceOpenDialog: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => {
if (state.activeDialog) {
state.dialogQueue.unshift(state.activeDialog);
}
state.activeDialog = action.payload;
},
closeDialogInTradeBox: (state: DialogsState) => {
state.activeTradeBoxDialog = undefined;
},
closeDialog: (state: DialogsState) => ({
...state,
activeDialog: state.dialogQueue.shift(),
}),
openDialog: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => ({
...state,
activeDialog: action.payload,
}),
closeDialogInTradeBox: (state: DialogsState) => ({
...state,
activeTradeBoxDialog: undefined,
}),
openDialogInTradeBox: (
state: DialogsState,
action: PayloadAction<DialogInfo<TradeBoxDialogTypes>>
) => ({
...state,
activeTradeBoxDialog: action.payload,
}),
) => {
state.activeTradeBoxDialog = action.payload;
},
},
});
export const {
addDialogToQueue,
forceOpenDialog,
closeDialog,
closeDialogInTradeBox,
openDialog,
-42
View File
@@ -1,42 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
export interface NavigationState {
tradeLocation: {
hash: string;
key: string;
pathname: string;
search: string;
state: any;
};
}
export const CLEAR_TRADE_LOCATION = {
hash: '',
key: '',
pathname: '',
search: '',
state: null,
};
const initialState: NavigationState = {
tradeLocation: {
hash: '',
key: '',
pathname: '',
search: '',
state: null,
},
};
export const navigationSlice = createSlice({
name: 'Navigation',
initialState,
reducers: {
setTradeLocation: (state, { payload }) => ({
...state,
tradeLocation: payload,
}),
},
});
export const { setTradeLocation } = navigationSlice.actions;
-33
View File
@@ -1,33 +0,0 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { LocalStorageKey } from '@/constants/localStorage';
import { closeDialogInTradeBox } from '@/state/dialogs';
import { setTradeLocation } from '@/state/navigation';
import abacusStateManager from '@/lib/abacus';
import { setLocalStorage } from '@/lib/localStorage';
import { getMarketIdFromLocation } from '@/lib/tradeData';
export default (store: any) => (next: any) => async (action: PayloadAction<any>) => {
next(action);
const { type, payload } = action;
switch (type) {
case setTradeLocation.type: {
const marketId = getMarketIdFromLocation(payload);
if (marketId) {
abacusStateManager.setMarket(marketId);
store.dispatch(closeDialogInTradeBox());
setLocalStorage({ key: LocalStorageKey.LastViewedMarket, value: marketId });
}
break;
}
default: {
break;
}
}
};
+6 -8
View File
@@ -24,7 +24,7 @@ export const MarketDetails: React.FC = () => {
const stringGetter = useStringGetter();
const { isTablet } = useBreakpoints();
const { configs, market } = useSelector(getCurrentMarketData, shallowEqual) || {};
const { symbol, name, resources } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const { id, name, resources } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
if (!configs) return null;
@@ -75,7 +75,7 @@ export const MarketDetails: React.FC = () => {
useGrouping
value={stepSize}
type={OutputType.Asset}
tag={symbol}
tag={id}
fractionDigits={stepSizeDecimals}
/>
),
@@ -88,7 +88,7 @@ export const MarketDetails: React.FC = () => {
useGrouping
value={minOrderSize}
type={OutputType.Asset}
tag={symbol}
tag={id}
fractionDigits={stepSizeDecimals}
/>
),
@@ -132,18 +132,16 @@ export const MarketDetails: React.FC = () => {
<Styled.Header>
<Styled.WrapRow>
<Styled.MarketTitle>
<AssetIcon symbol={symbol} />
<AssetIcon symbol={id} />
{name}
</Styled.MarketTitle>
{isTablet && <Styled.MarketLinks />}
</Styled.WrapRow>
<Styled.MarketDescription>
{primaryDescriptionKey && (
<p>{stringGetter({ key: STRING_KEYS[primaryDescriptionKey as StringKey] })}</p>
)}
{primaryDescriptionKey && <p>{stringGetter({ key: `APP.${primaryDescriptionKey}` })}</p>}
{secondaryDescriptionKey && (
<p>{stringGetter({ key: STRING_KEYS[secondaryDescriptionKey as StringKey] })}</p>
<p>{stringGetter({ key: `APP.${secondaryDescriptionKey}` })}</p>
)}
</Styled.MarketDescription>
+2 -2
View File
@@ -45,7 +45,7 @@ const defaultMarketStatistics = Object.values(MarketStats);
export const MarketStatsDetails = ({ showMidMarketPrice = true }: ElementProps) => {
const stringGetter = useStringGetter();
const { isTablet } = useBreakpoints();
const { symbol = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { tickSizeDecimals } = useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
const midMarketPrice = useSelector(getCurrentMarketMidMarketPrice);
const lastMidMarketPrice = useRef(midMarketPrice);
@@ -116,7 +116,7 @@ export const MarketStatsDetails = ({ showMidMarketPrice = true }: ElementProps)
<Styled.Output
type={OutputType.Number}
value={value}
tag={symbol}
tag={id}
fractionDigits={LARGE_TOKEN_DECIMALS}
/>
);
+4 -4
View File
@@ -60,12 +60,12 @@ const MarketsDropdownContent = ({ onRowAction }: { onRowAction?: (market: string
columnKey: 'market',
getCellValue: (row) => row.market,
label: stringGetter({ key: STRING_KEYS.MARKET }),
renderCell: ({ asset }) => (
renderCell: ({ assetId, id }) => (
<Styled.MarketName isFavorited={false}>
{/* TRCL-1693 <Icon iconName={IconName.Star} /> */}
<AssetIcon symbol={asset.symbol} />
<h2>{asset.name}</h2>
<Tag>{asset.symbol}</Tag>
<AssetIcon symbol={assetId} />
<h2>{id}</h2>
<Tag>{assetId}</Tag>
</Styled.MarketName>
),
},
+4 -4
View File
@@ -68,7 +68,7 @@ export const PositionInfo = ({ showNarrowVariation }: { showNarrowVariation?: bo
const isLoading = useSelector(calculateIsAccountLoading);
const { stepSizeDecimals, tickSizeDecimals } = currentMarketConfigs || {};
const { symbol } = currentMarketAssetData || {};
const { id } = currentMarketAssetData || {};
const { type: dialogType } = activeDialog || {};
const { type: tradeBoxDialogType } = activeTradeBoxDialog || {};
@@ -188,7 +188,7 @@ export const PositionInfo = ({ showNarrowVariation }: { showNarrowVariation?: bo
label: STRING_KEYS.LIQUIDATION_PRICE,
tooltip: 'liquidation-price',
tooltipParams: {
SYMBOL: symbol || '',
SYMBOL: id || '',
},
fractionDigits: tickSizeDecimals,
hasInvalidNewValue: Boolean(newLeverageIsInvalid),
@@ -326,7 +326,7 @@ export const PositionInfo = ({ showNarrowVariation }: { showNarrowVariation?: bo
notionalTotal={notionalTotal?.current}
postOrderSize={size?.postOrder}
stepSizeDecimals={stepSizeDecimals}
symbol={symbol || undefined}
symbol={id || undefined}
tickSizeDecimals={tickSizeDecimals}
showNarrowVariation={showNarrowVariation}
isLoading={isLoading}
@@ -370,7 +370,7 @@ export const PositionInfo = ({ showNarrowVariation }: { showNarrowVariation?: bo
notionalTotal={notionalTotal?.current}
postOrderSize={size?.postOrder}
stepSizeDecimals={stepSizeDecimals}
symbol={symbol || undefined}
symbol={id || undefined}
tickSizeDecimals={tickSizeDecimals}
isLoading={isLoading}
/>
+10 -1
View File
@@ -53,6 +53,15 @@ export const TransferStatusToast = ({
if (!status) return <LoadingDots size={3} />;
const statusString =
type === 'deposit'
? status?.squidTransactionStatus === 'success'
? STRING_KEYS.DEPOSIT_COMPLETE
: STRING_KEYS.DEPOSIT_STATUS
: status?.squidTransactionStatus === 'success'
? STRING_KEYS.WITHDRAW_COMPLETE
: STRING_KEYS.WITHDRAW_STATUS;
return (
<Styled.Root open={open} onOpenChange={setOpen}>
<WithReceipt
@@ -67,7 +76,7 @@ export const TransferStatusToast = ({
<Styled.BridgingStatus>
<Styled.Status>
{stringGetter({
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_STATUS : STRING_KEYS.WITHDRAW_STATUS,
key: statusString,
params: {
AMOUNT_USD: <Styled.InlineOutput type={OutputType.Fiat} value={toAmount} />,
ESTIMATED_DURATION: (
+1 -1
View File
@@ -51,7 +51,7 @@ export const TransferStatusSteps = ({ status }: ElementProps) => {
label: stringGetter({
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_TO_CHAIN : STRING_KEYS.WITHDRAW_TO_CHAIN,
params: {
CHAIN: status?.toChain?.chainData?.chainName,
CHAIN: type === 'deposit' ? 'dYdX' : status?.toChain?.chainData?.chainName,
},
}),
step: TransferStatusStep.ToChain,
+4 -4
View File
@@ -98,7 +98,7 @@ export const DepthChart = ({
// Chart data
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const { symbol = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
@@ -393,7 +393,7 @@ export const DepthChart = ({
? chartPointAtPointer.size
: tooltipData!.nearestDatum?.datum.depth
}
tag={symbol}
tag={id}
accentColor={
{
[DepthChartSeries.Asks]: 'var(--color-negative)',
@@ -476,7 +476,7 @@ export const DepthChart = ({
type={OutputType.Asset}
value={chartPointAtPointer.size}
fractionDigits={stepSizeDecimals}
tag={symbol}
tag={id}
useGrouping={false}
/>
),
@@ -543,7 +543,7 @@ export const DepthChart = ({
type={OutputType.Asset}
value={nearestDatum?.datum.depth}
fractionDigits={stepSizeDecimals}
tag={symbol}
tag={id}
useGrouping={false}
/>
),
+5 -1
View File
@@ -25,7 +25,11 @@ import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
// import { TooltipContent } from '@/components/visx/TooltipContent';
import { getSubaccount, getSubaccountHistoricalPnl, getSubaccountId } from '@/state/accountSelectors';
import {
getSubaccount,
getSubaccountHistoricalPnl,
getSubaccountId,
} from '@/state/accountSelectors';
import abacusStateManager from '@/lib/abacus';
import { formatRelativeTime } from '@/lib/dateTime';
+2 -2
View File
@@ -28,7 +28,7 @@ type ElementProps = {
};
export const ClosePositionDialog = ({ setIsOpen }: ElementProps) => {
const { symbol } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { isTablet } = useBreakpoints();
const stringGetter = useStringGetter();
@@ -46,7 +46,7 @@ export const ClosePositionDialog = ({ setIsOpen }: ElementProps) => {
} = {
[MobilePlaceOrderSteps.EditOrder]: {
title: <CloseOrderHeader />,
slotIcon: <AssetIcon symbol={symbol} />,
slotIcon: <AssetIcon symbol={id} />,
},
[MobilePlaceOrderSteps.PreviewOrder]: {
title: (
@@ -19,7 +19,7 @@ import { MustBigNumber } from '@/lib/numbers';
type ElementProps = {
fillId: string;
setIsOpen?: (open: boolean) => void;
setIsOpen: (open: boolean) => void;
};
export const FillDetailsDialog = ({ fillId, setIsOpen }: ElementProps) => {
@@ -94,7 +94,7 @@ export const FillDetailsDialog = ({ fillId, setIsOpen }: ElementProps) => {
return (
<DetailsDialog
slotIcon={<Styled.AssetIcon symbol={asset.symbol} />}
slotIcon={<Styled.AssetIcon symbol={asset.id} />}
title={resources.typeStringKey && stringGetter({ key: resources.typeStringKey })}
items={detailItems}
setIsOpen={setIsOpen}
@@ -194,7 +194,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
return (
<DetailsDialog
slotIcon={<Styled.AssetIcon symbol={asset.symbol} />}
slotIcon={<Styled.AssetIcon symbol={asset.id} />}
title={!resources.typeStringKey ? '' : stringGetter({ key: resources.typeStringKey })}
slotFooter={
isAccountViewOnly ? null : isOrderStatusClearable(status) ? (
+2 -2
View File
@@ -27,9 +27,9 @@ export const DisconnectDialog = ({ setIsOpen }: ElementProps) => {
};
return (
<Dialog isOpen setIsOpen={setIsOpen} title="Disconnect">
<Dialog isOpen setIsOpen={setIsOpen} title={stringGetter({ key: STRING_KEYS.DISCONNECT })}>
<Styled.Content>
<p>Are you sure you want to disconnect your account?</p>
<p>{stringGetter({ key: STRING_KEYS.DISCONNECT_CONFIRMATION })}</p>
<Styled.ButtonRow>
<Close asChild>
<Button action={ButtonAction.Destroy} onClick={disconnect}>
+22 -2
View File
@@ -5,6 +5,9 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { AbacusApiStatus } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { UNICODE } from '@/constants/unicode';
import { useApiState, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -29,8 +32,16 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
const selectedNetwork = useSelector(getSelectedNetwork);
const activeDialog = useSelector(getActiveDialog, shallowEqual);
const showOfflineMessage =
import.meta.env.MODE === 'production' &&
ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId === 'dydx-testnet-3';
useEffect(() => {
if (activeDialog?.type === DialogTypes.ExchangeOffline && status === AbacusApiStatus.NORMAL) {
if (
activeDialog?.type === DialogTypes.ExchangeOffline &&
status === AbacusApiStatus.NORMAL &&
!showOfflineMessage
) {
dispatch(closeDialog());
}
}, [status, selectedNetwork]);
@@ -43,7 +54,16 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
title={stringGetter({ key: STRING_KEYS.UNAVAILABLE })}
>
<Styled.Content>
<p>{statusErrorMessage}</p>
{showOfflineMessage ? (
<>
<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 />}
</Styled.Content>
</Dialog>
@@ -0,0 +1,107 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { Button } from '@/components/Button';
import { Dialog, DialogPlacement } from '@/components/Dialog';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = {
setIsOpen: (open: boolean) => void;
};
// TODO: replace placeholder URL with real URLs when avaialble
const KEPLR_DASHBOARD_URL = 'https://testnet.keplr.app/';
const HELP_URL = 'https://help.dydx.exchange/en/articles/2921366-how-do-i-create-an-account-or-sign-up';
export const ExternalNavKeplrDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const { isTablet } = useBreakpoints();
return (
<Dialog
isOpen
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.HAVE_YOU_EXPORTED })}
placement={isTablet ? DialogPlacement.FullScreen : DialogPlacement.Default}
>
<Styled.Content>
<Styled.Button type={ButtonType.Link} size={ButtonSize.XLarge} href={KEPLR_DASHBOARD_URL}>
<span>
{stringGetter({
key: STRING_KEYS.NAVIGATE_TO_KEPLR,
params: {
STRONG_YES: <strong>{stringGetter({ key: STRING_KEYS.YES })}</strong>,
},
})}
</span>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.XSmall}
/>
</Styled.Button>
<Styled.Button type={ButtonType.Link} size={ButtonSize.XLarge} href={HELP_URL}>
<span>
{stringGetter({
key: STRING_KEYS.LEARN_TO_EXPORT,
params: {
STRONG_NO: <strong>{stringGetter({ key: STRING_KEYS.NO })}</strong>,
},
})}
</span>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.XSmall}
/>
</Styled.Button>
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.TextToggle = styled.div`
${layoutMixins.stickyFooter}
color: var(--color-accent);
cursor: pointer;
margin-top: auto;
&:hover {
text-decoration: underline;
}
`;
Styled.Content = styled.div`
${layoutMixins.stickyArea0}
--stickyArea0-bottomHeight: 2rem;
--stickyArea0-bottomGap: 1rem;
--stickyArea0-totalInsetBottom: 0.5rem;
${layoutMixins.flexColumn}
gap: 1rem;
`;
Styled.Button = styled(Button)`
--button-font: var(--font-base-book);
--button-padding: 0 1.5rem;
gap: 0;
justify-content: space-between;
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
`;
@@ -19,7 +19,7 @@ import { useDisplayedWallets } from '@/hooks/useDisplayedWallets';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
const aboutWalletsLink = `https://www.dydxacademy.info/educational-video-series/onboarding-to-defi-with-dydx`;
const aboutWalletsLink = `https://www.dydx.academy/video/defi-wallet`;
export const ChooseWallet = () => {
const stringGetter = useStringGetter();
@@ -34,7 +34,14 @@ export const ChooseWallet = () => {
<Styled.AlertMessage type={AlertType.Error}>
{
<h4>
Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.
{stringGetter({
key: STRING_KEYS.COULD_NOT_CONNECT,
params: {
WALLET: stringGetter({
key: wallets[selectedWalletType].stringKey,
}),
},
})}
</h4>
}
{selectedWalletError}
@@ -9,8 +9,8 @@ import { AlertType } from '@/constants/alerts';
import { AnalyticsEvent } from '@/constants/analytics';
import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { DydxAddress, SIGN_TYPED_DATA } from '@/constants/wallets';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { DydxAddress, getSignTypedData } from '@/constants/wallets';
import { useAccounts, useBreakpoints, useDydxClient, useStringGetter } from '@/hooks';
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
@@ -55,7 +55,7 @@ export const GenerateKeys = ({
// 1. Switch network
const selectedNetwork = useSelector(getSelectedNetwork);
const chainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId);
const chainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
const { isMatchingNetwork, matchNetwork, isSwitchingNetwork } = useMatchingEvmNetwork({
chainId,
@@ -85,10 +85,11 @@ export const GenerateKeys = ({
EvmDerivedAccountStatus.Derived,
].includes(status);
const signTypedData = getSignTypedData(selectedNetwork);
const { signTypedDataAsync } = useSignTypedData({
...SIGN_TYPED_DATA,
...signTypedData,
domain: {
...SIGN_TYPED_DATA.domain,
...signTypedData.domain,
chainId,
},
});
@@ -165,8 +166,8 @@ export const GenerateKeys = ({
{[
{
status: EvmDerivedAccountStatus.Deriving,
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
title: stringGetter({ key: STRING_KEYS.GENERATE_DYDX_WALLET }),
description: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_OWNERSHIP }),
},
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
status: EvmDerivedAccountStatus.EnsuringDeterminism,
+42
View File
@@ -0,0 +1,42 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
preventClose?: boolean;
setIsOpen?: (open: boolean) => void;
};
export const RateLimitDialog = ({ preventClose, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
return (
<Dialog
isOpen
preventClose={preventClose}
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.RATE_LIMIT_REACHED_ERROR_TITLE })}
slotIcon={<Styled.Icon iconName={IconName.Warning} />}
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.RATE_LIMIT_REACHED_ERROR_MESSAGE })}
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Icon = styled(Icon)`
color: var(--color-warning);
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`;
+42
View File
@@ -0,0 +1,42 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
preventClose?: boolean;
setIsOpen?: (open: boolean) => void;
};
export const RestrictedGeoDialog = ({ preventClose, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
return (
<Dialog
isOpen
preventClose={preventClose}
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_TITLE })}
slotIcon={<Styled.Icon iconName={IconName.Warning} />}
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Icon = styled(Icon)`
color: var(--color-warning);
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`;
@@ -0,0 +1,42 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
preventClose?: boolean;
setIsOpen?: (open: boolean) => void;
};
export const RestrictedWalletDialog = ({ preventClose, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
return (
<Dialog
isOpen
preventClose={preventClose}
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.WALLET_RESTRICTED_ERROR_TITLE })}
slotIcon={<Styled.Icon iconName={IconName.Warning} />}
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Icon = styled(Icon)`
color: var(--color-warning);
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`;
+4 -4
View File
@@ -3,7 +3,7 @@ import { shallowEqual, useSelector } from 'react-redux';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { TradeInputField } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { STRING_KEYS, StringKey } from '@/constants/localization';
import { TradeTypes, TRADE_TYPE_STRINGS, MobilePlaceOrderSteps } from '@/constants/trade';
import { useBreakpoints, useStringGetter } from '@/hooks';
@@ -31,7 +31,7 @@ type ElementProps = {
export const TradeDialog = ({ isOpen, setIsOpen, slotTrigger }: ElementProps) => {
const { isMobile } = useBreakpoints();
const stringGetter = useStringGetter();
const { symbol } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const currentTradeData = useSelector(getInputTradeData, shallowEqual);
const { type } = currentTradeData || {};
const selectedTradeType = getSelectedTradeType(type);
@@ -40,9 +40,9 @@ export const TradeDialog = ({ isOpen, setIsOpen, slotTrigger }: ElementProps) =>
const allTradeTypeItems = typeOptions?.toArray()?.map(({ type, stringKey }) => ({
value: type,
label: stringGetter({
key: stringKey,
key: stringKey as StringKey,
}),
slotBefore: <AssetIcon symbol={symbol} />,
slotBefore: <AssetIcon symbol={id} />,
}));
const [currentStep, setCurrentStep] = useState<MobilePlaceOrderSteps>(
@@ -14,7 +14,7 @@ import { NumberSign } from '@/constants/numbers';
import type { EvmAddress } from '@/constants/wallets';
import { useAccounts, useDebounce, useStringGetter } from '@/hooks';
import { useAccountBalance } from '@/hooks/useAccountBalance';
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { NATIVE_TOKEN_ADDRESS, useSquid } from '@/hooks/useSquid';
import { useWalletConnection } from '@/hooks/useWalletConnection';
@@ -86,7 +86,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
// Async Data
const { balance, queryStatus, isQueryFetching } = useAccountBalance({
addressOrDenom: sourceToken?.address || undefined,
addressOrDenom: sourceToken?.address || CHAIN_DEFAULT_TOKEN_ADDRESS,
assetSymbol: sourceToken?.symbol || undefined,
chainId: chainId,
decimals: sourceToken?.decimals || undefined,
@@ -217,7 +217,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
!requestPayload.data ||
!requestPayload.value ||
!requestPayload.gasLimit ||
!requestPayload.gasPrice ||
!requestPayload.routeType
) {
throw new Error('Missing request payload');
@@ -168,6 +168,19 @@ export const DepositButtonAndReceipt = ({
/>
),
},
{
key: 'estimatedRouteDuration',
label: <span>{stringGetter({ key: STRING_KEYS.ESTIMATED_TIME })}</span>,
value: typeof summary?.estimatedRouteDuration === 'number' && (
<Output
type={OutputType.Text}
value={stringGetter({
key: STRING_KEYS.X_MINUTES_LOWERCASED,
params: { X: Math.round(summary?.estimatedRouteDuration / 60) },
})}
/>
),
},
];
const isFormValid = !isDisabled && !isEditingSlippage;
@@ -4,7 +4,7 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { ButtonAction, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useAccounts, useStringGetter, useSubaccount } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -65,7 +65,7 @@ export const TestnetDepositForm = ({ onDeposit, onError }: DepositFormProps) =>
key: STRING_KEYS.CREDITED_WITH,
params: {
AMOUNT_USD:
CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId === 'dydxprotocol-testnet'
ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId === 'dydxprotocol-testnet'
? 1000
: 100,
},
@@ -4,14 +4,22 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import type { NumberFormatValues } from 'react-number-format';
import { shallowEqual, useSelector } from 'react-redux';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
import { isAddress } from 'viem';
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
import { AlertType } from '@/constants/alerts';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { NumberSign } from '@/constants/numbers';
import { useDebounce, useStringGetter, useSubaccount } from '@/hooks';
import {
useAccounts,
useDebounce,
useDydxClient,
useRestrictions,
useStringGetter,
useSubaccount,
} from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -26,6 +34,7 @@ import { Link } from '@/components/Link';
import { OutputType } from '@/components/Output';
import { Tag } from '@/components/Tag';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
import { Icon, IconName } from '@/components/Icon';
import { ChainSelectMenu } from '@/views/forms/AccountManagementForms/ChainSelectMenu';
@@ -37,13 +46,14 @@ import { MustBigNumber } from '@/lib/numbers';
import { TokenSelectMenu } from './TokenSelectMenu';
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
import { join } from 'path';
export const WithdrawForm = () => {
const stringGetter = useStringGetter();
const [error, setError] = useState<Error | null>(null);
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const { simulateWithdraw, sendSquidWithdraw } = useSubaccount();
const { sendSquidWithdraw } = useSubaccount();
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
// User input
@@ -59,6 +69,8 @@ export const WithdrawForm = () => {
resources,
} = useSelector(getTransferInputs, shallowEqual) || {};
const isValidAddress = toAddress && isAddress(toAddress);
const toToken = useMemo(
() => (token ? resources?.tokenResources?.get(token) : undefined),
[token, resources]
@@ -67,9 +79,11 @@ export const WithdrawForm = () => {
const { addTransferNotification } = useLocalNotifications();
// Async Data
const debouncedAmountBN = MustBigNumber(debouncedAmount);
const withdrawAmountBN = MustBigNumber(withdrawAmount);
const freeCollateralBN = MustBigNumber(freeCollateral?.current);
const debouncedAmountBN = useMemo(() => MustBigNumber(debouncedAmount), [debouncedAmount]);
const freeCollateralBN = useMemo(
() => MustBigNumber(freeCollateral?.current),
[freeCollateral?.current]
);
useEffect(() => {
abacusStateManager.setTransferValue({
@@ -97,60 +111,89 @@ export const WithdrawForm = () => {
field: TransferInputField.usdcSize,
});
} else {
const stdFee = await simulateWithdraw(parseFloat(debouncedAmount));
const amount =
parseFloat(debouncedAmount) -
parseFloat(stdFee?.amount[0]?.amount || '0') / QUANTUM_MULTIPLIER;
abacusStateManager.setTransferValue({
value: amount.toString(),
value: debouncedAmount,
field: TransferInputField.usdcSize,
});
setError(null);
setError(undefined);
}
} catch (error) {
setError(error);
setError(error.message);
} finally {
setIsLoading(false);
}
};
setTransferValue();
}, [debouncedAmountBN.toNumber()]);
}, [debouncedAmountBN]);
const { screenAddresses } = useDydxClient();
const { dydxAddress } = useAccounts();
const onSubmit = useCallback(
async (e: FormEvent) => {
try {
e.preventDefault();
if (!requestPayload?.data || !debouncedAmountBN.toNumber()) {
if (!requestPayload?.data || !debouncedAmountBN.toNumber() || !toAddress || !dydxAddress) {
throw new Error('Invalid request payload');
}
setIsLoading(true);
const txHash = await sendSquidWithdraw(debouncedAmountBN.toNumber(), requestPayload?.data);
setError(undefined);
if (txHash?.hash) {
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
const screenResults = await screenAddresses({
addresses: [toAddress, dydxAddress],
});
addTransferNotification({
txHash: hash,
fromChainId: TESTNET_CHAIN_ID,
toChainId: chainIdStr || undefined,
toAmount: debouncedAmountBN.toNumber(),
triggeredAt: Date.now(),
});
abacusStateManager.clearTransferInputValues();
setWithdrawAmount('');
if (screenResults?.[dydxAddress]) {
setError(
stringGetter({
key: STRING_KEYS.WALLET_RESTRICTED_WITHDRAWAL_TRANSFER_ORIGINATION_ERROR_MESSAGE,
})
);
} else if (screenResults?.[toAddress]) {
setError(
stringGetter({
key: STRING_KEYS.WALLET_RESTRICTED_WITHDRAWAL_TRANSFER_DESTINATION_ERROR_MESSAGE,
})
);
} else {
const txHash = await sendSquidWithdraw(debouncedAmountBN.toNumber(), requestPayload.data);
if (txHash?.hash) {
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
addTransferNotification({
txHash: hash,
fromChainId: TESTNET_CHAIN_ID,
toChainId: chainIdStr || undefined,
toAmount: debouncedAmountBN.toNumber(),
triggeredAt: Date.now(),
});
abacusStateManager.clearTransferInputValues();
setWithdrawAmount('');
}
}
} catch (error) {
setError(error);
if (error?.code === 429) {
setError(stringGetter({ key: STRING_KEYS.RATE_LIMIT_REACHED_ERROR_MESSAGE }));
} else {
setError(
error.message
? stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: {
ERROR_MESSAGE:
error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
},
})
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG })
);
}
} finally {
setIsLoading(false);
}
},
[requestPayload, debouncedAmountBN, chainIdStr]
[requestPayload, debouncedAmountBN, chainIdStr, toAddress, screenAddresses, stringGetter]
);
const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => {
@@ -181,11 +224,10 @@ export const WithdrawForm = () => {
const onClickMax = useCallback(() => {
setWithdrawAmount(freeCollateralBN.toString());
}, [freeCollateral?.current, setWithdrawAmount]);
}, [freeCollateralBN, setWithdrawAmount]);
const onSelectChain = useCallback((chain: string) => {
if (chain) {
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.chain,
value: chain,
@@ -196,7 +238,6 @@ export const WithdrawForm = () => {
const onSelectToken = useCallback((token: TransferInputTokenResource) => {
if (token) {
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.token,
value: token.address,
@@ -219,7 +260,7 @@ export const WithdrawForm = () => {
value={freeCollateral?.current}
newValue={freeCollateral?.postOrder}
sign={NumberSign.Negative}
hasInvalidNewValue={withdrawAmountBN.minus(freeCollateralBN).isNegative()}
hasInvalidNewValue={MustBigNumber(withdrawAmount).minus(freeCollateralBN).isNegative()}
withDiff={
Boolean(withdrawAmount) && !debouncedAmountBN.isNaN() && !debouncedAmountBN.isZero()
}
@@ -228,18 +269,23 @@ export const WithdrawForm = () => {
},
];
const { sanctionedAddresses } = useRestrictions();
const errorMessage = useMemo(() => {
if (error) {
return error?.message
? stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: { ERROR_MESSAGE: error.message },
})
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
return stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: { ERROR_MESSAGE: error },
});
}
if (!toAddress) return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ADDRESS });
if (sanctionedAddresses.has(toAddress))
return stringGetter({
key: STRING_KEYS.TRANSFER_INVALID_DYDX_ADDRESS,
});
if (debouncedAmountBN) {
if (!chainIdStr) {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN });
@@ -253,7 +299,15 @@ export const WithdrawForm = () => {
}
return undefined;
}, [error, freeCollateralBN, chainIdStr, debouncedAmountBN, toToken]);
}, [
error,
freeCollateralBN,
chainIdStr,
debouncedAmountBN,
toToken,
toAddress,
sanctionedAddresses,
]);
const isDisabled =
!!errorMessage ||
@@ -261,7 +315,8 @@ export const WithdrawForm = () => {
!chainIdStr ||
!toAddress ||
debouncedAmountBN.isNaN() ||
debouncedAmountBN.isZero();
debouncedAmountBN.isZero() ||
isLoading;
return (
<Styled.Form onSubmit={onSubmit}>
@@ -271,7 +326,12 @@ export const WithdrawForm = () => {
placeholder={stringGetter({ key: STRING_KEYS.ADDRESS })}
onChange={onChangeAddress}
value={toAddress || ''}
label={stringGetter({ key: STRING_KEYS.DESTINATION })}
label={
<span>
{stringGetter({ key: STRING_KEYS.DESTINATION })}{' '}
{isValidAddress ? <Styled.CheckIcon iconName={IconName.Check} /> : null}
</span>
}
/>
<ChainSelectMenu
label={stringGetter({ key: STRING_KEYS.NETWORK })}
@@ -348,3 +408,10 @@ Styled.TransactionInfo = styled.span`
Styled.FormInputButton = styled(Button)`
${formMixins.inputInnerButton}
`;
Styled.CheckIcon = styled(Icon)`
margin: 0 1ch;
color: var(--color-positive);
font-size: 0.625rem;
`;
@@ -1,14 +1,19 @@
import { useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent } from 'styled-components';
import { formatUnits } from 'viem';
import { TransferInputTokenResource } from '@/constants/abacus';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign } from '@/constants/numbers';
import { formatSeconds } from '@/lib/timeUtils';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter } from '@/hooks';
import { useAccountBalance } from '@/hooks/useAccountBalance';
import { layoutMixins } from '@/styles/layoutMixins';
import { Button } from '@/components/Button';
@@ -16,6 +21,7 @@ import { Details, DetailsItem } from '@/components/Details';
import { DiffOutput } from '@/components/DiffOutput';
import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType } from '@/components/Output';
import { Tag } from '@/components/Tag';
import { ToggleButton } from '@/components/ToggleButton';
import { WithReceipt } from '@/components/WithReceipt';
@@ -49,22 +55,14 @@ export const WithdrawButtonAndReceipt = ({
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
const stringGetter = useStringGetter();
const { balance, queryStatus } = useAccountBalance({
addressOrDenom: withdrawToken?.address || undefined,
assetSymbol: withdrawToken?.symbol || undefined,
chainId: withdrawChain ? parseInt(withdrawChain) : undefined,
decimals: withdrawToken?.decimals || undefined,
isCosmosChain: false,
});
// TODO: uncomment when we have a way to get token amount estimate from abacus
// const balanceBN = MustBigNumber(balance);
// const newBalance =
// // toAmountMin && withdrawToken && parseUnits(toAmountMin, withdrawToken.decimals).toString();
const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const toAmount =
summary?.toAmount &&
withdrawToken?.decimals &&
formatUnits(BigInt(summary.toAmount), withdrawToken?.decimals);
const feeSubitems: DetailsItem[] = [];
if (typeof summary?.gasFee === 'number') {
@@ -98,9 +96,27 @@ export const WithdrawButtonAndReceipt = ({
),
subitems: feeSubitems,
},
{
key: 'wallet',
label: (
<span>
{stringGetter({ key: STRING_KEYS.AMOUNT_RECEIVED })}{' '}
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
</span>
),
value: (
<Styled.DiffOutput
type={OutputType.Asset}
value={'0'}
newValue={toAmount}
sign={NumberSign.Positive}
withDiff={Boolean(toAmount)}
/>
),
},
{
key: 'leverage',
label: <span>{stringGetter({ key: STRING_KEYS.LEVERAGE })}</span>,
label: <span>{stringGetter({ key: STRING_KEYS.ACCOUNT_LEVERAGE })}</span>,
value: (
<Styled.DiffOutput
type={OutputType.Multiple}
@@ -137,28 +153,22 @@ export const WithdrawButtonAndReceipt = ({
/>
),
},
// TODO: uncomment when we have a way to get token amount estimate from abacus
// {
// key: 'wallet',
// label: (
// <span>
// {stringGetter({ key: STRING_KEYS.WALLET })}{' '}
// {withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
// </span>
// ),
// value: (
// <Styled.DiffOutput
// type={OutputType.Asset}
// value={balanceBN?.toString()}
// newValue={newBalance}
// sign={NumberSign.Negative}
// withDiff={Boolean(balance !== newBalance)}
// />
// ),
// },
{
key: 'estimatedRouteDuration',
label: <span>{stringGetter({ key: STRING_KEYS.ESTIMATED_TIME })}</span>,
value: typeof summary?.estimatedRouteDuration === 'number' && (
<Output
type={OutputType.Text}
value={stringGetter({
key: STRING_KEYS.X_MINUTES_LOWERCASED,
params: { X: Math.round(summary?.estimatedRouteDuration / 60) },
})}
/>
),
},
];
const isFormValid = !isDisabled && !isEditingSlippage && queryStatus !== 'error';
const isFormValid = !isDisabled && !isEditingSlippage;
return (
<Styled.WithReceipt
+2 -2
View File
@@ -80,7 +80,7 @@ export const ClosePositionForm = ({
const { closePosition } = useSubaccount();
const market = useSelector(getCurrentMarketId);
const { symbol } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const { stepSizeDecimals } = useSelector(getCurrentMarketConfig, shallowEqual) || {};
const { size: sizeData, summary } = useSelector(getInputClosePositionData, shallowEqual) || {};
const { size, percent } = sizeData || {};
@@ -193,7 +193,7 @@ export const ClosePositionForm = ({
label={
<>
{stringGetter({ key: STRING_KEYS.AMOUNT })}
{symbol && <Tag>{symbol}</Tag>}
{id && <Tag>{id}</Tag>}
</>
}
decimals={stepSizeDecimals || TOKEN_DECIMALS}
@@ -6,7 +6,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
import { TradeInputField } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { STRING_KEYS, StringKey } from '@/constants/localization';
import { INTEGER_DECIMALS } from '@/constants/numbers';
import { TimeUnitShort } from '@/constants/time';
import { GOOD_TIL_TIME_TIMESCALE_STRINGS } from '@/constants/trade';
@@ -29,12 +29,12 @@ export const AdvancedTradeOptions = () => {
const currentTradeFormConfig = useSelector(getInputTradeOptions, shallowEqual);
const inputTradeData = useSelector(getInputTradeData, shallowEqual);
const { execution, goodUntil, postOnly, reduceOnly, timeInForce } = inputTradeData || {};
const { execution, goodTil, postOnly, reduceOnly, timeInForce } = inputTradeData || {};
const { executionOptions, needsGoodUntil, needsPostOnly, needsReduceOnly, timeInForceOptions } =
currentTradeFormConfig || {};
const { duration, unit } = goodUntil || {};
const { duration, unit } = goodTil || {};
const needsExecution = executionOptions || needsPostOnly || needsReduceOnly;
@@ -62,7 +62,7 @@ export const AdvancedTradeOptions = () => {
<Styled.SelectItem
key={type}
value={type}
label={stringGetter({ key: stringKey })}
label={stringGetter({ key: stringKey as StringKey })}
/>
))}
</Styled.SelectMenu>
@@ -76,7 +76,7 @@ export const AdvancedTradeOptions = () => {
onChange={({ value }: NumberFormatValues) => {
abacusStateManager.setTradeValue({
value: Number(value),
field: TradeInputField.goodUntilDuration,
field: TradeInputField.goodTilDuration,
});
}}
value={duration ?? ''}
@@ -86,7 +86,7 @@ export const AdvancedTradeOptions = () => {
onValueChange={(goodTilTimeTimescale: string) => {
abacusStateManager.setTradeValue({
value: goodTilTimeTimescale,
field: TradeInputField.goodUntilUnit,
field: TradeInputField.goodTilUnit,
});
}}
>
@@ -121,7 +121,7 @@ export const AdvancedTradeOptions = () => {
<Styled.SelectItem
key={type}
value={type}
label={stringGetter({ key: stringKey })}
label={stringGetter({ key: stringKey as StringKey })}
/>
))}
</Styled.SelectMenu>
@@ -9,6 +9,7 @@ import { TRADE_TYPE_STRINGS, MobilePlaceOrderSteps } from '@/constants/trade';
import { useStringGetter } from '@/hooks';
import { AssetIcon } from '@/components/AssetIcon';
import { Button } from '@/components/Button';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
@@ -62,7 +63,7 @@ export const PlaceOrderButtonAndReceipt = ({
const selectedTradeType = getSelectedTradeType(type);
const selectedOrderSide = getSelectedOrderSide(side);
const { fee, price: expectedPrice, total } = summary || {};
const { fee, price: expectedPrice, total, reward } = summary || {};
const items = [
{
@@ -79,6 +80,17 @@ export const PlaceOrderButtonAndReceipt = ({
label: stringGetter({ key: STRING_KEYS.FEE }),
value: <Output type={OutputType.Fiat} value={fee} useGrouping />,
},
{
key: 'max-reward',
label: (
<>
{stringGetter({ key: STRING_KEYS.MAXIMUM_REWARDS })}
{/* <AssetIcon symbol="DYDX" /> */}
</>
),
value: <Output type={OutputType.Asset} value={reward} useGrouping tag={reward ? "Dv4TNT" : ''} />,
tooltip: 'max-reward',
},
{
key: 'total',
label: stringGetter({ key: STRING_KEYS.TOTAL }),
@@ -21,7 +21,7 @@ type ElementProps = {
export const PositionPreview = ({ showNarrowVariation }: ElementProps) => {
const stringGetter = useStringGetter();
const { symbol } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const { configs, oraclePrice } = useSelector(getCurrentMarketData, shallowEqual) || {};
const { size: positionSize } = useSelector(getCurrentMarketPositionData, shallowEqual) || {};
const { stepSizeDecimals, tickSizeDecimals } = configs || {};
@@ -29,12 +29,12 @@ export const PositionPreview = ({ showNarrowVariation }: ElementProps) => {
return (
<Styled.PositionPreviewContainer>
<Styled.YourPosition>
{!showNarrowVariation && <AssetIcon symbol={symbol} />}
{!showNarrowVariation && <AssetIcon symbol={id} />}
<span>
{stringGetter({
key: STRING_KEYS.YOUR_MARKET_POSITION,
params: {
MARKET: showNarrowVariation ? '' : <strong>{symbol ?? ''}</strong>,
MARKET: showNarrowVariation ? '' : <strong>{id ?? ''}</strong>,
},
})}
</span>
@@ -44,7 +44,7 @@ export const PositionPreview = ({ showNarrowVariation }: ElementProps) => {
oraclePrice={oraclePrice}
postOrderSize={positionSize?.postOrder}
stepSizeDecimals={stepSizeDecimals}
symbol={symbol || undefined}
symbol={id || undefined}
tickSizeDecimals={tickSizeDecimals}
showNarrowVariation={showNarrowVariation}
/>
@@ -40,7 +40,7 @@ export const TradeSizeInputs = () => {
const stringGetter = useStringGetter();
const { isTablet } = useBreakpoints();
const { symbol } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const inputTradeSizeData = useSelector(getInputTradeSizeData, shallowEqual);
const currentTradeInputOptions = useSelector(getInputTradeOptions, shallowEqual);
@@ -94,7 +94,7 @@ export const TradeSizeInputs = () => {
shape={ButtonShape.Square}
>
<Icon iconName={IconName.Trade} />
{showUSDCInputOnTablet ? 'USD' : symbol}
{showUSDCInputOnTablet ? 'USD' : id}
</Styled.ToggleButton>
);
@@ -105,10 +105,10 @@ export const TradeSizeInputs = () => {
onInput={onSizeInput}
label={
<>
<WithTooltip tooltip="order-amount" stringParams={{ SYMBOL: symbol ?? '' }} side="right">
<WithTooltip tooltip="order-amount" stringParams={{ SYMBOL: id ?? '' }} side="right">
{stringGetter({ key: STRING_KEYS.AMOUNT })}
</WithTooltip>
{symbol && <Tag>{symbol}</Tag>}
{id && <Tag>{id}</Tag>}
</>
}
slotRight={isTablet && inputToggleButton}
@@ -126,11 +126,7 @@ export const TradeSizeInputs = () => {
decimals={tickSizeDecimals || USD_DECIMALS}
label={
<>
<WithTooltip
tooltip="order-amount-usd"
stringParams={{ SYMBOL: symbol ?? '' }}
side="right"
>
<WithTooltip tooltip="order-amount-usd" stringParams={{ SYMBOL: id ?? '' }} side="right">
{stringGetter({ key: STRING_KEYS.AMOUNT })}
</WithTooltip>
<Tag>USD</Tag>
+94 -88
View File
@@ -3,21 +3,25 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type NumberFormatValues } from 'react-number-format';
import { shallowEqual, useSelector } from 'react-redux';
import type { SyntheticInputEvent } from 'react-number-format/types/types';
import { debounce } from 'lodash';
import { StdFee } from '@cosmjs/stargate';
import { validation } from '@dydxprotocol/v4-client-js';
import { TransferInputField, TransferType } from '@/constants/abacus';
import { AlertType } from '@/constants/alerts';
import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { DYDX_CHAIN_ASSET_COIN_DENOM, DydxChainAsset } from '@/constants/wallets';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { NumberSign } from '@/constants/numbers';
import {
DYDX_CHAIN_ASSET_COIN_DENOM,
DYDX_CHAIN_ASSET_TAGS,
DydxChainAsset,
} from '@/constants/wallets';
import {
useAccountBalance,
useAccounts,
useDydxClient,
useRestrictions,
useSelectedNetwork,
useStringGetter,
useSubaccount,
@@ -52,28 +56,6 @@ type TransferFormProps = {
className?: string;
};
const debouncedEstimateFee = debounce(
async ({ amount, recipientAddress, asset, setFees, simulateTransfer }) => {
if (!amount || !recipientAddress) {
return;
}
try {
const coinDenom = DYDX_CHAIN_ASSET_COIN_DENOM[asset as DydxChainAsset];
const stdFee: StdFee = await simulateTransfer(amount, recipientAddress, coinDenom);
const fee = stdFee?.amount.find((coin) => coin.denom === coinDenom)?.amount;
const feeAmount = MustBigNumber(fee).div(QUANTUM_MULTIPLIER).toNumber();
setFees(feeAmount);
} catch (error) {
console.error('TransferForm > : debouncedEstimateFee > ', error);
}
},
1000,
{ trailing: true }
);
export const TransferForm = ({
selectedAsset = DydxChainAsset.DYDX,
onDone,
@@ -82,8 +64,12 @@ export const TransferForm = ({
const stringGetter = useStringGetter();
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { dydxAddress } = useAccounts();
const { address: recipientAddress, size } = useSelector(getTransferInputs, shallowEqual) || {};
const { transfer, simulateTransfer } = useSubaccount();
const {
address: recipientAddress,
size,
fee,
} = useSelector(getTransferInputs, shallowEqual) || {};
const { transfer } = useSubaccount();
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
const { selectedNetwork } = useSelectedNetwork();
@@ -91,9 +77,8 @@ export const TransferForm = ({
const [asset, setAsset] = useState<DydxChainAsset>(selectedAsset);
// Form states
const [error, setError] = useState<Error | undefined>();
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const [fees, setFees] = useState<number>();
const balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance;
const newBalance =
@@ -104,23 +89,13 @@ export const TransferForm = ({
.toNumber();
const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size;
const showNotEnoughGasWarning = fees && asset === DydxChainAsset.USDC && usdcBalance < fees;
const showNotEnoughGasWarning = fee && asset === DydxChainAsset.USDC && usdcBalance < fee;
// BN
const amountBN = MustBigNumber(amount);
const balanceBN = MustBigNumber(balance);
const newBalanceBN = MustBigNumber(newBalance);
const isAddressValid = useMemo(
() =>
recipientAddress &&
dydxAddress !== recipientAddress &&
validation.isValidAddress(recipientAddress),
[recipientAddress]
);
const isAmountValid = balance && amount && amountBN.gt(0) && newBalanceBN.gte(0);
useEffect(() => {
abacusStateManager.setTransferValue({
value: TransferType.transferOut.rawValue,
@@ -134,50 +109,83 @@ export const TransferForm = ({
useEffect(() => {
setError(undefined);
debouncedEstimateFee.cancel();
if (isAmountValid && isAddressValid) {
debouncedEstimateFee({
amount,
recipientAddress,
asset,
setFees,
simulateTransfer,
});
} else {
setFees(undefined);
}
}, [asset, amount, recipientAddress]);
useEffect(() => {
setError(undefined);
abacusStateManager.setTransferValue({
value: asset,
field: TransferInputField.token,
});
}, [asset]);
const { sanctionedAddresses } = useRestrictions();
const isAddressValid = useMemo(
() =>
recipientAddress &&
dydxAddress !== recipientAddress &&
validation.isValidAddress(recipientAddress) &&
!sanctionedAddresses.has(recipientAddress),
[recipientAddress, sanctionedAddresses, dydxAddress]
);
const isAmountValid = balance && amount && amountBN.gt(0) && newBalanceBN.gte(0);
const { screenAddresses } = useDydxClient();
const onTransfer = async () => {
if (!isAmountValid || !isAddressValid || !fees) return;
if (!isAmountValid || !isAddressValid || !fee) return;
setIsLoading(true);
setError(undefined);
try {
// Subtract fees from amount if sending native tokens
const amountToTransfer = (
asset === DydxChainAsset.DYDX ? amountBN.minus(fees) : amountBN
asset === DydxChainAsset.DYDX ? amountBN.minus(fee) : amountBN
).toNumber();
const txResponse = await transfer(
amountToTransfer,
recipientAddress as string,
DYDX_CHAIN_ASSET_COIN_DENOM[asset]
);
const screenResults = await screenAddresses({
addresses: [recipientAddress!, dydxAddress!],
});
if (txResponse?.code === 0) {
console.log('TransferForm > txReceipt > ', txResponse?.hash);
onDone?.();
if (screenResults?.[dydxAddress!]) {
setError(
stringGetter({
key: STRING_KEYS.WALLET_RESTRICTED_WITHDRAWAL_TRANSFER_ORIGINATION_ERROR_MESSAGE,
})
);
} else if (screenResults?.[recipientAddress!]) {
setError(
stringGetter({
key: STRING_KEYS.WALLET_RESTRICTED_WITHDRAWAL_TRANSFER_DESTINATION_ERROR_MESSAGE,
})
);
} else {
throw new Error(txResponse?.rawLog ?? 'Transaction did not commit.');
const txResponse = await transfer(
amountToTransfer,
recipientAddress as string,
DYDX_CHAIN_ASSET_COIN_DENOM[asset]
);
if (txResponse?.code === 0) {
console.log('TransferForm > txReceipt > ', txResponse?.hash);
onDone?.();
} else {
throw new Error(txResponse?.rawLog ?? 'Transaction did not commit.');
}
}
} catch (error) {
setError(error);
if (error?.code === 429) {
setError(stringGetter({ key: STRING_KEYS.RATE_LIMIT_REACHED_ERROR_MESSAGE }));
} else {
setError(
error.message
? stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: {
ERROR_MESSAGE: error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
},
})
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG })
);
}
log('TransferForm/onTransfer', error);
} finally {
setIsLoading(false);
@@ -212,7 +220,7 @@ export const TransferForm = ({
value: DydxChainAsset.USDC,
label: (
<Styled.InlineRow>
<AssetIcon symbol="USDC" /> USDC
<AssetIcon symbol="USDC" /> {DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.USDC]}
</Styled.InlineRow>
),
},
@@ -221,7 +229,7 @@ export const TransferForm = ({
label: (
<Styled.InlineRow>
{/* <AssetIcon symbol="DYDX" /> */}
Dv4TNT
{DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.DYDX]}
</Styled.InlineRow>
),
},
@@ -229,7 +237,7 @@ export const TransferForm = ({
const networkOptions = [
{
chainId: CLIENT_NETWORK_CONFIGS[selectedNetwork].dydxChainId,
chainId: ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId,
label: (
<Styled.InlineRow>
<AssetIcon symbol="DYDX" /> {stringGetter({ key: STRING_KEYS.DYDX_CHAIN })}
@@ -243,7 +251,7 @@ export const TransferForm = ({
key: 'amount',
label: (
<span>
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{asset}</Tag>
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[asset]}</Tag>
</span>
),
value: (
@@ -259,13 +267,6 @@ export const TransferForm = ({
},
];
const addressValidationErrorMessage = stringGetter({
key:
dydxAddress === recipientAddress
? STRING_KEYS.TRANSFER_TO_YOURSELF
: STRING_KEYS.TRANSFER_INVALID_DYDX_ADDRESS,
});
const renderFormInputButton = ({
label,
isInputEmpty,
@@ -319,7 +320,7 @@ export const TransferForm = ({
/>
<Styled.NetworkSelectMenu
label={stringGetter({ key: STRING_KEYS.NETWORK })}
value={CLIENT_NETWORK_CONFIGS[selectedNetwork].dydxChainId}
value={ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId}
slotTriggerAfter={null}
>
{networkOptions.map(({ chainId, label }) => (
@@ -330,7 +331,12 @@ export const TransferForm = ({
{recipientAddress && !isAddressValid && (
<Styled.AddressValidationAlertMessage type={AlertType.Error}>
{addressValidationErrorMessage}
{stringGetter({
key:
dydxAddress === recipientAddress
? STRING_KEYS.TRANSFER_TO_YOURSELF
: STRING_KEYS.TRANSFER_INVALID_DYDX_ADDRESS,
})}
</Styled.AddressValidationAlertMessage>
)}
@@ -370,14 +376,14 @@ export const TransferForm = ({
</AlertMessage>
)}
{error && <AlertMessage type={AlertType.Error}>{error.message}</AlertMessage>}
{error && <AlertMessage type={AlertType.Error}>{error}</AlertMessage>}
<Styled.Footer>
<TransferButtonAndReceipt
selectedAsset={asset}
fees={fees}
isDisabled={!isAmountValid || !isAddressValid || !fees}
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fees)}
fees={fee || undefined}
isDisabled={!isAmountValid || !isAddressValid || !fee || isLoading}
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fee)}
/>
</Styled.Footer>
</Styled.Form>
@@ -3,7 +3,7 @@ import { shallowEqual, useSelector } from 'react-redux';
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { NumberSign } from '@/constants/numbers';
import { DydxChainAsset } from '@/constants/wallets';
import { DYDX_CHAIN_ASSET_TAGS, DydxChainAsset } from '@/constants/wallets';
import { useAccountBalance, useStringGetter } from '@/hooks';
@@ -60,7 +60,7 @@ export const TransferButtonAndReceipt = ({
key: 'fees',
label: (
<span>
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{selectedAsset}</Tag>
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag>
</span>
),
value: <Output type={OutputType.Asset} value={fees} />,
@@ -69,7 +69,7 @@ export const TransferButtonAndReceipt = ({
key: 'balance',
label: (
<span>
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{selectedAsset}</Tag>
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag>
</span>
),
value: (
+7 -9
View File
@@ -129,15 +129,13 @@ export const useGlobalCommands = (): MenuConfig<string, string> => {
{
value: NavItems.NavigateToMarket,
label: 'Navigate to Market',
subitems: joinedPerpetualMarketsAndAssets.map(
({ market = '', name = '', symbol = '' }) => ({
value: market, // `${market}|${name}`,
slotBefore: <AssetIcon symbol={symbol} />,
label: name,
tag: symbol, // <abbr>{symbol}</abbr>,
onSelect: () => navigate(`/trade/${market}`),
})
),
subitems: joinedPerpetualMarketsAndAssets.map(({ market = '', name = '', id = '' }) => ({
value: market,
slotBefore: <AssetIcon symbol={id} />,
label: name,
tag: id,
onSelect: () => navigate(`/trade/${market}`),
})),
},
],
},
+10 -10
View File
@@ -1,13 +1,13 @@
import { type MenuItem } from '@/constants/menus';
import { type DydxNetwork, NETWORK_ENDPOINTS } from '@/constants/networks';
import { useStringGetter } from '@/hooks';
import {
AVAILABLE_ENVIRONMENTS,
type DydxNetwork,
ENVIRONMENT_CONFIG_MAP,
} from '@/constants/networks';
export const useNetworks = (): MenuItem<DydxNetwork>[] => {
const stringGetter = useStringGetter();
return NETWORK_ENDPOINTS.environments.map(({ stringKey, environment }) => ({
key: environment,
label: stringGetter({ key: stringKey }),
value: environment,
export const useNetworks = (): MenuItem<DydxNetwork>[] =>
(AVAILABLE_ENVIRONMENTS.environments as DydxNetwork[]).map((dydxNetwork) => ({
key: dydxNetwork,
label: ENVIRONMENT_CONFIG_MAP[dydxNetwork].name,
value: dydxNetwork,
}));
};
+4 -6
View File
@@ -89,8 +89,8 @@ const getFillsTableColumnDef = ({
label: `${stringGetter({ key: STRING_KEYS.TYPE })} / ${stringGetter({
key: STRING_KEYS.AMOUNT,
})}`,
renderCell: ({ resources, size, stepSizeDecimals, asset: { symbol } }) => (
<TableCell stacked slotLeft={<Styled.AssetIcon symbol={symbol} />}>
renderCell: ({ resources, size, stepSizeDecimals, asset: { id } }) => (
<TableCell stacked slotLeft={<Styled.AssetIcon symbol={id} />}>
<span>
{resources.typeStringKey ? stringGetter({ key: resources.typeStringKey }) : null}
</span>
@@ -161,7 +161,7 @@ const getFillsTableColumnDef = ({
}[orderSide],
})}
</Styled.Side>
<Output type={OutputType.Text} value={asset.symbol} />
<Output type={OutputType.Text} value={asset.id} />
</Styled.TableCell>
),
},
@@ -325,9 +325,7 @@ export const FillsTable = ({
if (hasUnseenFillUpdates) dispatch(viewedFills());
}, [hasUnseenFillUpdates]);
const symbol = currentMarket
? allAssets[allPerpetualMarkets[currentMarket]?.assetId]?.symbol
: null;
const symbol = currentMarket ? allAssets[allPerpetualMarkets[currentMarket]?.assetId]?.id : null;
const fillsData = fills.map((fill: SubaccountFill) =>
getHydratedTradingData({
+5 -2
View File
@@ -12,7 +12,10 @@ import { MarketTableCell } from '@/components/Table/MarketTableCell';
import { Output, OutputType } from '@/components/Output';
import { Table, TableCell, type ColumnDef } from '@/components/Table';
import { getCurrentMarketFundingPayments, getSubaccountFundingPayments } from '@/state/accountSelectors';
import {
getCurrentMarketFundingPayments,
getSubaccountFundingPayments,
} from '@/state/accountSelectors';
import { getAssets } from '@/state/assetsSelectors';
import { getPerpetualMarkets } from '@/state/perpetualsSelectors';
@@ -111,7 +114,7 @@ export const FundingPaymentsTable = ({
type={OutputType.Asset}
value={Math.abs(positionSize)}
fractionDigits={stepSizeDecimals}
tag={asset.symbol}
tag={asset.id}
/>
<Styled.Output
type={OutputType.Text}
+3 -3
View File
@@ -43,7 +43,7 @@ export const LiveTrades = ({ className, histogramSide = 'left' }: StyleProps) =>
const currentMarketConfig = useSelector(getCurrentMarketConfig, shallowEqual);
const currentMarketLiveTrades = useSelector(getCurrentMarketLiveTrades, shallowEqual) || [];
const { symbol = '' } = currentMarketAssetData ?? {};
const { id = '' } = currentMarketAssetData ?? {};
const { stepSizeDecimals, tickSizeDecimals } = currentMarketConfig || {};
const rows = currentMarketLiveTrades.map(
@@ -84,7 +84,7 @@ export const LiveTrades = ({ className, histogramSide = 'left' }: StyleProps) =>
columnKey: 'size',
getCellValue: (row: RowData) => row.size,
label: stringGetter({ key: STRING_KEYS.SIZE }),
tag: symbol,
tag: id,
renderCell: (row: RowData) => (
<Styled.SizeOutput
type={OutputType.Asset}
@@ -111,7 +111,7 @@ export const LiveTrades = ({ className, histogramSide = 'left' }: StyleProps) =>
},
!isTablet && timeColumn,
].filter(isTruthy);
}, [stepSizeDecimals, tickSizeDecimals, symbol, histogramSide, stringGetter]);
}, [stepSizeDecimals, tickSizeDecimals, id, histogramSide, stringGetter]);
return (
<Styled.LiveTradesTable

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