Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1e817ea92 | ||
|
|
4b85ec7df0 | ||
|
|
051bdda73a | ||
|
|
27b79957d4 | ||
|
|
4bacc901dc | ||
|
|
82d32a91a9 | ||
|
|
2a794f9459 | ||
|
|
5c26a0c270 | ||
|
|
e4991cc31d | ||
|
|
da908c826a | ||
|
|
af46278539 | ||
|
|
ea58b55152 | ||
|
|
54d65bb81c | ||
|
|
e047a49bbc | ||
|
|
14316163e4 | ||
|
|
ac619a9da1 | ||
|
|
cb7f542a99 | ||
|
|
bc5569c4c9 | ||
|
|
dd087ae786 | ||
|
|
eb023ff0ff | ||
|
|
348e82293e | ||
|
|
48a48b4d70 | ||
|
|
944cea0d3d | ||
|
|
4ea2bdf53d | ||
|
|
c40f5eb549 | ||
|
|
d53e8f693e | ||
|
|
f9e301b3f0 | ||
|
|
67ecbd75b4 | ||
|
|
a05d88136d | ||
|
|
f1ca86326d | ||
|
|
5d1d734c59 | ||
|
|
9ba6b3d6ba | ||
|
|
62eeab06f5 | ||
|
|
fa621a085b | ||
|
|
0730e71109 | ||
|
|
fdd3fbd774 |
@@ -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
|
||||
@@ -4,5 +4,6 @@ VITE_ALCHEMY_API_KEY=
|
||||
|
||||
VITE_PK_ENCRYPTION_KEY=
|
||||
|
||||
VITE_WALLETCONNECT1_BRIDGE=
|
||||
VITE_WALLETCONNECT2_PROJECT_ID=
|
||||
|
||||
VITE_V3_TOKEN_ADDRESS=
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,7 @@ dist
|
||||
dist-ssr
|
||||
*.local
|
||||
*.key
|
||||
.env
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
## Prerequisites
|
||||
|
||||
- Node.js version 18 and `pnpm` installed on your system
|
||||
- Wallet Connect account
|
||||
|
||||
For deploying with Vercel, create an account with [Vercel](https://vercel.com/signup) if you don't have one already.
|
||||
|
||||
@@ -61,7 +62,14 @@ pnpm ladle
|
||||
|
||||
This will automatically open your default browser at `http://localhost:61000`.
|
||||
|
||||
## Part 3: Deploying with Vercel
|
||||
## Part 3: Configuring environment
|
||||
|
||||
Add or modify the relevant endpoints, links and options in `/public/configs/env.json`.
|
||||
You'll need to provide a Wallet Connect project id to enable onboarding and wallet connection:
|
||||
- Create a project on https://cloud.walletconnect.com/app
|
||||
- Copy over the project ID into this [field](https://github.com/dydxprotocol/v4-web/blob/67ecbd75b43e0c264b7b4d2d9b3d969830b0621c/public/configs/env.json#L822C33-L822C46)
|
||||
|
||||
## Part 4: Deploying with Vercel
|
||||
|
||||
### Step 1: Connect your repository to Vercel
|
||||
|
||||
@@ -78,7 +86,7 @@ If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our
|
||||
|
||||
For more details, check out Vercel's [official documentation](https://vercel.com/docs).
|
||||
|
||||
## Part 4: Deploying to IPFS
|
||||
## Part 5: Deploying to IPFS
|
||||
|
||||
### web3.storage: deploy to IPFS via web3.storage using the provided script
|
||||
|
||||
|
||||
@@ -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.6.3",
|
||||
"@dydxprotocol/v4-client-js": "^0.36.1",
|
||||
"@dydxprotocol/v4-localization": "^0.1.12",
|
||||
"@dydxprotocol/v4-abacus": "^0.7.2",
|
||||
"@dydxprotocol/v4-client-js": "^0.40.3",
|
||||
"@dydxprotocol/v4-localization": "^0.1.32",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
|
||||
@@ -27,14 +27,14 @@ dependencies:
|
||||
specifier: ^0.31.0
|
||||
version: 0.31.0
|
||||
'@dydxprotocol/v4-abacus':
|
||||
specifier: ^0.6.3
|
||||
version: 0.6.3
|
||||
specifier: ^0.7.2
|
||||
version: 0.7.2
|
||||
'@dydxprotocol/v4-client-js':
|
||||
specifier: ^0.36.1
|
||||
version: 0.36.1
|
||||
specifier: ^0.40.3
|
||||
version: 0.40.3
|
||||
'@dydxprotocol/v4-localization':
|
||||
specifier: ^0.1.12
|
||||
version: 0.1.12
|
||||
specifier: ^0.1.32
|
||||
version: 0.1.32
|
||||
'@ethersproject/providers':
|
||||
specifier: ^5.7.2
|
||||
version: 5.7.2
|
||||
@@ -645,7 +645,7 @@ packages:
|
||||
/@confio/ics23@0.6.8:
|
||||
resolution: {integrity: sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==}
|
||||
dependencies:
|
||||
'@noble/hashes': 1.3.1
|
||||
'@noble/hashes': 1.3.2
|
||||
protobufjs: 6.11.4
|
||||
dev: false
|
||||
|
||||
@@ -717,7 +717,7 @@ packages:
|
||||
'@cosmjs/encoding': 0.30.1
|
||||
'@cosmjs/math': 0.30.1
|
||||
'@cosmjs/utils': 0.30.1
|
||||
'@noble/hashes': 1.3.1
|
||||
'@noble/hashes': 1.3.2
|
||||
bn.js: 5.2.1
|
||||
elliptic: 6.5.4
|
||||
libsodium-wrappers: 0.7.11
|
||||
@@ -979,12 +979,12 @@ packages:
|
||||
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
|
||||
dev: true
|
||||
|
||||
/@dydxprotocol/v4-abacus@0.6.3:
|
||||
resolution: {integrity: sha512-oYMbII7o7fXjNpl2PZpG6mwKQ49e8Qky5vA8Ua9boNs7nRUx3PC6azfoPtjHKMdYY33eHoXcZBpTynhneLXCVg==}
|
||||
/@dydxprotocol/v4-abacus@0.7.2:
|
||||
resolution: {integrity: sha512-5foamomN82FH6ko3EuzwFlSoAEY6Sal8fDgvZo2RsB19Ks6SFgFDeeOcEDdfoa3DNwvl8yW0C9cfxeQ+BfU8uA==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-client-js@0.36.1:
|
||||
resolution: {integrity: sha512-KJ3MtWI61wmm+xZtXQd7/hYKIoVFG32TRdyyVaXXO8vpW9Oqd0JD8QSM/wuegkciAh5YmP/BrDzYuu77zHO6yg==}
|
||||
/@dydxprotocol/v4-client-js@0.40.3:
|
||||
resolution: {integrity: sha512-nMQd6tYisKDKIbfx0BGJSYEOSt+I+t5SsaBSmlgTdso+w2rCRO9vyxS+u6MF9h4w5FsVRfhHo3e6uTWX48WpGQ==}
|
||||
dependencies:
|
||||
'@cosmjs/amino': 0.30.1
|
||||
'@cosmjs/encoding': 0.31.1
|
||||
@@ -993,14 +993,15 @@ packages:
|
||||
'@cosmjs/stargate': 0.30.1
|
||||
'@cosmjs/tendermint-rpc': 0.30.1
|
||||
'@cosmjs/utils': 0.31.1
|
||||
'@dydxprotocol/v4-proto': 0.2.1
|
||||
'@dydxprotocol/v4-proto': 0.4.1
|
||||
'@osmonauts/lcd': 0.6.0
|
||||
'@scure/bip32': 1.3.1
|
||||
'@scure/bip32': 1.3.2
|
||||
'@scure/bip39': 1.2.1
|
||||
axios: 1.1.3
|
||||
bech32: 1.1.4
|
||||
bignumber.js: 9.1.1
|
||||
ethereum-cryptography: 2.1.2
|
||||
ethers: 6.6.1
|
||||
long: 4.0.0
|
||||
protobufjs: 6.11.4
|
||||
ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10)
|
||||
@@ -1010,12 +1011,12 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-localization@0.1.12:
|
||||
resolution: {integrity: sha512-EiP/8+6Nk9QBa6YSWsyzsZaXoVbz5o/KgUlMp1Cew6VWkXL6DkQY0oMIEEy21pv0zuu/woxhlbyXygpUIcpdMg==}
|
||||
/@dydxprotocol/v4-localization@0.1.32:
|
||||
resolution: {integrity: sha512-ZtXScxgFLjUt0Ag52RyFnQe9aEl1Aky5fp+vMGOBduVkFiCEMRMkSsSgg6grMdyw/x0AbnxOTRkhYoX2MsK9Ow==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-proto@0.2.1:
|
||||
resolution: {integrity: sha512-Q5b/g331DoyMjBpO2/jtrxUX3WF5UaIKZp/unK332xuefHNFnkMYtZ05p0V71iSdO35tBdz8yt0VYP2WtUsp+w==}
|
||||
/@dydxprotocol/v4-proto@0.4.1:
|
||||
resolution: {integrity: sha512-2pBkokufWcxfIpiXE6QuQOBXUe154OTp6ojCIdOPW9TFLxzchoygxXnb5dL6rAQVNj9Qbg+hhXBllutt4m99dw==}
|
||||
dependencies:
|
||||
protobufjs: 6.11.4
|
||||
dev: false
|
||||
@@ -4708,8 +4709,8 @@ packages:
|
||||
resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==}
|
||||
dependencies:
|
||||
'@noble/curves': 1.1.0
|
||||
'@noble/hashes': 1.3.1
|
||||
'@scure/base': 1.1.1
|
||||
'@noble/hashes': 1.3.2
|
||||
'@scure/base': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@scure/bip32@1.3.2:
|
||||
@@ -4731,7 +4732,7 @@ packages:
|
||||
resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==}
|
||||
dependencies:
|
||||
'@noble/hashes': 1.3.2
|
||||
'@scure/base': 1.1.1
|
||||
'@scure/base': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@solana/buffer-layout@4.0.1:
|
||||
@@ -4745,8 +4746,8 @@ packages:
|
||||
resolution: {integrity: sha512-up5VG1dK+GPhykmuMIozJZBbVqpm77vbOG6/r5dS7NBGZonwHfTLdBbsYc3rjmaQ4DpCXUa3tUc4RZHRORvZrw==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.10
|
||||
'@noble/curves': 1.1.0
|
||||
'@noble/hashes': 1.3.1
|
||||
'@noble/curves': 1.2.0
|
||||
'@noble/hashes': 1.3.2
|
||||
'@solana/buffer-layout': 4.0.1
|
||||
agentkeepalive: 4.5.0
|
||||
bigint-buffer: 1.1.5
|
||||
@@ -7144,7 +7145,7 @@ packages:
|
||||
/bip39@3.1.0:
|
||||
resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==}
|
||||
dependencies:
|
||||
'@noble/hashes': 1.3.1
|
||||
'@noble/hashes': 1.3.2
|
||||
dev: false
|
||||
|
||||
/bl@5.1.0:
|
||||
|
||||
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 122 KiB |
@@ -1,244 +1,847 @@
|
||||
{
|
||||
"deployments":{
|
||||
"MAINNET":null,
|
||||
"TESTNET":{
|
||||
"environments":[
|
||||
"deployments": {
|
||||
"MAINNET": {
|
||||
"environments": [
|
||||
"dydxprotocol-mainnet"
|
||||
],
|
||||
"default": "dydxprotocol-mainnet"
|
||||
},
|
||||
"TESTNET": {
|
||||
"environments": [
|
||||
"dydxprotocol-testnet"
|
||||
],
|
||||
"default":"dydxprotocol-testnet"
|
||||
"default": "dydxprotocol-testnet"
|
||||
},
|
||||
"DEV":{
|
||||
"environments":[
|
||||
"DEV": {
|
||||
"environments": [
|
||||
"dydxprotocol-dev",
|
||||
"dydxprotocol-dev-2",
|
||||
"dydxprotocol-dev-4",
|
||||
"dydxprotocol-dev-5",
|
||||
"dydxprotocol-staging",
|
||||
"dydxprotocol-testnet"
|
||||
"dydxprotocol-staging-west",
|
||||
"dydxprotocol-testnet",
|
||||
"dydxprotocol-testnet-dydx",
|
||||
"dydxprotocol-testnet-dsrv",
|
||||
"dydxprotocol-testnet-nodefleet",
|
||||
"dydxprotocol-testnet-kingnodes",
|
||||
"dydxprotocol-testnet-liquify"
|
||||
],
|
||||
"default":"dydxprotocol-testnet"
|
||||
"default": "dydxprotocol-testnet"
|
||||
}
|
||||
},
|
||||
"environments":{
|
||||
"dydxprotocol-dev":{
|
||||
"name":"v4 Dev",
|
||||
"ethereumChainId":"5",
|
||||
"dydxChainId":"dydxprotocol-testnet",
|
||||
"isMainNet":false,
|
||||
"tokens":{
|
||||
"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": "dv4tnt",
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints":{
|
||||
"indexers":[
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api":"http://indexer.v4dev.dydx.exchange",
|
||||
"socket":"wss://indexer.v4dev.dydx.exchange"
|
||||
"api": "http://indexer.v4dev.dydx.exchange",
|
||||
"socket": "wss://indexer.v4dev.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators":[
|
||||
"validators": [
|
||||
"http://validator.v4dev.dydx.exchange"
|
||||
],
|
||||
"0xsquid":"https://testnet.api.0xsquid.com",
|
||||
"faucet":"http://faucet.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}"
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"isMainNet":false,
|
||||
"tokens":{
|
||||
"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": "dv4tnt",
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints":{
|
||||
"indexers":[
|
||||
"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"
|
||||
"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"
|
||||
"validators": [
|
||||
"http://54.92.118.111"
|
||||
],
|
||||
"0xsquid":"https://testnet.api.0xsquid.com"
|
||||
"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}"
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"isMainNet":false,
|
||||
"tokens":{
|
||||
"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": "dv4tnt",
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints":{
|
||||
"indexers":[
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api":"http://indexer.v4dev4.dydx.exchange",
|
||||
"socket":"ws://indexer.v4dev4.dydx.exchange"
|
||||
"api": "http://indexer.v4dev4.dydx.exchange",
|
||||
"socket": "ws://indexer.v4dev4.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators":[
|
||||
"validators": [
|
||||
"http://validator.v4dev4.dydx.exchange"
|
||||
],
|
||||
"0xsquid":"https://testnet.api.0xsquid.com"
|
||||
"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}"
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"isMainNet":false,
|
||||
"tokens":{
|
||||
"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": "dv4tnt",
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints":{
|
||||
"indexers":[
|
||||
"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"
|
||||
"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":[
|
||||
"validators": [
|
||||
"http://18.223.78.50"
|
||||
],
|
||||
"0xsquid":"https://testnet.api.0xsquid.com"
|
||||
"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}"
|
||||
"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"
|
||||
},
|
||||
"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",
|
||||
"isMainNet":false,
|
||||
"tokens":{
|
||||
"dydxprotocol-staging": {
|
||||
"name": "v4 Staging",
|
||||
"ethereumChainId": "5",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
"name": "DYDX",
|
||||
"denom": "dv4tnt",
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints":{
|
||||
"indexers":[
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api":"https://indexer.v4staging.dydx.exchange",
|
||||
"socket":"wss://indexer.v4staging.dydx.exchange"
|
||||
"api": "https://indexer.v4staging.dydx.exchange",
|
||||
"socket": "wss://indexer.v4staging.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"faucet":"https://faucet.v4staging.dydx.exchange",
|
||||
"validators":[
|
||||
"faucet": "https://faucet.v4staging.dydx.exchange",
|
||||
"validators": [
|
||||
"https://validator.v4staging.dydx.exchange"
|
||||
],
|
||||
"0xsquid":"https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app"
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"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-3",
|
||||
"isMainNet":false,
|
||||
"tokens":{
|
||||
"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": "dv4tnt",
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints":{
|
||||
"indexers":[
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api":"https://dydx-testnet.imperator.co",
|
||||
"socket":"wss://dydx-testnet.imperator.co"
|
||||
"api": "https://indexer.v4staging.dydx.exchange",
|
||||
"socket": "wss://indexer.v4staging.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators":[
|
||||
"https://dydx-testnet.nodefleet.org",
|
||||
"https://test-dydx.kingnodes.com/"
|
||||
"faucet": "https://faucet.v4staging.dydx.exchange",
|
||||
"validators": [
|
||||
"https://validator-uswest1.v4staging.dydx.exchange"
|
||||
],
|
||||
"0xsquid":"https://squid-api-git-main-cosmos-testnet-0xsquid.vercel.app",
|
||||
"faucet":"https://faucet.v4testnet.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"
|
||||
"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"
|
||||
},
|
||||
"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": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-testnet.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"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "/walletsegue"
|
||||
},
|
||||
"images": "/wallets/",
|
||||
"signTypedDataAction": "dYdX Chain 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": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://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"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "/walletsegue"
|
||||
},
|
||||
"images": "/wallets/",
|
||||
"signTypedDataAction": "dYdX Chain Onboarding",
|
||||
"signTypedDataDomainName": "dYdX V4"
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-dsrv": {
|
||||
"name": "v4 Public Testnet/DSRV",
|
||||
"ethereumChainId": "5",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-testnet-archive.allthatnode.com:26657/XZvMM41hESf8PJrEQiTzbCOMVyFca79R"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"links": {
|
||||
"tos": "https://dydx.exchange/v4-terms",
|
||||
"privacy": "https://dydx.exchange/privacy",
|
||||
"statusPage": "https://status.v4testnet.dydx.exchange/",
|
||||
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||
"documentation": "https://v4-teacher.vercel.app/",
|
||||
"community": "https://discord.com/invite/dydx",
|
||||
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
||||
"blogs": "https://www.dydx.foundation/blog"
|
||||
},
|
||||
"wallets": {
|
||||
"walletconnect": {
|
||||
"client": {
|
||||
"name": "dYdX v4",
|
||||
"description": "dYdX v4 App",
|
||||
"iconUrl": "/logos/dydx-x.png"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "/walletsegue"
|
||||
},
|
||||
"images": "/wallets/",
|
||||
"signTypedDataAction": "dYdX Chain Onboarding",
|
||||
"signTypedDataDomainName": "dYdX V4"
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-nodefleet": {
|
||||
"name": "v4 Public Testnet/nodefleet",
|
||||
"ethereumChainId": "5",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "dydx-chain.png",
|
||||
"isMainNet": false,
|
||||
"tokens": {
|
||||
"chain": {
|
||||
"name": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-testnet.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"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "/walletsegue"
|
||||
},
|
||||
"images": "/wallets/",
|
||||
"signTypedDataAction": "dYdX Chain 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": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://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"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "/walletsegue"
|
||||
},
|
||||
"images": "/wallets/",
|
||||
"signTypedDataAction": "dYdX Chain 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": "Dv4TNT",
|
||||
"denom": "adv4tnt",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
},
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://dydx-testnet.imperator.co",
|
||||
"socket": "wss://dydx-testnet.imperator.co"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-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"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "/walletsegue"
|
||||
},
|
||||
"images": "/wallets/",
|
||||
"signTypedDataAction": "dYdX Chain Onboarding",
|
||||
"signTypedDataDomainName": "dYdX V4"
|
||||
}
|
||||
},
|
||||
"dydxprotocol-mainnet": {
|
||||
"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": "TokenName",
|
||||
"denom": "tokenDenom",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"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]"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 4.8 KiB |
@@ -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
|
||||
@@ -7,13 +7,20 @@ import { GrazProvider } from 'graz';
|
||||
|
||||
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
|
||||
|
||||
import { useBreakpoints, useInitializePage, useShouldShowFooter, useAnalytics } from '@/hooks';
|
||||
import {
|
||||
useBreakpoints,
|
||||
useTokenConfigs,
|
||||
useInitializePage,
|
||||
useShouldShowFooter,
|
||||
useAnalytics,
|
||||
} from '@/hooks';
|
||||
import { DydxProvider } from '@/hooks/useDydxClient';
|
||||
import { AccountsProvider } from '@/hooks/useAccounts';
|
||||
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';
|
||||
|
||||
@@ -26,6 +33,8 @@ import ProfilePage from '@/pages/Profile';
|
||||
import { SettingsPage } from '@/pages/settings/Settings';
|
||||
import TradePage from '@/pages/trade/Trade';
|
||||
import { RewardsPage } from '@/pages/rewards/RewardsPage';
|
||||
import { TermsOfUsePage } from '@/pages/TermsOfUsePage';
|
||||
import { PrivacyPolicyPage } from '@/pages/PrivacyPolicyPage';
|
||||
|
||||
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
|
||||
import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
|
||||
@@ -54,6 +63,7 @@ const Content = () => {
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
const isShowingHeader = isNotTablet;
|
||||
const isShowingFooter = useShouldShowFooter();
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
return (
|
||||
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
|
||||
@@ -66,10 +76,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={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
||||
{isTablet && (
|
||||
<>
|
||||
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
||||
@@ -81,6 +90,10 @@ const Content = () => {
|
||||
<Route element={<GuardedMobileRoute />}>
|
||||
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
||||
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
||||
|
||||
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
@@ -111,6 +124,7 @@ const providers = [
|
||||
wrapProvider(GrazProvider),
|
||||
wrapProvider(WagmiConfig, { config }),
|
||||
wrapProvider(LocaleProvider),
|
||||
wrapProvider(RestrictionProvider),
|
||||
wrapProvider(DydxProvider),
|
||||
wrapProvider(AccountsProvider),
|
||||
wrapProvider(SubaccountProvider),
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
AddressConnectorIcon,
|
||||
ArrowIcon,
|
||||
Bar3Icon,
|
||||
BellIcon,
|
||||
BellStrokeIcon,
|
||||
BoxCloseIcon,
|
||||
CalculatorIcon,
|
||||
CaretIcon,
|
||||
@@ -71,6 +73,8 @@ export enum IconName {
|
||||
AddressConnector = 'AddressConnector',
|
||||
Arrow = 'Arrow',
|
||||
Bar3 = 'Bar3',
|
||||
Bell = 'Bell',
|
||||
BellStroked = 'BellStroked',
|
||||
BoxClose = 'BoxClose',
|
||||
Calculator = 'Calculator',
|
||||
Caret = 'Caret',
|
||||
@@ -137,6 +141,8 @@ const icons = {
|
||||
[IconName.AddressConnector]: AddressConnectorIcon,
|
||||
[IconName.Arrow]: ArrowIcon,
|
||||
[IconName.Bar3]: Bar3Icon,
|
||||
[IconName.Bell]: BellIcon,
|
||||
[IconName.BellStroked]: BellStrokeIcon,
|
||||
[IconName.BoxClose]: BoxCloseIcon,
|
||||
[IconName.Calculator]: CalculatorIcon,
|
||||
[IconName.Caret]: CaretIcon,
|
||||
@@ -213,7 +219,8 @@ export const Icon = styled(
|
||||
iconComponent: Component = iconName && icons[iconName],
|
||||
className,
|
||||
...props
|
||||
}: ElementProps & StyleProps) => (Component ? <Component className={className} {...props} /> : null)
|
||||
}: ElementProps & StyleProps) =>
|
||||
Component ? <Component className={className} {...props} /> : null
|
||||
)`
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
@@ -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)`
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -59,16 +59,6 @@ export const TransactionType = Abacus.exchange.dydx.abacus.protocols.Transaction
|
||||
const transactionTypes = [...TransactionType.values()] as const;
|
||||
export type TransactionTypes = (typeof transactionTypes)[number];
|
||||
|
||||
export type NetworkConfig = {
|
||||
chainId: string;
|
||||
indexerUrl: string;
|
||||
indexerSocketUrl: string;
|
||||
validatorUrl: string;
|
||||
faucetUrl?: string | null;
|
||||
};
|
||||
|
||||
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
||||
|
||||
// ------ State ------
|
||||
export type AbacusApiState = Abacus.exchange.dydx.abacus.state.manager.ApiState;
|
||||
export const AbacusApiStatus = Abacus.exchange.dydx.abacus.state.manager.ApiStatus;
|
||||
@@ -78,8 +68,13 @@ 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;
|
||||
@@ -199,6 +194,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
|
||||
@@ -256,3 +257,20 @@ export const TRADE_TYPES: Record<
|
||||
[AbacusOrderType.liquidation.name]: null,
|
||||
[AbacusOrderType.trailingStop.name]: null,
|
||||
};
|
||||
|
||||
// Custom types involving Abacus
|
||||
|
||||
export type NetworkConfig = Partial<{
|
||||
indexerUrl: Nullable<string>;
|
||||
websocketUrl: Nullable<string>;
|
||||
validatorUrl: Nullable<string>;
|
||||
chainId: Nullable<string>;
|
||||
faucetUrl: Nullable<string>;
|
||||
USDC_DENOM: Nullable<string>;
|
||||
USDC_DECIMALS: Nullable<number>;
|
||||
USDC_GAS_DENOM: Nullable<string>;
|
||||
CHAINTOKEN_DENOM: Nullable<string>;
|
||||
CHAINTOKEN_DECIMALS: Nullable<number>;
|
||||
}>;
|
||||
|
||||
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -24,6 +24,6 @@ export enum LocalStorageKey {
|
||||
}
|
||||
|
||||
export const LOCAL_STORAGE_VERSIONS = {
|
||||
[LocalStorageKey.EvmDerivedAddresses]: 'v1',
|
||||
[LocalStorageKey.EvmDerivedAddresses]: 'v2',
|
||||
// TODO: version all localStorage keys
|
||||
};
|
||||
|
||||
@@ -7,7 +7,8 @@ export enum AppRoute {
|
||||
Profile = '/profile',
|
||||
Alerts = '/alerts',
|
||||
Settings = '/settings',
|
||||
Rewards = '/DV4TNT',
|
||||
Terms = '/terms',
|
||||
Privacy = '/privacy',
|
||||
}
|
||||
|
||||
export enum PortfolioRoute {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ExternalProvider } from '@ethersproject/providers';
|
||||
import { USDC_DENOM, type onboarding, DYDX_DENOM } from '@dydxprotocol/v4-client-js';
|
||||
import { type onboarding } from '@dydxprotocol/v4-client-js';
|
||||
import type { suggestChain } from 'graz';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
@@ -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,19 +87,35 @@ export enum WalletType {
|
||||
Rainbow = 'RAINBOW_WALLET',
|
||||
TokenPocket = 'TOKEN_POCKET',
|
||||
TrustWallet = 'TRUST_WALLET',
|
||||
WalletConnect = 'WALLETCONNECT',
|
||||
WalletConnect2 = 'WALLETCONNECT_2',
|
||||
// TestWallet = 'TEST_WALLET',
|
||||
OtherWallet = 'OTHER_WALLET',
|
||||
}
|
||||
|
||||
const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
||||
Metamask: 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96',
|
||||
imToken: 'ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef',
|
||||
TokenPocket: '20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66',
|
||||
Trust: '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0',
|
||||
Rainbow: '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369',
|
||||
Zerion: 'ecc4036f814562b41a5268adc86270fba1365471402006302e70169465b7ac18',
|
||||
Ledger: '19177a98252e07ddfc9af2083ba8e07ef627cb6103467ffebb3f8f4205fd7927',
|
||||
Fireblocks: '5864e2ced7c293ed18ac35e0db085c09ed567d67346ccb6f58a0327a75137489',
|
||||
Uniswap: 'c03dfee351b6fcc421b4494ea33b9d4b92a984f87aa76d1663bb28705e95034a',
|
||||
Robinhood: '8837dd9413b1d9b585ee937d27a816590248386d9dbf59f5cd3422dbbb65683e',
|
||||
'1inch': 'c286eebc742a537cd1d6818363e9dc53b21759a1e8e5d9b263d0c03ec7703576',
|
||||
};
|
||||
|
||||
export const WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS = Object.values(
|
||||
WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS
|
||||
);
|
||||
|
||||
type WalletConfig = {
|
||||
type: WalletType;
|
||||
stringKey: string;
|
||||
icon: string;
|
||||
connectionTypes: WalletConnectionType[];
|
||||
matchesInjectedEip1193?: (provider: ExternalProvider & any) => boolean;
|
||||
walletconnect1Name?: string;
|
||||
walletconnect2Id?: string;
|
||||
};
|
||||
|
||||
@@ -115,7 +128,6 @@ export const wallets: Record<WalletType, WalletConfig> = {
|
||||
WalletConnectionType.InjectedEip1193,
|
||||
// WalletConnectionType.CoinbaseWalletSdk,
|
||||
WalletConnectionType.WalletConnect2,
|
||||
WalletConnectionType.WalletConnect1,
|
||||
],
|
||||
matchesInjectedEip1193: (provider) =>
|
||||
Object.entries(wallets).every(
|
||||
@@ -129,30 +141,22 @@ 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,122 +165,78 @@ 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',
|
||||
},
|
||||
[WalletType.ImToken]: {
|
||||
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',
|
||||
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.imToken,
|
||||
},
|
||||
[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',
|
||||
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.Metamask,
|
||||
},
|
||||
[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',
|
||||
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.Rainbow,
|
||||
},
|
||||
[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',
|
||||
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.TokenPocket,
|
||||
},
|
||||
[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',
|
||||
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.Trust,
|
||||
},
|
||||
[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 +278,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>;
|
||||
|
||||
@@ -341,10 +302,10 @@ export type DydxAddress = `dydx${string}`;
|
||||
export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
|
||||
rpc: 'https://dydx-testnet-archive.allthatnode.com:26657',
|
||||
rest: 'https://dydx-testnet-archive.allthatnode.com:1317',
|
||||
chainId: 'dydx-testnet-3',
|
||||
chainId: 'dydx-testnet-4',
|
||||
chainName: 'dYdX Public Testnet',
|
||||
chainSymbolImageUrl:
|
||||
'https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/dydx-testnet-3/chain.png',
|
||||
'https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/dydx-testnet-4/chain.png',
|
||||
bech32Config: {
|
||||
bech32PrefixAccPub: 'dydxpub',
|
||||
bech32PrefixValPub: 'dydxvaloperpub',
|
||||
@@ -358,26 +319,31 @@ export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
|
||||
},
|
||||
stakeCurrency: {
|
||||
coinDenom: 'DV4TNT',
|
||||
coinDecimals: 6,
|
||||
coinMinimalDenom: 'dv4tnt',
|
||||
coinDecimals: 18,
|
||||
coinMinimalDenom: 'adv4tnt',
|
||||
},
|
||||
currencies: [
|
||||
{
|
||||
coinDenom: 'DV4TNT',
|
||||
coinDecimals: 6,
|
||||
coinMinimalDenom: 'dv4tnt',
|
||||
coinDecimals: 18,
|
||||
coinMinimalDenom: 'adv4tnt',
|
||||
},
|
||||
{
|
||||
coinDenom: 'USDC',
|
||||
coinMinimalDenom: 'ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5',
|
||||
coinDecimals: 6,
|
||||
coinMinimalDenom: 'ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5',
|
||||
},
|
||||
],
|
||||
feeCurrencies: [
|
||||
{
|
||||
coinDenom: 'DV4TNT',
|
||||
coinDecimals: 18,
|
||||
coinMinimalDenom: 'adv4tnt',
|
||||
},
|
||||
{
|
||||
coinDenom: 'USDC',
|
||||
coinDecimals: 6,
|
||||
coinMinimalDenom: 'dv4tnt',
|
||||
coinMinimalDenom: 'ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5',
|
||||
},
|
||||
],
|
||||
features: [],
|
||||
@@ -386,15 +352,5 @@ export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
|
||||
// TODO: export this type from abacus instead
|
||||
export enum DydxChainAsset {
|
||||
USDC = 'usdc',
|
||||
DYDX = 'dydx',
|
||||
CHAINTOKEN = 'chain',
|
||||
}
|
||||
|
||||
export const DYDX_CHAIN_ASSET_COIN_DENOM: Record<DydxChainAsset, string> = {
|
||||
[DydxChainAsset.USDC]: USDC_DENOM,
|
||||
[DydxChainAsset.DYDX]: DYDX_DENOM,
|
||||
};
|
||||
|
||||
export const DYDX_CHAIN_ASSET_TAGS: Record<DydxChainAsset, string> = {
|
||||
[DydxChainAsset.USDC]: 'USDC',
|
||||
[DydxChainAsset.DYDX]: 'Dv4TNT',
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useApiState } from './useApiState';
|
||||
import { useBreakpoints } from './useBreakpoints';
|
||||
import { useTokenConfigs } from './useTokenConfigs';
|
||||
import { useCommandMenu } from './useCommandMenu';
|
||||
import { useCurrentMarketId } from './useCurrentMarketId';
|
||||
import { useDebounce } from './useDebounce';
|
||||
@@ -16,6 +17,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';
|
||||
@@ -24,6 +26,7 @@ import { useSubaccount } from './useSubaccount';
|
||||
export {
|
||||
useApiState,
|
||||
useBreakpoints,
|
||||
useTokenConfigs,
|
||||
useCommandMenu,
|
||||
useCurrentMarketId,
|
||||
useDebounce,
|
||||
@@ -40,6 +43,7 @@ export {
|
||||
useNow,
|
||||
useOnClickOutside,
|
||||
usePageTitlePriceUpdates,
|
||||
useRestrictions,
|
||||
useShouldShowFooter,
|
||||
useSelectedNetwork,
|
||||
useStringGetter,
|
||||
|
||||
@@ -33,7 +33,7 @@ export const useTradingView = ({
|
||||
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||
const selectedLocale = useSelector(getSelectedLocale);
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const { getCandlesForDatafeed } = useDydxClient();
|
||||
const { getCandlesForDatafeed, isConnected: isClientConnected } = useDydxClient();
|
||||
|
||||
const [savedTvChartConfig, setTvChartConfig] = useLocalStorage<object | undefined>({
|
||||
key: LocalStorageKey.TradingViewChartConfig,
|
||||
@@ -44,7 +44,7 @@ export const useTradingView = ({
|
||||
const hasMarkets = marketIds.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMarkets) {
|
||||
if (hasMarkets && isClientConnected) {
|
||||
const widgetOptions = getWidgetOptions();
|
||||
const widgetOverrides = getWidgetOverrides(appTheme);
|
||||
const options = {
|
||||
@@ -75,7 +75,7 @@ export const useTradingView = ({
|
||||
tvWidgetRef.current = null;
|
||||
setIsChartReady(false);
|
||||
};
|
||||
}, [getCandlesForDatafeed, hasMarkets, selectedLocale, selectedNetwork]);
|
||||
}, [getCandlesForDatafeed, isClientConnected, hasMarkets, selectedLocale, selectedNetwork]);
|
||||
|
||||
return { savedResolution };
|
||||
};
|
||||
|
||||
@@ -5,8 +5,6 @@ import { StargateClient } from '@cosmjs/stargate';
|
||||
import { useQuery } from 'react-query';
|
||||
import { formatUnits } from 'viem';
|
||||
|
||||
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';
|
||||
@@ -14,10 +12,11 @@ import { EvmAddress } from '@/constants/wallets';
|
||||
import { convertBech32Address } from '@/lib/addressUtils';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
|
||||
import { getBalances } from '@/state/accountSelectors';
|
||||
import { getBalances, getStakingBalances } from '@/state/accountSelectors';
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
import { useAccounts } from './useAccounts';
|
||||
import { useTokenConfigs } from './useTokenConfigs';
|
||||
|
||||
type UseAccountBalanceProps = {
|
||||
// Token Items
|
||||
@@ -52,7 +51,9 @@ export const useAccountBalance = ({
|
||||
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const balances = useSelector(getBalances, shallowEqual);
|
||||
const { chainTokenDenom, usdcDenom } = useTokenConfigs();
|
||||
const evmChainId = Number(ENVIRONMENT_CONFIG_MAP[selectedNetwork].ethereumChainId);
|
||||
const stakingBalances = useSelector(getStakingBalances, shallowEqual);
|
||||
|
||||
const evmQuery = useBalance({
|
||||
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
|
||||
@@ -92,17 +93,19 @@ export const useAccountBalance = ({
|
||||
const { formatted: evmBalance } = evmQuery.data || {};
|
||||
const balance = !assetSymbol ? '0' : isCosmosChain ? cosmosQuery.data : evmBalance;
|
||||
|
||||
const nativeTokenCoinBalance = balances?.[DYDX_DENOM];
|
||||
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount)
|
||||
.div(QUANTUM_MULTIPLIER)
|
||||
.toNumber();
|
||||
const nativeTokenCoinBalance = balances?.[chainTokenDenom];
|
||||
const nativeTokenBalance = MustBigNumber(nativeTokenCoinBalance?.amount);
|
||||
|
||||
const usdcCoinBalance = balances?.[USDC_DENOM];
|
||||
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber();
|
||||
const usdcCoinBalance = balances?.[usdcDenom];
|
||||
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).toNumber();
|
||||
|
||||
const nativeStakingCoinBalanace = stakingBalances?.[chainTokenDenom];
|
||||
const nativeStakingBalance = MustBigNumber(nativeStakingCoinBalanace?.amount).toNumber();
|
||||
|
||||
return {
|
||||
balance,
|
||||
nativeTokenBalance,
|
||||
nativeStakingBalance,
|
||||
usdcBalance,
|
||||
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
|
||||
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
|
||||
|
||||
@@ -2,21 +2,23 @@ import { useCallback, useContext, createContext, useEffect, useState, useMemo }
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-client-js';
|
||||
import { LocalWallet, type Subaccount } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||
import { 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]);
|
||||
@@ -126,16 +130,8 @@ const useAccountsContext = () => {
|
||||
// dYdX subaccounts
|
||||
const [dydxSubaccounts, setDydxSubaccounts] = useState<Subaccount[] | undefined>();
|
||||
|
||||
const { getAccountBalance, getSubaccounts } = useMemo(
|
||||
const { getSubaccounts } = useMemo(
|
||||
() => ({
|
||||
getAccountBalance: async ({
|
||||
dydxAddress,
|
||||
denom = USDC_DENOM,
|
||||
}: {
|
||||
dydxAddress: DydxAddress;
|
||||
denom?: string;
|
||||
}) => await compositeClient?.validatorClient.get.getAccountBalance(dydxAddress, denom),
|
||||
|
||||
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
|
||||
try {
|
||||
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
|
||||
@@ -217,7 +213,6 @@ const useAccountsContext = () => {
|
||||
}, [evmAddress, evmDerivedAddresses, signerWagmi, connectedDydxAddress, signerGraz]);
|
||||
|
||||
// abacus
|
||||
// TODO: useAbacus({ dydxAddress })
|
||||
useEffect(() => {
|
||||
if (dydxAddress) abacusStateManager.setAccount(localDydxWallet);
|
||||
else abacusStateManager.attemptDisconnectAccount();
|
||||
@@ -258,6 +253,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);
|
||||
@@ -308,7 +318,6 @@ const useAccountsContext = () => {
|
||||
disconnect,
|
||||
|
||||
// dydxClient Account methods
|
||||
getAccountBalance,
|
||||
getSubaccounts,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -44,6 +44,9 @@ export const useCurrentMarketId = () => {
|
||||
}, [validId]);
|
||||
|
||||
useEffect(() => {
|
||||
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
||||
}, [selectedNetwork, marketId]);
|
||||
// Check for marketIds otherwise Abacus will silently fail its isMarketValid check
|
||||
if (marketIds) {
|
||||
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
||||
}
|
||||
}, [selectedNetwork, marketIds, marketId]);
|
||||
};
|
||||
|
||||
@@ -2,27 +2,23 @@ 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' && 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);
|
||||
|
||||
@@ -13,12 +13,17 @@ import {
|
||||
|
||||
import type { ResolutionString } from 'public/tradingview/charting_library';
|
||||
|
||||
import type { NetworkConfig, ConnectNetworkEvent } from '@/constants/abacus';
|
||||
import type { ConnectNetworkEvent, NetworkConfig } from '@/constants/abacus';
|
||||
import { type Candle, RESOLUTION_MAP } from '@/constants/candles';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
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';
|
||||
@@ -33,8 +38,9 @@ const useDydxClientContext = () => {
|
||||
// ------ Network ------ //
|
||||
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
|
||||
|
||||
const [networkConfig, setNetworkConfig] = useState<Partial<NetworkConfig>>();
|
||||
const [networkConfig, setNetworkConfig] = useState<NetworkConfig>();
|
||||
|
||||
useEffect(() => {
|
||||
const onConnectNetwork = (event: ConnectNetworkEvent) => setNetworkConfig(event.detail);
|
||||
@@ -54,15 +60,22 @@ const useDydxClientContext = () => {
|
||||
if (
|
||||
networkConfig?.chainId &&
|
||||
networkConfig?.indexerUrl &&
|
||||
networkConfig?.indexerSocketUrl &&
|
||||
networkConfig?.websocketUrl &&
|
||||
networkConfig?.validatorUrl
|
||||
) {
|
||||
try {
|
||||
const initializedClient = await CompositeClient.connect(
|
||||
new Network(
|
||||
selectedNetwork,
|
||||
new IndexerConfig(networkConfig.indexerUrl, networkConfig.indexerSocketUrl),
|
||||
new ValidatorConfig(networkConfig.validatorUrl, networkConfig.chainId, {
|
||||
new IndexerConfig(networkConfig.indexerUrl, networkConfig.websocketUrl),
|
||||
new ValidatorConfig(networkConfig.validatorUrl, networkConfig.chainId,
|
||||
{
|
||||
USDC_DENOM: tokensConfigs[DydxChainAsset.USDC].denom,
|
||||
USDC_DECIMALS: tokensConfigs[DydxChainAsset.USDC].decimals,
|
||||
USDC_GAS_DENOM: tokensConfigs[DydxChainAsset.USDC].gasDenom,
|
||||
CHAINTOKEN_DENOM: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
|
||||
CHAINTOKEN_DECIMALS: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
|
||||
}, {
|
||||
broadcastPollIntervalMs: 3_000,
|
||||
broadcastTimeoutMs: 60_000,
|
||||
})
|
||||
@@ -178,7 +191,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
|
||||
@@ -186,11 +219,13 @@ const useDydxClientContext = () => {
|
||||
networkConfig,
|
||||
compositeClient,
|
||||
faucetClient,
|
||||
isConnected: !!compositeClient,
|
||||
|
||||
// Wallet Methods
|
||||
getWalletFromEvmSignature,
|
||||
|
||||
// Public Methods
|
||||
getCandlesForDatafeed,
|
||||
screenAddresses,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ 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,6 +19,7 @@ export const useInitializePage = () => {
|
||||
const [localStorageNetwork] = useLocalStorage<DydxNetwork>({
|
||||
key: LocalStorageKey.SelectedNetwork,
|
||||
defaultValue: DEFAULT_APP_ENVIRONMENT,
|
||||
validateFn: validateAgainstAvailableEnvironments,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -4,15 +4,14 @@ import { useSelector, shallowEqual, useDispatch } from 'react-redux';
|
||||
import { groupBy } from 'lodash';
|
||||
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { AbacusOrderStatus, ORDER_SIDES, ORDER_STATUS_STRINGS } from '@/constants/abacus';
|
||||
import { AbacusOrderStatus, ORDER_SIDES } from '@/constants/abacus';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { STRING_KEYS, StringKey } from '@/constants/localization';
|
||||
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
|
||||
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade';
|
||||
import { ORDER_SIDE_STRINGS } from '@/constants/trade';
|
||||
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { AlertMessage } from '@/components/AlertMessage';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { TransferStatusToast } from '@/views/TransferStatus';
|
||||
@@ -69,11 +68,11 @@ export const notificationTypes = [
|
||||
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
|
||||
),
|
||||
title: `${stringGetter({
|
||||
key: TRADE_TYPE_STRINGS[order.type.rawValue as TradeTypes].tradeTypeKey,
|
||||
key: order.resources.typeStringKey as StringKey,
|
||||
})} ${
|
||||
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0
|
||||
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
|
||||
: stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] })
|
||||
: stringGetter({ key: order.resources.statusStringKey as StringKey })
|
||||
}`,
|
||||
description: `${stringGetter({
|
||||
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
|
||||
@@ -91,18 +90,34 @@ export const notificationTypes = [
|
||||
}, [orderIds]);
|
||||
},
|
||||
|
||||
useNotificationAction: () => {
|
||||
const dispatch = useDispatch();
|
||||
// useNotificationAction: () => {
|
||||
// const dispatch = useDispatch();
|
||||
// const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
|
||||
// const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
|
||||
|
||||
return (orderId) => {
|
||||
dispatch(
|
||||
openDialog({
|
||||
type: DialogTypes.OrderDetails,
|
||||
dialogProps: { orderId },
|
||||
})
|
||||
);
|
||||
};
|
||||
},
|
||||
// const fills = useSelector(getSubaccountFills, shallowEqual) || [];
|
||||
// const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
|
||||
|
||||
// return (id) => {
|
||||
// if (ordersByOrderId[id]) {
|
||||
// dispatch(
|
||||
// openDialog({
|
||||
// type: DialogTypes.OrderDetails,
|
||||
// dialogProps: { orderId: id },
|
||||
// })
|
||||
// );
|
||||
// } else if (fillsByOrderId[id]) {
|
||||
// const fillId = fillsByOrderId[id][0].id;
|
||||
|
||||
// dispatch(
|
||||
// openDialog({
|
||||
// type: DialogTypes.FillDetails,
|
||||
// dialogProps: { fillId },
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
// },
|
||||
} as NotificationTypeConfig<string, [string, number]>,
|
||||
{
|
||||
type: NotificationType.SquidTransfer,
|
||||
@@ -110,18 +125,18 @@ export const notificationTypes = [
|
||||
const stringGetter = useStringGetter();
|
||||
const { transferNotifications } = useLocalNotifications();
|
||||
|
||||
const getTitleStringKey = useCallback((type: 'deposit' | 'withdraw', finished: boolean) => {
|
||||
const getTitleStringKey = useCallback((type: 'deposit' | 'withdrawal', finished: boolean) => {
|
||||
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
|
||||
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
|
||||
if (type === 'withdraw' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
|
||||
if (type === 'withdrawal' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
|
||||
return STRING_KEYS.WITHDRAW;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
for (const transfer of transferNotifications) {
|
||||
const { toChainId, status, txHash, toAmount } = transfer;
|
||||
const { fromChainId, status, txHash, toAmount } = transfer;
|
||||
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
||||
const type = toChainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdraw';
|
||||
const type = fromChainId === TESTNET_CHAIN_ID ? 'withdrawal' : 'deposit';
|
||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
||||
|
||||
@@ -138,7 +153,8 @@ export const notificationTypes = [
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||
params: {
|
||||
ERROR_MESSAGE: error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
|
||||
ERROR_MESSAGE:
|
||||
error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
|
||||
},
|
||||
})}
|
||||
</Styled.ErrorMessage>
|
||||
@@ -154,6 +170,7 @@ export const notificationTypes = [
|
||||
description: description,
|
||||
customContent: (
|
||||
<TransferStatusToast
|
||||
type={type}
|
||||
toAmount={transfer.toAmount}
|
||||
triggeredAt={transfer.triggeredAt}
|
||||
status={transfer.status}
|
||||
@@ -162,7 +179,7 @@ export const notificationTypes = [
|
||||
customMenuContent: !finished && (
|
||||
<div>
|
||||
{description}
|
||||
<TransferStatusSteps status={transfer.status} />
|
||||
<TransferStatusSteps status={transfer.status} type={type} />
|
||||
</div>
|
||||
),
|
||||
toastSensitivity: 'foreground',
|
||||
@@ -181,8 +198,8 @@ Styled.TransferText = styled.span`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5ch;
|
||||
`
|
||||
`;
|
||||
|
||||
Styled.ErrorMessage = styled.div`
|
||||
max-width: 13rem;
|
||||
`;
|
||||
`;
|
||||
|
||||
@@ -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);
|
||||
@@ -2,13 +2,15 @@ import { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
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 { validateAgainstAvailableEnvironments } from '@/lib/network';
|
||||
|
||||
export const useSelectedNetwork = (): {
|
||||
switchNetwork: (network: DydxNetwork) => void;
|
||||
selectedNetwork: DydxNetwork;
|
||||
@@ -20,6 +22,7 @@ export const useSelectedNetwork = (): {
|
||||
const [, setLocalStorageNetwork] = useLocalStorage<DydxNetwork>({
|
||||
key: LocalStorageKey.SelectedNetwork,
|
||||
defaultValue: DEFAULT_APP_ENVIRONMENT,
|
||||
validateFn: validateAgainstAvailableEnvironments,
|
||||
});
|
||||
|
||||
const switchNetwork = useCallback(
|
||||
|
||||
@@ -3,15 +3,10 @@ import { shallowEqual, useSelector, useDispatch } from 'react-redux';
|
||||
import type { Nullable } from '@dydxprotocol/v4-abacus';
|
||||
import Long from 'long';
|
||||
import type { IndexedTx } from '@cosmjs/stargate';
|
||||
import type { EncodeObject, Coin } from '@cosmjs/proto-signing';
|
||||
import type { EncodeObject } from '@cosmjs/proto-signing';
|
||||
import { Method } from '@cosmjs/tendermint-rpc';
|
||||
|
||||
import {
|
||||
type LocalWallet,
|
||||
SubaccountClient,
|
||||
DYDX_DENOM,
|
||||
USDC_DENOM,
|
||||
} from '@dydxprotocol/v4-client-js';
|
||||
import { type LocalWallet, SubaccountClient } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import type {
|
||||
AccountBalance,
|
||||
@@ -34,9 +29,9 @@ import { MustBigNumber } from '@/lib/numbers';
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { useAccounts } from './useAccounts';
|
||||
import { useTokenConfigs } from './useTokenConfigs';
|
||||
import { useDydxClient } from './useDydxClient';
|
||||
|
||||
|
||||
type SubaccountContextType = ReturnType<typeof useSubaccountContext>;
|
||||
const SubaccountContext = createContext<SubaccountContextType>({} as SubaccountContextType);
|
||||
SubaccountContext.displayName = 'Subaccount';
|
||||
@@ -53,6 +48,7 @@ export const useSubaccount = () => useContext(SubaccountContext);
|
||||
|
||||
export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWallet }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { usdcDenom } = useTokenConfigs();
|
||||
const { compositeClient, faucetClient } = useDydxClient();
|
||||
|
||||
const { getFaucetFunds } = useMemo(
|
||||
@@ -92,7 +88,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
}: {
|
||||
subaccountClient: SubaccountClient;
|
||||
amount: number;
|
||||
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount),
|
||||
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount.toString()),
|
||||
|
||||
transferFromSubaccountToAddress: async ({
|
||||
subaccountClient,
|
||||
@@ -135,12 +131,21 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
amount: number;
|
||||
recipient: string;
|
||||
}) =>
|
||||
await compositeClient?.validatorClient.post.sendToken(
|
||||
subaccountClient,
|
||||
recipient,
|
||||
DYDX_DENOM,
|
||||
Long.fromNumber(amount * QUANTUM_MULTIPLIER),
|
||||
await compositeClient?.validatorClient.post.send(
|
||||
subaccountClient.wallet,
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const msg = compositeClient?.sendTokenMessage(
|
||||
subaccountClient.wallet,
|
||||
amount.toString(),
|
||||
recipient,
|
||||
);
|
||||
|
||||
resolve([msg]);
|
||||
}),
|
||||
false,
|
||||
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
||||
undefined,
|
||||
Method.BroadcastTxCommit
|
||||
),
|
||||
|
||||
@@ -157,7 +162,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
|
||||
const transaction = JSON.parse(payload);
|
||||
|
||||
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount);
|
||||
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount.toString());
|
||||
const ibcMsg: EncodeObject = {
|
||||
typeUrl: transaction.msgTypeUrl,
|
||||
value: transaction.msg,
|
||||
@@ -211,7 +216,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
);
|
||||
|
||||
const balances = useSelector(getBalances, shallowEqual);
|
||||
const usdcCoinBalance = balances?.[USDC_DENOM];
|
||||
const usdcCoinBalance = balances?.[usdcDenom];
|
||||
|
||||
useEffect(() => {
|
||||
if (usdcCoinBalance) {
|
||||
@@ -248,8 +253,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
if (!subaccountClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (await (coinDenom === USDC_DENOM
|
||||
return (await (coinDenom === usdcDenom
|
||||
? transferFromSubaccountToAddress
|
||||
: transferNativeToken)({ subaccountClient, amount, recipient })) as IndexedTx;
|
||||
},
|
||||
@@ -275,6 +279,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
await getFaucetFunds({ dydxAddress, subaccountNumber });
|
||||
} catch (error) {
|
||||
log('useSubaccount/getFaucetFunds', error);
|
||||
throw error;
|
||||
}
|
||||
}, [dydxAddress, getFaucetFunds, subaccountNumber]);
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import { useSelectedNetwork } from '@/hooks';
|
||||
|
||||
export const useTokenConfigs = (): {
|
||||
tokensConfigs: {
|
||||
[DydxChainAsset.USDC]: {
|
||||
denom: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
gasDenom?: string;
|
||||
},
|
||||
[DydxChainAsset.CHAINTOKEN]: {
|
||||
denom: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
gasDenom?: string;
|
||||
},
|
||||
};
|
||||
usdcDenom: string;
|
||||
usdcLabel: string;
|
||||
chainTokenDenom: string;
|
||||
chainTokenLabel: string;
|
||||
} => {
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
|
||||
|
||||
return {
|
||||
tokensConfigs,
|
||||
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
|
||||
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
|
||||
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
|
||||
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
|
||||
import {
|
||||
type DydxAddress,
|
||||
@@ -28,6 +30,8 @@ import {
|
||||
WalletType as CosmosWalletType,
|
||||
} from 'graz';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
import { resolveWagmiConnector } from '@/lib/wagmi';
|
||||
import { getWalletConnection, parseWalletError } from '@/lib/wallet';
|
||||
import { log } from '@/lib/telemetry';
|
||||
@@ -84,6 +88,9 @@ export const useWalletConnection = () => {
|
||||
|
||||
// Wallet connection
|
||||
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
|
||||
|
||||
const { connectAsync: connectWagmi } =
|
||||
walletType && walletConnectionType
|
||||
? useConnectWagmi({
|
||||
@@ -92,6 +99,7 @@ export const useWalletConnection = () => {
|
||||
walletConnection: {
|
||||
type: walletConnectionType,
|
||||
},
|
||||
walletConnectConfig,
|
||||
}),
|
||||
})
|
||||
: useConnectWagmi();
|
||||
@@ -127,6 +135,7 @@ export const useWalletConnection = () => {
|
||||
connector: resolveWagmiConnector({
|
||||
walletType,
|
||||
walletConnection,
|
||||
walletConnectConfig,
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -217,7 +226,7 @@ export const useWalletConnection = () => {
|
||||
evmAddressWagmi,
|
||||
signerWagmi,
|
||||
publicClientWagmi,
|
||||
|
||||
|
||||
// Wallet connection (Cosmos)
|
||||
dydxAddress,
|
||||
dydxAddressGraz,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.3811 13.235C11.9338 13.0512 13.4593 12.6847 14.9261 12.1433C13.6839 10.7673 12.9975 8.97875 13.0003 7.125V6.54167V6.5C13.0003 5.17392 12.4735 3.90215 11.5358 2.96447C10.5981 2.02678 9.32636 1.5 8.00028 1.5C6.67419 1.5 5.40242 2.02678 4.46474 2.96447C3.52706 3.90215 3.00028 5.17392 3.00028 6.5V7.125C3.00279 8.97886 2.3161 10.7675 1.07361 12.1433C2.51778 12.6767 4.04027 13.0475 5.61944 13.235M10.3811 13.235C8.79943 13.4226 7.20112 13.4226 5.61944 13.235M10.3811 13.235C10.5012 13.6099 10.5311 14.0078 10.4683 14.3964C10.4055 14.785 10.2518 15.1533 10.0198 15.4713C9.78772 15.7893 9.48386 16.048 9.13293 16.2263C8.78201 16.4047 8.39392 16.4976 8.00028 16.4976C7.60663 16.4976 7.21854 16.4047 6.86762 16.2263C6.51669 16.048 6.21283 15.7893 5.98079 15.4713C5.74875 15.1533 5.59508 14.785 5.53229 14.3964C5.46949 14.0078 5.49936 13.6099 5.61944 13.235" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 24"><path fill="currentColor" d="M11 0C8.5 0 6.101.948 4.333 2.636 2.565 4.324 1.57 6.613 1.57 9v5.379l-1.11 1.06c-.22.21-.37.477-.43.768-.061.291-.03.593.089.867s.32.508.578.673c.259.165.562.253.873.253h18.86c.31 0 .614-.088.873-.253.258-.165.46-.399.578-.673.12-.274.15-.576.09-.867a1.48 1.48 0 0 0-.43-.768l-1.111-1.06V9c0-2.387-.993-4.676-2.762-6.364C15.9.948 13.501 0 11 0Zm0 24a4.83 4.83 0 0 1-3.334-1.318 4.399 4.399 0 0 1-1.38-3.182h9.428a4.399 4.399 0 0 1-1.38 3.182A4.83 4.83 0 0 1 11 24Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 24"><path fill="currentColor" d="M11 0C8.5 0 6.101.948 4.333 2.636 2.565 4.324 1.57 6.613 1.57 9v5.379l-1.11 1.06c-.22.21-.37.477-.43.768-.061.291-.03.593.089.867s.32.508.578.673c.259.165.562.253.873.253h18.86c.31 0 .614-.088.873-.253.258-.165.46-.399.578-.673.12-.274.15-.576.09-.867a1.48 1.48 0 0 0-.43-.768l-1.111-1.06V9c0-2.387-.993-4.676-2.762-6.364C15.9.948 13.501 0 11 0Zm0 24a4.83 4.83 0 0 1-3.334-1.318 4.399 4.399 0 0 1-1.38-3.182h9.428a4.399 4.399 0 0 1-1.38 3.182A4.83 4.83 0 0 1 11 24Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 563 B After Width: | Height: | Size: 564 B |
@@ -2,6 +2,7 @@ export { default as AddressConnectorIcon } from './address-connector.svg';
|
||||
export { default as ArrowIcon } from './arrow.svg';
|
||||
export { default as Bar3Icon } from './bar3.svg';
|
||||
export { default as BellIcon } from './bell.svg';
|
||||
export { default as BellStrokeIcon } from './bell-stroke.svg';
|
||||
export { default as BoxCloseIcon } from './box-close.svg';
|
||||
export { default as CalculatorIcon } from './calculator.svg';
|
||||
export { default as CaretIcon } from './caret-down.svg';
|
||||
|
||||
@@ -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} />,
|
||||
|
||||
@@ -6,8 +6,9 @@ import { ButtonShape } from '@/constants/buttons';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import { LogoShortIcon, BellIcon } from '@/icons';
|
||||
import { LogoShortIcon, BellStrokeIcon } from '@/icons';
|
||||
|
||||
import { useTokenConfigs, useStringGetter } from '@/hooks';
|
||||
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
@@ -27,6 +28,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
||||
export const HeaderDesktop = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
const dispatch = useDispatch();
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
@@ -42,6 +44,11 @@ export const HeaderDesktop = () => {
|
||||
label: stringGetter({ key: STRING_KEYS.TRADE }),
|
||||
href: AppRoute.Trade,
|
||||
},
|
||||
{
|
||||
value: chainTokenLabel,
|
||||
label: chainTokenLabel,
|
||||
href: `/${chainTokenLabel}`,
|
||||
},
|
||||
{
|
||||
value: 'PORTFOLIO',
|
||||
label: stringGetter({ key: STRING_KEYS.PORTFOLIO }),
|
||||
@@ -73,13 +80,13 @@ export const HeaderDesktop = () => {
|
||||
value: 'TERMS_OF_USE',
|
||||
slotBefore: <Icon iconName={IconName.File} />,
|
||||
label: stringGetter({ key: STRING_KEYS.TERMS_OF_USE }),
|
||||
href: 'https://dydx.exchange/terms',
|
||||
href: AppRoute.Terms,
|
||||
},
|
||||
{
|
||||
value: 'PRIVACY_POLICY',
|
||||
slotBefore: <Icon iconName={IconName.Privacy} />,
|
||||
label: stringGetter({ key: STRING_KEYS.PRIVACY_POLICY }),
|
||||
href: 'https://dydx.exchange/privacy',
|
||||
href: AppRoute.Privacy,
|
||||
},
|
||||
{
|
||||
value: 'HELP',
|
||||
@@ -126,7 +133,9 @@ export const HeaderDesktop = () => {
|
||||
<VerticalSeparator />
|
||||
|
||||
<NotificationsMenu
|
||||
slotTrigger={<Styled.IconButton shape={ButtonShape.Rectangle} iconComponent={BellIcon} />}
|
||||
slotTrigger={
|
||||
<Styled.IconButton shape={ButtonShape.Rectangle} iconComponent={BellStrokeIcon} />
|
||||
}
|
||||
/>
|
||||
|
||||
<VerticalSeparator />
|
||||
@@ -160,7 +169,7 @@ Styled.Header = styled.header`
|
||||
)
|
||||
var(--border-width) 1fr var(--border-width) auto;
|
||||
|
||||
font-size: 0.9375em;
|
||||
font-size: 0.9375rem;
|
||||
|
||||
:before {
|
||||
backdrop-filter: blur(10px);
|
||||
@@ -209,7 +218,8 @@ Styled.NavAfter = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.IconButton = styled(IconButton)`
|
||||
Styled.IconButton = styled(IconButton)<{ size?: string }>`
|
||||
${headerMixins.button}
|
||||
--button-border: none;
|
||||
--button-icon-size: 1rem;
|
||||
`;
|
||||
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
OrderSide,
|
||||
OrderTimeInForce,
|
||||
OrderExecution,
|
||||
DYDX_DENOM,
|
||||
GAS_PRICE_DYDX_DENOM,
|
||||
} from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import {
|
||||
@@ -61,22 +59,42 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
}
|
||||
|
||||
async connectNetwork(
|
||||
indexerUrl: string,
|
||||
indexerSocketUrl: string,
|
||||
validatorUrl: string,
|
||||
chainId: string,
|
||||
faucetUrl: Nullable<string> | undefined,
|
||||
paramsInJson: Nullable<string>,
|
||||
callback: (p0: Nullable<string>) => void
|
||||
): Promise<void> {
|
||||
try {
|
||||
const parsedParams = paramsInJson ? JSON.parse(paramsInJson) : {};
|
||||
const {
|
||||
indexerUrl,
|
||||
websocketUrl,
|
||||
validatorUrl,
|
||||
chainId,
|
||||
USDC_DENOM,
|
||||
USDC_DECIMALS,
|
||||
USDC_GAS_DENOM,
|
||||
CHAINTOKEN_DENOM,
|
||||
CHAINTOKEN_DECIMALS,
|
||||
} = parsedParams;
|
||||
|
||||
const compositeClient = await CompositeClient.connect(
|
||||
new Network(
|
||||
chainId,
|
||||
new IndexerConfig(indexerUrl, indexerSocketUrl),
|
||||
new ValidatorConfig(validatorUrl, chainId, {
|
||||
broadcastPollIntervalMs: 3_000,
|
||||
broadcastTimeoutMs: 60_000,
|
||||
})
|
||||
new IndexerConfig(indexerUrl, websocketUrl),
|
||||
new ValidatorConfig(
|
||||
validatorUrl,
|
||||
chainId,
|
||||
{
|
||||
USDC_DENOM,
|
||||
USDC_DECIMALS,
|
||||
USDC_GAS_DENOM,
|
||||
CHAINTOKEN_DENOM,
|
||||
CHAINTOKEN_DECIMALS,
|
||||
},
|
||||
{
|
||||
broadcastPollIntervalMs: 3_000,
|
||||
broadcastTimeoutMs: 60_000,
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -84,13 +102,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
|
||||
// Dispatch custom event to notify other parts of the app that the network has been connected
|
||||
const customEvent = new CustomEvent('abacus:connectNetwork', {
|
||||
detail: {
|
||||
indexerUrl,
|
||||
indexerSocketUrl,
|
||||
validatorUrl,
|
||||
chainId,
|
||||
faucetUrl,
|
||||
},
|
||||
detail: parsedParams,
|
||||
});
|
||||
|
||||
globalThis.dispatchEvent(customEvent);
|
||||
@@ -99,9 +111,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
this.store?.dispatch(
|
||||
openDialog({ type: DialogTypes.ExchangeOffline, dialogProps: { preventClose: true } })
|
||||
);
|
||||
|
||||
log('DydxChainTransactions/connectNetwork', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,13 +223,13 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
params ?? {};
|
||||
|
||||
try {
|
||||
const tx = await this.compositeClient?.cancelOrder(
|
||||
const tx = await this.compositeClient?.cancelRawOrder(
|
||||
new SubaccountClient(this.localWallet, subaccountNumber),
|
||||
clientId,
|
||||
orderFlags,
|
||||
clobPairId,
|
||||
goodTilBlock ?? undefined,
|
||||
goodTilBlockTime ?? undefined
|
||||
goodTilBlock || undefined,
|
||||
goodTilBlockTime || undefined,
|
||||
);
|
||||
|
||||
const parsedTx = this.parseToPrimitives(tx);
|
||||
@@ -251,7 +261,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount);
|
||||
|
||||
resolve([msg]);
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const parsedTx = this.parseToPrimitives(tx);
|
||||
@@ -266,7 +276,9 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
async simulateTransferNativeTokenTransaction(params: HumanReadableTransferPayload): Promise<string> {
|
||||
async simulateTransferNativeTokenTransaction(
|
||||
params: HumanReadableTransferPayload
|
||||
): Promise<string> {
|
||||
if (!this.compositeClient || !this.localWallet) {
|
||||
throw new Error('Missing compositeClient or localWallet');
|
||||
}
|
||||
@@ -280,16 +292,18 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
this.localWallet,
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const msg = compositeClient?.validatorClient.post.composer.composeMsgSendToken(
|
||||
subaccountClient.address,
|
||||
if (!this.localWallet) {
|
||||
throw new Error('Missing compositeClient or localWallet');
|
||||
}
|
||||
const msg = compositeClient?.sendTokenMessage(
|
||||
this.localWallet,
|
||||
amount,
|
||||
recipient,
|
||||
DYDX_DENOM,
|
||||
Long.fromNumber(amount * QUANTUM_MULTIPLIER)
|
||||
);
|
||||
|
||||
resolve([msg]);
|
||||
}),
|
||||
GAS_PRICE_DYDX_DENOM,
|
||||
this.compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
||||
);
|
||||
|
||||
const parsedTx = this.parseToPrimitives(tx);
|
||||
@@ -397,9 +411,10 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
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 accountBalances =
|
||||
await this.compositeClient?.validatorClient.get.getAccountBalances(
|
||||
this.localWallet.address
|
||||
);
|
||||
const parsedAccountBalances = this.parseToPrimitives(accountBalances);
|
||||
callback(JSON.stringify(parsedAccountBalances));
|
||||
break;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -20,10 +20,12 @@ import type { RootStore } from '@/state/_store';
|
||||
|
||||
import {
|
||||
setBalances,
|
||||
setStakingBalances,
|
||||
setFills,
|
||||
setFundingPayments,
|
||||
setHistoricalPnl,
|
||||
setLatestOrder,
|
||||
setRestrictionType,
|
||||
setSubaccount,
|
||||
setTransfers,
|
||||
setWallet,
|
||||
@@ -34,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;
|
||||
@@ -82,6 +85,13 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
|
||||
}
|
||||
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)) {
|
||||
@@ -100,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)) {
|
||||
|
||||
@@ -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,7 +36,15 @@ import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet';
|
||||
import { InjectedConnector } from 'wagmi/connectors/injected';
|
||||
import { MetaMaskConnector } from 'wagmi/connectors/metaMask';
|
||||
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect';
|
||||
import { WalletConnectLegacyConnector } from 'wagmi/connectors/walletConnectLegacy';
|
||||
|
||||
import {
|
||||
type WalletConnection,
|
||||
WalletConnectionType,
|
||||
type WalletType,
|
||||
walletConnectionTypes,
|
||||
wallets,
|
||||
WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
// Config
|
||||
|
||||
@@ -87,53 +95,46 @@ const injectedConnectorOptions = {
|
||||
},
|
||||
};
|
||||
|
||||
const walletconnect1ConnectorOptions = {
|
||||
chains,
|
||||
options: {
|
||||
bridge: import.meta.env.VITE_WALLETCONNECT1_BRIDGE,
|
||||
qrcode: true,
|
||||
},
|
||||
type WalletConnectConfig = {
|
||||
client: {
|
||||
name: string;
|
||||
description: string;
|
||||
iconUrl: string;
|
||||
};
|
||||
v2: {
|
||||
projectId: string;
|
||||
};
|
||||
};
|
||||
|
||||
const walletconnect2ConnectorOptions: ConstructorParameters<typeof WalletConnectConnector>[0] = {
|
||||
const getWalletconnect2ConnectorOptions = (
|
||||
config: WalletConnectConfig
|
||||
): ConstructorParameters<typeof WalletConnectConnector>[0] => ({
|
||||
chains,
|
||||
options: {
|
||||
projectId: import.meta.env.VITE_WALLETCONNECT2_PROJECT_ID,
|
||||
projectId: config.v2.projectId,
|
||||
metadata: {
|
||||
// TODO: update to local URLs/images
|
||||
name: 'dYdX',
|
||||
description: '',
|
||||
url: 'https://trade.dydx.exchange',
|
||||
icons: ['https://trade.dydx.exchange/cbw-image.png'],
|
||||
name: config.client.name,
|
||||
description: config.client.description,
|
||||
url: import.meta.env.VITE_BASE_URL,
|
||||
icons: [config.client.iconUrl],
|
||||
},
|
||||
showQrModal: true,
|
||||
qrModalOptions: {
|
||||
themeMode: 'dark' as const,
|
||||
themeVariables: {
|
||||
'--w3m-accent-color': '#5973fe',
|
||||
'--w3m-background-color': '#5973fe',
|
||||
'--w3m-color-bg-1': 'var(--color-layer-3)',
|
||||
'--w3m-color-bg-2': 'var(--color-layer-4)',
|
||||
'--w3m-color-bg-3': 'var(--color-layer-5)',
|
||||
'--w3m-font-family': 'var(--fontFamily-base)',
|
||||
'--w3m-font-feature-settings': 'none',
|
||||
'--w3m-overlay-backdrop-filter': 'blur(6px)',
|
||||
// '--w3m-logo-image-url': 'https://trade.dydx.exchange/cbw-image.png',
|
||||
'--wcm-accent-color': '#5973fe',
|
||||
'--wcm-font-family': 'var(--fontFamily-base)',
|
||||
},
|
||||
enableExplorer: true,
|
||||
explorerAllowList: [],
|
||||
explorerDenyList: [],
|
||||
chainImages: {},
|
||||
explorerRecommendedWalletIds: WALLET_CONNECT_EXPLORER_RECOMMENDED_IDS,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const connectors = [
|
||||
const getConnectors = (walletConnectConfig: WalletConnectConfig) => [
|
||||
new MetaMaskConnector({
|
||||
chains,
|
||||
options: {
|
||||
shimDisconnect: true,
|
||||
shimChainChangedDisconnect: false,
|
||||
},
|
||||
}),
|
||||
new CoinbaseWalletConnector({
|
||||
@@ -143,8 +144,7 @@ const connectors = [
|
||||
reloadOnDisconnect: false,
|
||||
},
|
||||
}),
|
||||
new WalletConnectLegacyConnector(walletconnect1ConnectorOptions),
|
||||
new WalletConnectConnector(walletconnect2ConnectorOptions),
|
||||
new WalletConnectConnector(getWalletconnect2ConnectorOptions(walletConnectConfig)),
|
||||
new InjectedConnector(injectedConnectorOptions),
|
||||
];
|
||||
|
||||
@@ -166,58 +166,41 @@ 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({
|
||||
const createWalletConnect2ConnectorWithId = (
|
||||
walletconnectId: string,
|
||||
walletConnectConfig: WalletConnectConfig
|
||||
) => {
|
||||
const walletconnect2ConnectorOptions = getWalletconnect2ConnectorOptions(walletConnectConfig);
|
||||
return new WalletConnectConnector({
|
||||
...walletconnect2ConnectorOptions,
|
||||
options: {
|
||||
...walletconnect2ConnectorOptions.options,
|
||||
qrModalOptions: {
|
||||
...walletconnect2ConnectorOptions.options.qrModalOptions,
|
||||
explorerRecommendedWalletIds: [walletconnect2Id],
|
||||
explorerRecommendedWalletIds: [walletconnectId],
|
||||
explorerExcludedWalletIds: 'ALL',
|
||||
chainImages: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Custom connector from wallet selection
|
||||
import {
|
||||
type WalletConnection,
|
||||
WalletConnectionType,
|
||||
type WalletType,
|
||||
walletConnectionTypes,
|
||||
wallets,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
export const resolveWagmiConnector = ({
|
||||
walletType,
|
||||
walletConnection,
|
||||
walletConnectConfig,
|
||||
}: {
|
||||
walletType: WalletType;
|
||||
walletConnection: WalletConnection;
|
||||
walletConnectConfig: WalletConnectConfig;
|
||||
}) => {
|
||||
const walletConfig = wallets[walletType];
|
||||
const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
|
||||
|
||||
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);
|
||||
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id, walletConnectConfig)
|
||||
: getConnectors(walletConnectConfig).find(({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId);
|
||||
};
|
||||
|
||||
@@ -45,12 +45,6 @@ export const getWalletConnection = ({
|
||||
break;
|
||||
}
|
||||
|
||||
case WalletConnectionType.WalletConnect1: {
|
||||
return {
|
||||
type: WalletConnectionType.WalletConnect1,
|
||||
};
|
||||
}
|
||||
|
||||
case WalletConnectionType.WalletConnect2: {
|
||||
return {
|
||||
type: WalletConnectionType.WalletConnect2,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { articleMixins } from '@/styles/articleMixins';
|
||||
|
||||
export const PrivacyPolicyPage = () => (
|
||||
<Styled.Article>
|
||||
<header>
|
||||
<h1>Privacy Policy</h1>
|
||||
</header>
|
||||
</Styled.Article>
|
||||
);
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Article = styled.article`
|
||||
${articleMixins.article}
|
||||
`;
|
||||
@@ -0,0 +1,17 @@
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { articleMixins } from '@/styles/articleMixins';
|
||||
|
||||
export const TermsOfUsePage = () => (
|
||||
<Styled.Article>
|
||||
<header>
|
||||
<h1>Terms of Use</h1>
|
||||
</header>
|
||||
</Styled.Article>
|
||||
);
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Article = styled.article`
|
||||
${articleMixins.article}
|
||||
`;
|
||||
@@ -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)} />}
|
||||
|
||||
@@ -3,7 +3,7 @@ import styled, { AnyStyledComponent } from 'styled-components';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { useAccountBalance, useAccounts, useStringGetter } from '@/hooks';
|
||||
import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
|
||||
|
||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
@@ -29,15 +29,16 @@ export const DYDXBalancePanel = () => {
|
||||
|
||||
const { walletType } = useAccounts();
|
||||
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
|
||||
const { nativeTokenBalance } = useAccountBalance();
|
||||
const { nativeTokenBalance, nativeStakingBalance } = useAccountBalance();
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
return (
|
||||
<Styled.TransfersCard
|
||||
<Panel
|
||||
slotHeader={
|
||||
<Styled.Header>
|
||||
<Styled.Title>
|
||||
{/* <AssetIcon symbol="DYDX" /> */}
|
||||
Dv4TNT
|
||||
{chainTokenLabel}
|
||||
</Styled.Title>
|
||||
<Styled.ReceiveAndTransferButtons>
|
||||
{!canAccountTrade ? (
|
||||
@@ -97,7 +98,7 @@ export const DYDXBalancePanel = () => {
|
||||
</Styled.Label>
|
||||
),
|
||||
|
||||
value: <Output type={OutputType.Asset} value={undefined} />,
|
||||
value: <Output type={OutputType.Asset} value={nativeStakingBalance} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -106,21 +107,23 @@ 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={chainTokenLabel}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</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 +177,14 @@ 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);
|
||||
|
||||
@@ -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);
|
||||
`;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
SubaccountTransfers,
|
||||
HistoricalPnlPeriods,
|
||||
SubAccountHistoricalPNLs,
|
||||
UsageRestriction,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { OnboardingGuard, OnboardingState } from '@/constants/account';
|
||||
@@ -21,33 +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;
|
||||
balances?: Record<string, AccountBalance>;
|
||||
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>({
|
||||
@@ -58,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({
|
||||
@@ -123,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)
|
||||
@@ -154,6 +176,9 @@ export const accountSlice = createSlice({
|
||||
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);
|
||||
},
|
||||
@@ -174,11 +199,13 @@ export const {
|
||||
setOnboardingGuard,
|
||||
setOnboardingState,
|
||||
setHistoricalPnl,
|
||||
setRestrictionType,
|
||||
setSubaccount,
|
||||
setWallet,
|
||||
viewedFills,
|
||||
viewedOrders,
|
||||
setBalances,
|
||||
setStakingBalances,
|
||||
addUncommittedOrderClientId,
|
||||
removeUncommittedOrderClientId,
|
||||
} = accountSlice.actions;
|
||||
|
||||
@@ -337,3 +337,18 @@ export const getUserStats = (state: RootState) => ({
|
||||
* @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;
|
||||
|
||||
@@ -2,10 +2,10 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import { AbacusApiState } from '@/constants/abacus';
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
|
||||
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;
|
||||
@@ -19,6 +19,7 @@ const initialState: AppState = {
|
||||
selectedNetwork: getLocalStorage({
|
||||
key: LocalStorageKey.SelectedNetwork,
|
||||
defaultValue: DEFAULT_APP_ENVIRONMENT,
|
||||
validateFn: validateAgainstAvailableEnvironments,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -23,39 +23,38 @@ export const dialogsSlice = createSlice({
|
||||
name: 'Dialogs',
|
||||
initialState,
|
||||
reducers: {
|
||||
addDialogToQueue: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => {
|
||||
const dialogQueue = state.dialogQueue;
|
||||
dialogQueue.push(action.payload);
|
||||
openDialog: (state: DialogsState, action: PayloadAction<DialogInfo<DialogTypes>>) => {
|
||||
if (state.activeDialog?.type === action.payload.type) return;
|
||||
|
||||
return {
|
||||
...state,
|
||||
dialogQueue,
|
||||
};
|
||||
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,
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
css,
|
||||
type FlattenInterpolation,
|
||||
type FlattenSimpleInterpolation,
|
||||
type ThemeProps,
|
||||
} from 'styled-components';
|
||||
|
||||
export const articleMixins: Record<
|
||||
string,
|
||||
FlattenSimpleInterpolation | FlattenInterpolation<ThemeProps<any>>
|
||||
> = {
|
||||
article: css`
|
||||
padding: 2rem;
|
||||
overflow-wrap: break-word;
|
||||
user-select: text;
|
||||
margin: 0 auto;
|
||||
width: 60rem;
|
||||
max-width: 100vw;
|
||||
|
||||
header {
|
||||
color: var(--color-text-0);
|
||||
margin-bottom: 2rem;
|
||||
|
||||
h1 {
|
||||
font: var(--font-extra-book);
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font: var(--font-large-book);
|
||||
color: var(--color-text-2);
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
h3,
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
`,
|
||||
};
|
||||
@@ -75,16 +75,14 @@ export const AccountInfoConnectedState = () => {
|
||||
<Styled.Header>
|
||||
<span>{stringGetter({ key: STRING_KEYS.ACCOUNT })}</span>
|
||||
<Styled.TransferButtons>
|
||||
{import.meta.env.MODE !== 'production' && (
|
||||
<Styled.Button
|
||||
state={{ isDisabled: !dydxAccounts }}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Withdraw }))}
|
||||
shape={ButtonShape.Pill}
|
||||
size={ButtonSize.XSmall}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.WITHDRAW })}
|
||||
</Styled.Button>
|
||||
)}
|
||||
<Styled.Button
|
||||
state={{ isDisabled: !dydxAccounts }}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Withdraw }))}
|
||||
shape={ButtonShape.Pill}
|
||||
size={ButtonSize.XSmall}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.WITHDRAW })}
|
||||
</Styled.Button>
|
||||
<Styled.Button
|
||||
state={{ isDisabled: !dydxAccounts }}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
|
||||
|
||||
@@ -139,11 +139,9 @@ export const MarketDetails: React.FC = () => {
|
||||
</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>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
import { Root, Trigger, Content } from '@radix-ui/react-collapsible';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
@@ -21,12 +20,14 @@ import { LoadingDots } from '@/components/Loading/LoadingDots';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
type: 'withdrawal' | 'deposit';
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
export const TransferStatusToast = ({
|
||||
type,
|
||||
toAmount,
|
||||
triggeredAt = Date.now(),
|
||||
status,
|
||||
@@ -38,11 +39,6 @@ export const TransferStatusToast = ({
|
||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
||||
|
||||
const type = useMemo(
|
||||
() => (status?.toChain?.chainData?.chainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal'),
|
||||
[status]
|
||||
);
|
||||
|
||||
const updateSecondsLeft = useCallback(() => {
|
||||
const fromChainEta = (status?.fromChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
const toChainEta = (status?.toChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
@@ -53,6 +49,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
|
||||
@@ -60,14 +65,14 @@ export const TransferStatusToast = ({
|
||||
side="bottom"
|
||||
slotReceipt={
|
||||
<Styled.Receipt>
|
||||
<TransferStatusSteps status={status} />
|
||||
<TransferStatusSteps status={status} type={type} />
|
||||
</Styled.Receipt>
|
||||
}
|
||||
>
|
||||
<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,7 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
@@ -15,6 +14,7 @@ import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
type ElementProps = {
|
||||
status?: StatusResponse;
|
||||
type: 'withdrawal' | 'deposit';
|
||||
};
|
||||
|
||||
enum TransferStatusStep {
|
||||
@@ -24,14 +24,13 @@ enum TransferStatusStep {
|
||||
Complete,
|
||||
}
|
||||
|
||||
export const TransferStatusSteps = ({ status }: ElementProps) => {
|
||||
export const TransferStatusSteps = ({ status, type }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const { currentStep, steps, type } = useMemo(() => {
|
||||
const { currentStep, steps } = useMemo(() => {
|
||||
const routeStatus = status?.routeStatus;
|
||||
const fromChain = status?.fromChain?.chainData?.chainId;
|
||||
const toChain = status?.toChain?.chainData?.chainId;
|
||||
const type = toChain === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal';
|
||||
|
||||
const steps = [
|
||||
{
|
||||
@@ -51,7 +50,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,
|
||||
@@ -74,6 +73,10 @@ export const TransferStatusSteps = ({ status }: ElementProps) => {
|
||||
currentStep = TransferStatusStep.FromChain;
|
||||
}
|
||||
|
||||
if (status?.squidTransactionStatus === 'success') {
|
||||
currentStep = TransferStatusStep.Complete;
|
||||
}
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
steps,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -32,12 +32,9 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
||||
>
|
||||
<Styled.Content>
|
||||
{showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />}
|
||||
{/* TODO TRCL-1693 - uncomment when deposit form is ready */}
|
||||
{import.meta.env.MODE !== 'production' && (
|
||||
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
|
||||
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
|
||||
</Styled.TextToggle>
|
||||
)}
|
||||
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
|
||||
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
|
||||
</Styled.TextToggle>
|
||||
</Styled.Content>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { AbacusApiStatus } from '@/constants/abacus';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { useApiState, useStringGetter } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
|
||||
@@ -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);
|
||||
`;
|
||||
@@ -37,7 +37,7 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { isMobile } = useBreakpoints();
|
||||
|
||||
const { walletType } = useAccounts();
|
||||
const { disconnect, walletType } = useAccounts();
|
||||
|
||||
const currentOnboardingStep = useSelector(calculateOnboardingStep);
|
||||
|
||||
@@ -45,10 +45,17 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
|
||||
if (!currentOnboardingStep) setIsOpen?.(false);
|
||||
}, [currentOnboardingStep]);
|
||||
|
||||
const setIsOpenFromDialog = (open: boolean) => {
|
||||
if (!open && currentOnboardingStep === OnboardingSteps.AcknowledgeTerms) {
|
||||
disconnect();
|
||||
}
|
||||
setIsOpen?.(open);
|
||||
}
|
||||
|
||||
return (
|
||||
<Styled.Dialog
|
||||
isOpen={Boolean(currentOnboardingStep)}
|
||||
setIsOpen={setIsOpen}
|
||||
setIsOpen={setIsOpenFromDialog}
|
||||
{...(currentOnboardingStep &&
|
||||
{
|
||||
[OnboardingSteps.ChooseWallet]: {
|
||||
@@ -87,10 +94,10 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
|
||||
title: stringGetter({ key: STRING_KEYS.ACKNOWLEDGE_TERMS }),
|
||||
children: (
|
||||
<Styled.Content>
|
||||
<AcknowledgeTerms />
|
||||
<AcknowledgeTerms onClose={() => setIsOpenFromDialog?.(false)} />
|
||||
</Styled.Content>
|
||||
),
|
||||
width: '20rem',
|
||||
width: '30rem',
|
||||
},
|
||||
[OnboardingSteps.DepositFunds]: {
|
||||
title: stringGetter({ key: STRING_KEYS.DEPOSIT }),
|
||||
|
||||
@@ -2,17 +2,21 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { useAccounts, useStringGetter } from '@/hooks';
|
||||
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ButtonAction } from '@/constants/buttons';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Button } from '@/components/Button';
|
||||
import { Link } from '@/components/Link';
|
||||
|
||||
type ElementProps = {
|
||||
onClose?: () => void;
|
||||
onContinue?: () => void;
|
||||
};
|
||||
|
||||
export const AcknowledgeTerms = ({ onContinue }: ElementProps) => {
|
||||
export const AcknowledgeTerms = ({ onClose, onContinue }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const { saveHasAcknowledgedTerms } = useAccounts();
|
||||
@@ -24,26 +28,40 @@ export const AcknowledgeTerms = ({ onContinue }: ElementProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
<p>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.LEGAL_UPDATES_DESCRIPTION,
|
||||
key: STRING_KEYS.TOS_TITLE,
|
||||
params: {
|
||||
TOU: (
|
||||
<Styled.Link href="https://dydx.exchange/v4-terms">
|
||||
TERMS_LINK: (
|
||||
<Styled.Link href={`/#${AppRoute.Terms}`}>
|
||||
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
|
||||
</Styled.Link>
|
||||
),
|
||||
PRIVACY_POLICY: (
|
||||
<Styled.Link href="https://dydx.exchange/privacy">
|
||||
PRIVACY_POLICY_LINK: (
|
||||
<Styled.Link href={`/#${AppRoute.Privacy}`}>
|
||||
{stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })}
|
||||
</Styled.Link>
|
||||
),
|
||||
},
|
||||
})}
|
||||
</span>
|
||||
<Button onClick={onAcknowledgement} action={ButtonAction.Primary}>
|
||||
{stringGetter({ key: STRING_KEYS.I_AGREE })}
|
||||
</Button>
|
||||
</p>
|
||||
<Styled.TOS>
|
||||
<ul>
|
||||
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE1 })}</li>
|
||||
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE2 })}</li>
|
||||
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE3 })}</li>
|
||||
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE4 })}</li>
|
||||
<li>{stringGetter({ key: STRING_KEYS.TOS_LINE5 })}</li>
|
||||
</ul>
|
||||
</Styled.TOS>
|
||||
<Styled.Row>
|
||||
<Styled.Button onClick={onClose} action={ButtonAction.Base}>
|
||||
{stringGetter({ key: STRING_KEYS.CLOSE })}
|
||||
</Styled.Button>
|
||||
<Styled.Button onClick={onAcknowledgement} action={ButtonAction.Primary}>
|
||||
{stringGetter({ key: STRING_KEYS.I_AGREE })}
|
||||
</Styled.Button>
|
||||
</Styled.Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -58,3 +76,23 @@ Styled.Link = styled(Link)`
|
||||
color: var(--color-accent);
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.TOS = styled.section`
|
||||
background-color: var(--color-layer-4);
|
||||
padding: 1rem 1rem 1rem 2rem;
|
||||
border-radius: 0.875rem;
|
||||
> ul {
|
||||
${layoutMixins.column};
|
||||
gap: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.Row = styled.div`
|
||||
${layoutMixins.row};
|
||||
|
||||
gap: 1rem;
|
||||
`;
|
||||
|
||||
Styled.Button = styled(Button)`
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AnalyticsEvent } from '@/constants/analytics';
|
||||
import { ButtonAction } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { DydxAddress, SIGN_TYPED_DATA } from '@/constants/wallets';
|
||||
import { DydxAddress, getSignTypedData } from '@/constants/wallets';
|
||||
|
||||
import { useAccounts, useBreakpoints, useDydxClient, useStringGetter } from '@/hooks';
|
||||
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
|
||||
@@ -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,
|
||||
@@ -263,7 +264,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.StatusCardsContainer = styled.div`
|
||||
display: grid;
|
||||
margin-top: 1rem;
|
||||
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 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;
|
||||
`;
|
||||
@@ -6,7 +6,7 @@ import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { useAccounts, useStringGetter } from '@/hooks';
|
||||
import { useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
|
||||
|
||||
import { AssetIcon } from '@/components/AssetIcon';
|
||||
import { CopyButton } from '@/components/CopyButton';
|
||||
@@ -24,9 +24,10 @@ type ElementProps = {
|
||||
setIsOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }: ElementProps) => {
|
||||
export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.CHAINTOKEN, setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { dydxAddress } = useAccounts();
|
||||
const { chainTokenLabel, usdcLabel } = useTokenConfigs();
|
||||
|
||||
const [asset, setAsset] = useState(selectedAsset);
|
||||
|
||||
@@ -35,16 +36,17 @@ export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }
|
||||
value: DydxChainAsset.USDC,
|
||||
label: (
|
||||
<Styled.InlineRow>
|
||||
<AssetIcon symbol="USDC" /> USDC
|
||||
<AssetIcon symbol="USDC" /> {usdcLabel}
|
||||
</Styled.InlineRow>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: DydxChainAsset.DYDX,
|
||||
value: DydxChainAsset.CHAINTOKEN,
|
||||
label: (
|
||||
<Styled.InlineRow>
|
||||
{/* TODO: conditionally display the native token assetIcon */}
|
||||
{/* <AssetIcon symbol="DYDX" /> */}
|
||||
Dv4TNT
|
||||
{chainTokenLabel}
|
||||
</Styled.InlineRow>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
`;
|
||||
@@ -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,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,10 +46,11 @@ 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 { sendSquidWithdraw } = useSubaccount();
|
||||
@@ -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({
|
||||
@@ -101,50 +115,85 @@ export const WithdrawForm = () => {
|
||||
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>) => {
|
||||
@@ -175,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,
|
||||
@@ -190,7 +238,6 @@ export const WithdrawForm = () => {
|
||||
|
||||
const onSelectToken = useCallback((token: TransferInputTokenResource) => {
|
||||
if (token) {
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
abacusStateManager.setTransferValue({
|
||||
field: TransferInputField.token,
|
||||
value: token.address,
|
||||
@@ -213,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()
|
||||
}
|
||||
@@ -222,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 });
|
||||
@@ -247,7 +299,15 @@ export const WithdrawForm = () => {
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [error, freeCollateralBN, chainIdStr, debouncedAmountBN, toToken]);
|
||||
}, [
|
||||
error,
|
||||
freeCollateralBN,
|
||||
chainIdStr,
|
||||
debouncedAmountBN,
|
||||
toToken,
|
||||
toAddress,
|
||||
sanctionedAddresses,
|
||||
]);
|
||||
|
||||
const isDisabled =
|
||||
!!errorMessage ||
|
||||
@@ -255,7 +315,8 @@ export const WithdrawForm = () => {
|
||||
!chainIdStr ||
|
||||
!toAddress ||
|
||||
debouncedAmountBN.isNaN() ||
|
||||
debouncedAmountBN.isZero();
|
||||
debouncedAmountBN.isZero() ||
|
||||
isLoading;
|
||||
|
||||
return (
|
||||
<Styled.Form onSubmit={onSubmit}>
|
||||
@@ -265,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 })}
|
||||
@@ -342,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();
|
||||
|
||||
// TODO: uncomment when we have a way to get token amount estimate from abacus
|
||||
// const { balance, queryStatus } = useAccountBalance({
|
||||
// addressOrDenom: withdrawToken?.address || undefined,
|
||||
// assetSymbol: withdrawToken?.symbol || undefined,
|
||||
// chainId: withdrawChain ? parseInt(withdrawChain) : undefined,
|
||||
// decimals: withdrawToken?.decimals || undefined,
|
||||
// isCosmosChain: false,
|
||||
// });
|
||||
|
||||
// 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,25 +153,19 @@ 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;
|
||||
|
||||
@@ -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,14 +29,15 @@ 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, type } = inputTradeData || {};
|
||||
|
||||
const { executionOptions, needsGoodUntil, needsPostOnly, needsReduceOnly, timeInForceOptions } =
|
||||
currentTradeFormConfig || {};
|
||||
|
||||
const { duration, unit } = goodUntil || {};
|
||||
const { duration, unit } = goodTil || {};
|
||||
|
||||
const needsExecution = executionOptions || needsPostOnly || needsReduceOnly;
|
||||
const hasTimeInForce = timeInForceOptions?.toArray()?.length;
|
||||
|
||||
return (
|
||||
<Styled.Collapsible
|
||||
@@ -47,7 +48,7 @@ export const AdvancedTradeOptions = () => {
|
||||
>
|
||||
<Styled.AdvancedInputsContainer>
|
||||
<Styled.AdvancedInputsRow needsGoodUntil={needsGoodUntil}>
|
||||
{timeInForceOptions?.toArray() && (
|
||||
{hasTimeInForce && (
|
||||
<Styled.SelectMenu
|
||||
value={timeInForce}
|
||||
onValueChange={(selectedTimeInForceOption: string) =>
|
||||
@@ -62,7 +63,7 @@ export const AdvancedTradeOptions = () => {
|
||||
<Styled.SelectItem
|
||||
key={type}
|
||||
value={type}
|
||||
label={stringGetter({ key: stringKey })}
|
||||
label={stringGetter({ key: stringKey as StringKey })}
|
||||
/>
|
||||
))}
|
||||
</Styled.SelectMenu>
|
||||
@@ -72,11 +73,13 @@ export const AdvancedTradeOptions = () => {
|
||||
id="trade-good-til-time"
|
||||
type={InputType.Number}
|
||||
decimals={INTEGER_DECIMALS}
|
||||
label={stringGetter({ key: STRING_KEYS.TIME })}
|
||||
label={stringGetter({
|
||||
key: hasTimeInForce ? STRING_KEYS.TIME : STRING_KEYS.GOOD_TIL_TIME,
|
||||
})}
|
||||
onChange={({ value }: NumberFormatValues) => {
|
||||
abacusStateManager.setTradeValue({
|
||||
value: Number(value),
|
||||
field: TradeInputField.goodUntilDuration,
|
||||
field: TradeInputField.goodTilDuration,
|
||||
});
|
||||
}}
|
||||
value={duration ?? ''}
|
||||
@@ -86,7 +89,7 @@ export const AdvancedTradeOptions = () => {
|
||||
onValueChange={(goodTilTimeTimescale: string) => {
|
||||
abacusStateManager.setTradeValue({
|
||||
value: goodTilTimeTimescale,
|
||||
field: TradeInputField.goodUntilUnit,
|
||||
field: TradeInputField.goodTilUnit,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -121,7 +124,7 @@ export const AdvancedTradeOptions = () => {
|
||||
<Styled.SelectItem
|
||||
key={type}
|
||||
value={type}
|
||||
label={stringGetter({ key: stringKey })}
|
||||
label={stringGetter({ key: stringKey as StringKey })}
|
||||
/>
|
||||
))}
|
||||
</Styled.SelectMenu>
|
||||
|
||||
@@ -3,8 +3,6 @@ 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';
|
||||
@@ -12,19 +10,18 @@ import { AlertType } from '@/constants/alerts';
|
||||
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||
import {
|
||||
DYDX_CHAIN_ASSET_COIN_DENOM,
|
||||
DYDX_CHAIN_ASSET_TAGS,
|
||||
DydxChainAsset,
|
||||
} from '@/constants/wallets';
|
||||
import { NumberSign } from '@/constants/numbers';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import {
|
||||
useAccountBalance,
|
||||
useAccounts,
|
||||
useDydxClient,
|
||||
useRestrictions,
|
||||
useSelectedNetwork,
|
||||
useStringGetter,
|
||||
useSubaccount,
|
||||
useTokenConfigs,
|
||||
} from '@/hooks';
|
||||
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
@@ -57,7 +54,7 @@ type TransferFormProps = {
|
||||
};
|
||||
|
||||
export const TransferForm = ({
|
||||
selectedAsset = DydxChainAsset.DYDX,
|
||||
selectedAsset = DydxChainAsset.CHAINTOKEN,
|
||||
onDone,
|
||||
className,
|
||||
}: TransferFormProps) => {
|
||||
@@ -72,12 +69,13 @@ export const TransferForm = ({
|
||||
const { transfer } = useSubaccount();
|
||||
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const { tokensConfigs, usdcLabel, chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
// User Input
|
||||
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 balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance;
|
||||
@@ -96,16 +94,6 @@ export const TransferForm = ({
|
||||
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,
|
||||
@@ -125,6 +113,21 @@ export const TransferForm = ({
|
||||
});
|
||||
}, [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 || !fee) return;
|
||||
setIsLoading(true);
|
||||
@@ -133,23 +136,54 @@ export const TransferForm = ({
|
||||
try {
|
||||
// Subtract fees from amount if sending native tokens
|
||||
const amountToTransfer = (
|
||||
asset === DydxChainAsset.DYDX ? amountBN.minus(fee) : amountBN
|
||||
asset === DydxChainAsset.CHAINTOKEN ? 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,
|
||||
tokensConfigs[asset].denom,
|
||||
);
|
||||
|
||||
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);
|
||||
@@ -184,16 +218,16 @@ export const TransferForm = ({
|
||||
value: DydxChainAsset.USDC,
|
||||
label: (
|
||||
<Styled.InlineRow>
|
||||
<AssetIcon symbol="USDC" /> {DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.USDC]}
|
||||
<AssetIcon symbol="USDC" /> {usdcLabel}
|
||||
</Styled.InlineRow>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: DydxChainAsset.DYDX,
|
||||
value: DydxChainAsset.CHAINTOKEN,
|
||||
label: (
|
||||
<Styled.InlineRow>
|
||||
{/* <AssetIcon symbol="DYDX" /> */}
|
||||
{DYDX_CHAIN_ASSET_TAGS[DydxChainAsset.DYDX]}
|
||||
{chainTokenLabel}
|
||||
</Styled.InlineRow>
|
||||
),
|
||||
},
|
||||
@@ -215,7 +249,7 @@ export const TransferForm = ({
|
||||
key: 'amount',
|
||||
label: (
|
||||
<span>
|
||||
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[asset]}</Tag>
|
||||
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{tokensConfigs[asset].name}</Tag>
|
||||
</span>
|
||||
),
|
||||
value: (
|
||||
@@ -231,13 +265,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,
|
||||
@@ -302,7 +329,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>
|
||||
)}
|
||||
|
||||
@@ -342,13 +374,13 @@ 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={fee || undefined}
|
||||
isDisabled={!isAmountValid || !isAddressValid || !fee}
|
||||
isDisabled={!isAmountValid || !isAddressValid || !fee || isLoading}
|
||||
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fee)}
|
||||
/>
|
||||
</Styled.Footer>
|
||||
|
||||
@@ -3,9 +3,9 @@ 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 { DYDX_CHAIN_ASSET_TAGS, DydxChainAsset } from '@/constants/wallets';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import { useAccountBalance, useStringGetter } from '@/hooks';
|
||||
import { useAccountBalance, useTokenConfigs, useStringGetter } from '@/hooks';
|
||||
|
||||
import { Button } from '@/components/Button';
|
||||
import { DiffOutput } from '@/components/DiffOutput';
|
||||
@@ -37,6 +37,7 @@ export const TransferButtonAndReceipt = ({
|
||||
const stringGetter = useStringGetter();
|
||||
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
|
||||
const { size } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const { tokensConfigs } = useTokenConfigs();
|
||||
|
||||
const { equity: equityInfo, leverage: leverageInfo } =
|
||||
useSelector(getSubaccount, shallowEqual) || {};
|
||||
@@ -60,7 +61,7 @@ export const TransferButtonAndReceipt = ({
|
||||
key: 'fees',
|
||||
label: (
|
||||
<span>
|
||||
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag>
|
||||
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
|
||||
</span>
|
||||
),
|
||||
value: <Output type={OutputType.Asset} value={fees} />,
|
||||
@@ -69,7 +70,7 @@ export const TransferButtonAndReceipt = ({
|
||||
key: 'balance',
|
||||
label: (
|
||||
<span>
|
||||
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{DYDX_CHAIN_ASSET_TAGS[selectedAsset]}</Tag>
|
||||
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
|
||||
</span>
|
||||
),
|
||||
value: (
|
||||
|
||||
@@ -9,7 +9,13 @@ import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS, TOOLTIP_STRING_KEYS } from '@/constants/localization';
|
||||
import { DydxChainAsset, wallets } from '@/constants/wallets';
|
||||
|
||||
import { useAccounts, useBreakpoints, useStringGetter, useAccountBalance } from '@/hooks';
|
||||
import {
|
||||
useAccounts,
|
||||
useBreakpoints,
|
||||
useTokenConfigs,
|
||||
useStringGetter,
|
||||
useAccountBalance,
|
||||
} from '@/hooks';
|
||||
|
||||
import { OnboardingTriggerButton } from '@/views/dialogs/OnboardingTriggerButton';
|
||||
|
||||
@@ -39,6 +45,7 @@ export const AccountMenu = () => {
|
||||
const onboardingState = useSelector(getOnboardingState);
|
||||
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
||||
const { nativeTokenBalance } = useAccountBalance();
|
||||
const { usdcLabel, chainTokenLabel } = useTokenConfigs();
|
||||
|
||||
const { evmAddress, walletType, dydxAddress, hdKey } = useAccounts();
|
||||
|
||||
@@ -107,17 +114,23 @@ export const AccountMenu = () => {
|
||||
<div>
|
||||
<div>
|
||||
<Styled.label>
|
||||
{stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'Dv4TNT' } })}
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.ASSET_BALANCE,
|
||||
params: { ASSET: chainTokenLabel },
|
||||
})}
|
||||
{/* <AssetIcon symbol="DYDX" /> */}
|
||||
</Styled.label>
|
||||
<Styled.BalanceOutput type={OutputType.Asset} value={nativeTokenBalance} />
|
||||
</div>
|
||||
<AssetActions asset={DydxChainAsset.DYDX} dispatch={dispatch} />
|
||||
<AssetActions asset={DydxChainAsset.CHAINTOKEN} dispatch={dispatch} />
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<Styled.label>
|
||||
{stringGetter({ key: STRING_KEYS.ASSET_BALANCE, params: { ASSET: 'USDC' } })}
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.ASSET_BALANCE,
|
||||
params: { ASSET: usdcLabel },
|
||||
})}
|
||||
<AssetIcon symbol="USDC" />
|
||||
</Styled.label>
|
||||
<Styled.BalanceOutput
|
||||
|
||||
@@ -178,10 +178,11 @@ export const NotificationsMenu = ({
|
||||
};
|
||||
|
||||
const $UnreadIndicator = styled.div`
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-accent);
|
||||
border: 1px solid var(--color-layer-2);
|
||||
`;
|
||||
|
||||
const $TriggerContainer = styled.div`
|
||||
@@ -192,8 +193,8 @@ const $TriggerUnreadIndicator = styled($UnreadIndicator)`
|
||||
place-self: center;
|
||||
|
||||
position: relative;
|
||||
right: -0.425em;
|
||||
top: -0.425em;
|
||||
right: -0.2rem;
|
||||
top: -0.325rem;
|
||||
`;
|
||||
|
||||
const $Output = styled(Output)`
|
||||
|
||||